Skip to content

fix: enforce uniqueItems on generated array fields - #59

Merged
damaz91 merged 2 commits into
Universal-Commerce-Protocol:mainfrom
XiaolongZhang-TT:fix/enforce-unique-items
Aug 3, 2026
Merged

fix: enforce uniqueItems on generated array fields#59
damaz91 merged 2 commits into
Universal-Commerce-Protocol:mainfrom
XiaolongZhang-TT:fix/enforce-unique-items

Conversation

@XiaolongZhang-TT

Copy link
Copy Markdown
Contributor

Description

datamodel-code-generator drops uniqueItems, so generated list fields accept duplicate entries in violation of the schema. Three UCP array properties declare uniqueItems: true at the 2026-04-08 release this SDK targets: context.eligibility, card_payment_instrument.brands, and identity_linking.required_claims.

This extends postprocess_models.py to collect array property names declared with uniqueItems and inject a field_validator(mode="after") into each generated class that declares a matching list field. The check uses equality (item in seen) rather than a set, so it holds for both hashable (str) and unhashable (Pydantic model) items. It mirrors the data-driven, idempotent approach already used for minProperties (#55), and complements the open contains work in #57 — the third constraint the generator drops.

Two generated models carry the affected list fields and gain the validator: Context.eligibility and Constraints.brands. required_claims has no generated typed field (ScopePolicy is extra="allow" free-form, so the field is an untyped extra key), so it is not enforceable and is skipped. Verified against the real release/2026-04-08 schemas that exactly these two models are touched on a fresh tree.

Note: like #57, this modifies postprocess_models.py, so it may need a rebase if #57 lands first.

Category (Required)

Please select one or more categories that apply to this change.

  • Core Protocol: Changes to the base communication layer, global context, or breaking refactors. (Requires Technical Council approval)
  • Governance/Contributing: Updates to GOVERNANCE.md, CONTRIBUTING.md, or CODEOWNERS. (Requires Governance Council approval)
  • Capability: New schemas (Discovery, Cart, etc.) or extensions. (Requires Maintainer approval)
  • Documentation: Updates to README, or documentations regarding schema or capabilities. (Requires Maintainer approval)
  • Infrastructure: CI/CD, Linters, or build scripts. (Requires DevOps Maintainer approval)
  • Maintenance: Version bumps, lockfile updates, or minor bug fixes. (Requires DevOps Maintainer approval)
  • SDK: Language-specific SDK updates and releases. (Requires DevOps Maintainer approval)
  • Samples / Conformance: Maintaining samples and the conformance suite. (Requires Maintainer approval)
  • UCP Schema: Changes to the ucp-schema tool (resolver, linter, validator). (Requires Maintainer approval)
  • Community Health (.github): Updates to templates, workflows, or org-level configs. (Requires DevOps Maintainer approval)

Related Issues

None. (Follow-up to the constraint-enforcement work in #55; complements the open contains enforcement in #57.)

Checklist

  • I have followed the Contributing Guide (including Conventional Commits title requirements and ! for breaking changes).
  • I have updated the documentation (if applicable).
  • My changes pass all local linting and formatting checks.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • (For Core/Capability) I have included/updated the relevant JSON schemas.
  • I have regenerated Python Pydantic models by running generate_models.sh under python_sdk.

Screenshots / Logs (if applicable)

The injected validator (e.g. Constraints.brands):

@field_validator("brands", mode="after")
def _enforce_unique_items_brands(cls, value):  # noqa: N805
    """JSON Schema uniqueItems: reject duplicate entries."""
    if value is None:
        return value
    seen = []
    for item in value:
        if item in seen:
            raise ValueError("Items must be unique (schema uniqueItems=true)")
        seen.append(item)
    return value

Before: Constraints(brands=["visa", "visa"]) was accepted. After: it raises ValidationError ("Items must be unique").

postprocess run reports: uniqueItems fields ['brands', 'eligibility', 'required_claims'] -> 2 module(s) patched (context.py, card_payment_instrument.py).

Full suite green as CI runs it (python -m unittest discover -s tests -p "test_*.py"): 35 passed, 0 failed. ruff (--ignore D,E501, matching the pre-commit hook), ruff format, and codespell are clean.

@damaz91 damaz91 added status:needs-triage Signal that the PR is ready for human triage status:under-review and removed status:needs-triage Signal that the PR is ready for human triage labels Aug 3, 2026
datamodel-code-generator drops `uniqueItems`, so generated list fields
accept duplicate entries in violation of the schema. Three UCP array
properties declare `uniqueItems: true` at 2026-04-08: context.eligibility,
card_payment_instrument.brands, and identity_linking.required_claims.

Extend postprocess_models.py to collect array property names declared with
`uniqueItems` and inject a `field_validator(mode="after")` into each
generated class that declares a matching list field. The check uses
equality (`item in seen`) so it holds for both hashable (str) and
unhashable (model) items. Mirrors the data-driven, idempotent approach used
for minProperties (Universal-Commerce-Protocol#55).

Two generated models carry the affected list fields and gain the
validator: Context.eligibility and Constraints.brands. required_claims has
no generated typed field (ScopePolicy is extra="allow" free-form), so it is
not enforceable and is skipped.

Add UniqueItemsInjectorTest (scan walks nested properties and ignores
non-arrays; injection targets only matching list fields, is idempotent, and
enforces uniqueness when exec'd) and UniqueItemsSemanticTest (Constraints
rejects duplicate brands, accepts unique/None).
@damaz91
damaz91 force-pushed the fix/enforce-unique-items branch from 55d3608 to 0be0897 Compare August 3, 2026 08:57

@damaz91 damaz91 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebased on main to resolve conflicts with #57 (contains validation). Verified all tests pass. Approving.

@damaz91
damaz91 merged commit ba23d97 into Universal-Commerce-Protocol:main Aug 3, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants