Issue #1300: fix: reject identity-mapping fields wider than their column with a 422 - #1313
Merged
Merged
Conversation
…umn with a 422 A value longer than its column reached MariaDB and came back as a generic 500. The save endpoint's body is now an IdentityMappingRequest whose fields carry max_length read from the SQLAlchemy model's column widths, so an oversized value is a 422 naming the field. Enforced at the API boundary rather than on the shared datatypes DTO, which a dozen projects package for a class only the Identity Mapper uses; the service still receives plain IdentityMapping objects. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bjagg
approved these changes
Sep 26, 2026
bjagg
left a comment
Contributor
There was a problem hiding this comment.
Approving. 86 pass, and reverting core.py fails exactly the five over-width cases. I mutation-tested the limits: dropping one field's max_length, off-by-one in either direction, a single width for every field, skipping the conversion back to plain IdentityMapping, and reverting the body type are all caught. Pinning the widths as literals is what catches the subtle ones. The model, 02-ddl.sql and #1300's table agree, and the service's body-vs-path match means an over-long path segment can't bypass the check. Good call keeping this off the shared datatypes DTO.
Resolve CHANGELOG.md with #1310 by keeping both entries.
bjagg
added a commit
that referenced
this pull request
Sep 26, 2026
Resolve CHANGELOG.md with #1310 and #1313 by keeping every entry. In test_core.py, keep main's file plus this branch's TestMutationFailureDoesNotLeakBody, minus its monkeypatch.setattr(type_factory, "input_type_cache", {}) line: #1314 removed that module attribute, so the line raised AttributeError (the step #1314's PR body describes).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of Change
Problem.
IdentityMapping's string fields have nomax_length. As #1300 describes, a value longer than its column reaches MariaDB, fails with1406 Data too longin strict mode, and returns to the caller as a generic 500 throughDataStoreException. #1258 lowered the threshold for three fields, from 255 to 191.Solution: enforce the widths at the API boundary. The issue left the placement open; this puts it at the API boundary rather than on the shared DTO.
IdentityMappingRequest(IdentityMapping)inbases/lif/identity_mapper_restapi/core.pyis thePOST .../mappingsbody type. Each of the five string fields carriesmax_lengthread from the SQLAlchemy column (IdentityMappingModel.__table__.c[name].type.length). The model already mirrors02-ddl.sql, so there's no third copy of the numbers.IdentityMappingbefore calling the service. The request model only validates; the service and storage receive exactly what they did before. This also keepslist[IdentityMapping]type-correct forty(listis invariant) without widening the service's signature.422, withlocnaming it, e.g.["body", 0, "target_system_id"].Why not
max_lengthon the DTO incomponents/lif/datatypes. Only the Identity Mapper bricks useIdentityMapping, butdatatypesis packaged by 12 projects and watched by 9 deploy workflows. Putting it there would redeploy about nine services for a class one of them uses, and it would be a third copy of the widths. Here, only the Identity Mapper redeploys.Limitations.
max_lengthcounts characters, the same unit as MariaDB'sVARCHAR(n). The 500 this replaces is as the issue describes it; I didn't reproduce it against a real MariaDB for this PR. The tests prove the 422 at the API, which is the part this change owns. TheGET/DELETEpath parameters aren't limited: an over-long one just matches no row.How reviewers should test it.
Related Issues
Closes #1300
Refs #1131
Type of Change
Project Area(s) Affected
Checklist
uv run ruff check)uv run ruff format)uv run ty check)bases/lif/identity_mapper_restapi/README.md). No pageunder
docs/documents the Identity Mapper's status codesTesting
Two tests, each parametrized over all five fields, with the widths pinned as literals from the issue's table (191 / 191 / 191 / 100 / 255). A width change then shows up as a deliberate test edit.
main422, errorlocis exactly["body", 0, <field>], service never called200, service receives a plainIdentityMappingWith only
core.pyreverted tomain, the five "over" cases fail again. The three existing save tests, which compare the service's call arguments againstIdentityMapping(...), pass unchanged, which confirms the service still gets the plain DTO. The identity-mapper base, service and storage suites pass (86 tests).pre-commit run --fileson all four changed files is green, including the full pytest suite.Additional Notes
Merge order with #1312 (#1261, the 409 on a persistent collision). I trial-merged the two branches:
core.pyand the test file merge cleanly.CHANGELOG.mdandbases/lif/identity_mapper_restapi/README.mdconflict: both PRs add a paragraph at the same spot. Keep both.CHANGELOG.mdis also shared with #1310, #1311 and #1148, under the same keep-both rule.🤖 Generated with Claude Code