feat: enforce uniqueItems and contains/minContains/maxContains in generated zod schemas - #37
Merged
damaz91 merged 2 commits intoJul 30, 2026
Conversation
…erated zod schemas Follows js-sdk#34 (value-constraint injector, Universal-Commerce-Protocol#33): the same post-generation, object-scoped in-place injection now also restores the JSON Schema array set/cardinality constraints quicktype's typescript-zod target drops. - uniqueItems: true -> an appended .refine() uniqueness check (Context.eligibility). - contains + minContains/maxContains -> an appended .superRefine() cardinality check (checkout/cart/order totals MUST hold exactly one subtotal and one total entry). Injection stays object-scoped by the parent object's property-name set, so a checkout totals (references types/totals.json, which carries the contains rules) is constrained while a fulfillment-option/line-item totals (an inline total.json array with no contains) is left untouched. Idempotent and wired into the existing generate_models.sh injector step. Tests mirror spec-constraints.test.js and exercise each array constraint via .shape.
damaz91
approved these changes
Jul 30, 2026
damaz91
merged commit Jul 30, 2026
958d263
into
Universal-Commerce-Protocol:main
8 of 9 checks passed
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.
Follow-on to #34 (which closed #33). #34 restored the scalar, length, and
itemsconstraints that quicktype'stypescript-zodtarget drops during generation. This restores the array set and cardinality constraints it also drops, using the same post-generation, object-scoped in-place injector - no regeneration against a moving base.What it enforces
uniqueItems-> appended.refine(). One field:Context.eligibility(CheckoutCreateRequestContextSchema, aliased byLookupRequestContextSchema).contains+minContains+maxContains-> appended.superRefine(). The top-leveltotalsof the 8 checkout/cart/order response schemas MUST contain exactly onesubtotaland onetotalentry.Object-scoping is what keeps this correct: checkout
totals(which$refstypes/totals.jsoncarrying thecontainsrules) is constrained, while fulfillment-option, line-item, and adjustmenttotals(inline arrays without those rules) are left untouched. A test covers that distinction explicitly.Approach
Same mechanism as #34: constraints keyed by the parent object's full property-name set and applied only when the generated field's base kind agrees (the schema-drift guard), spliced onto the base
z.array(...)call via the TS compiler API, wired idempotently into the existinggenerate_models.shstep..refine()/.superRefine()are used for the two keywords zod has no native method for. zod is parse-only, so this is a safe, non-breaking addition.Scope note:
constconstwas evaluated and has no injectable target in the generated 2026-04-08 schemas: the surviving occurrences are either unified away by quicktype (themessage_*typeconsts collapse into a singleMessageTypeenum), live on subtypes that are not generated (PaymentInstrument.typeis correctly unconstrained at the base), rewritten by the compat projection, or sit inside anif/thenconditional. The only load-bearingconsts are the discriminators insidetotals'containsclauses, which this PR enforces via the contains mechanism. Soconstis intentionally out of scope.Verification
npm run build(strict tsc) clean;npm test24/24 pass (12 baseline from fix: enforce JSON Schema value constraints in generated zod schemas (#33) #34 + 12 new).spec_generated.tsfails exactly the 7 negative assertions (uniqueItems duplicate + checkout contains rejections + order cardinality); positives still pass. Non-vacuous.pre-commit(prettier v4.0.0-alpha.8, ShellCheck, codespell) clean.