Repository navigation
Commit 41039de
fix(codegen): a field.map's value objects were written unvalidated — Java TPH, and every C# path (#362)
`field.map @objectRef` reaches the generated write surface, and its VALUES are
value objects carrying their own constraints. Those constraints were not
enforced. The failure mode is the bad one: a POST or PATCH carrying
`{"sites": {"hq": {…missing @required street…}}}` returned 201/200 and the row
was written. No error, nothing in a log, and nothing an adopter could find by
reading their own source — the defect lives in generated code.
**Java — TPH only.** Both per-subtype write paths validate field-by-field with
`validator.validateValue`, which applies a property's own constraints and stops;
it does not cascade `@Valid` into a nested bean. The vanilla handler has an
explicit cascade for exactly this reason, and it is not invoked on the TPH path.
Create now runs a null-guarded loop over the map's values (the body binds the
union `<Base>Dto`, whose accessors are plain, so a null check is the whole
guard); PATCH reuses the shared presence-guarded loop, since `<Sub>Patch` carries
the `hasX()`/`x()` tristate.
**C# — all three: vanilla create, vanilla PATCH, TPH.** Two independent causes,
and fixing either alone would have left it broken:
1. `RoutesGenerator.ValueObjectFields` admits `field.object` only, so a map never
reached the validating arms at all. A sibling `MapValueObjectFields` now feeds
them — deliberately NOT folded into `VoField`, which drives owned-nav
machinery (EF `OwnsMany`/`.ToJson`, array null-clears) that a plain
jsonb-converted property must not be dragged through. PATCH gets a typed arm
ahead of the generic property path, so a present key is validated before
assignment rather than after persistence.
2. `ValueObjectValidator` treated a `Dictionary` as a plain `IEnumerable`.
Enumerating one yields `KeyValuePair` structs, whose assembly is CoreLib, so
the graph reported "no value objects here" and validated **vacuously**. An
`IDictionary` arm now unwraps to `.Values`, placed before the `IEnumerable`
arm because every dictionary satisfies both. This also fixes a map nested
INSIDE a value object, which the property walk reaches by the same route.
TypeScript and Python were never affected; Kotlin writes no map column.
Scalar-valued maps (`@valueType`) carry no bean and are untouched — asserted as
the control in both new tests, so an implementation that validated *every* map
would fail rather than pass.
**Proven, not assumed.** The new Java test was run against the un-fixed generator
and fails there (`expected the TPH create to iterate the map's VALUES`). On the
C# side the previous session had written a characterization test that PINNED the
behaviour, asserting 201 on an invalid nested value; it is INVERTED rather than
deleted — silent acceptance is invisible by nature, so the case that proved it
quiet is the one that must now prove it loud, live against a real generated
server on Testcontainers Postgres.
Docs follow the code: `docs/features/field-types.md` and the audit skill's
`field.map` rung stop describing an open hole, and both now tell a reader on
1.0.0 that it IS open for them — the version is the thing that decides, and an
adopter who added their own boundary check needs to know it became redundant
rather than wrong. Agent-context corpus regenerated in the same commit (five
stacks), and the `@Valid` grounding exemption added earlier today is removed
again: the prose that named it is gone, so the exemption would have been a
comment describing a rung that no longer says that.
Closes #362.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTcEKXTQMYt84fAjuw5A2M1 parent e98d79a commit 41039de
14 files changed
Lines changed: 269 additions & 45 deletions
File tree
- agent-context/skills/metaobjects-audit
- docs/features
- fixtures/agent-context-conformance
- java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-audit
- java-react/expected/.claude/skills/metaobjects-audit
- python/expected/.claude/skills/metaobjects-audit
- ts-react-tanstack/expected/.claude/skills/metaobjects-audit
- ts-requirements/expected/.claude/skills/metaobjects-audit
- server
- csharp
- MetaObjects.Codegen.Tests
- MetaObjects.Codegen
- Generators
- Runtime
- MetaObjects.IntegrationTests/Api
- java/codegen-spring/src
- main/java/com/metaobjects/generator/spring
- test/java/com/metaobjects/generator/spring
- typescript/packages/sdk/test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
366 | 366 | | |
367 | 367 | | |
368 | 368 | | |
369 | | - | |
| 369 | + | |
370 | 370 | | |
371 | 371 | | |
372 | 372 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
66 | 67 | | |
67 | 68 | | |
68 | 69 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
366 | 366 | | |
367 | 367 | | |
368 | 368 | | |
369 | | - | |
| 369 | + | |
370 | 370 | | |
371 | 371 | | |
372 | 372 | | |
| |||
0 commit comments