Repository navigation
Conversation
33e031a to
233fbe2
Compare
dffea30 to
5c6f9b9
Compare
233fbe2 to
9971a9c
Compare
8ad5fa6 to
f8dd89c
Compare
7940a92 to
cd8c9b3
Compare
f8dd89c to
4ddcbe6
Compare
cd8c9b3 to
ff6d9c1
Compare
4ddcbe6 to
de91200
Compare
ff6d9c1 to
792c4ed
Compare
ce60854 to
2ad1c6c
Compare
damaz91
left a comment
There was a problem hiding this comment.
Clean implementation of Stage 7 decentralized subtype discovery (register_extended_subtypes) and Stage 3 extension overlay property relaxation (relax_extension_property_required)! Left a few inline notes on edge cases.
| *val = Value::String("optional".to_string()); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Schema transition objects in ucp_request / ucp_response:
relax_extension_property_required handles string shorthand ("ucp_request": "required") and per-operation strings ("ucp_request": {"complete": "required"}), but if an extension property uses a schema transition object ({"transition": {"from": "required", "to": "omit", ...}} or per-operation {"complete": {"transition": {"from": "required", ...}}}), val.as_str() == Some("required") won't match transition.from (which resolver::parse_transition_value uses as the active visibility). Consider also relaxing "from": "required" inside nested transition objects.
| .entry(disc_prop.to_string()) | ||
| .or_insert_with(|| Value::Object(Map::new())); | ||
| if let Some(disc_map) = disc_entry.as_object_mut() { | ||
| disc_map.remove("not"); |
There was a problem hiding this comment.
Inlined $ref discriminator constraints (pattern) leaking from <ParentUnion>Base into extended subtypes:
When a parent union's discriminator property is a $ref to an open string type with constraints (such as Policy.properties.type referencing ReverseDomainName), build_union_base_schema removes "$ref" and inlines ReverseDomainName's "pattern" onto PolicyBase.properties.type.
When register_extended_subtypes merges base_template (PolicyBase) into an extended subtype via inherit_parent_into_union_variant, PolicyBase.properties.type no longer has "$ref", so merge_schema_property retains "pattern" from PolicyBase. Stripping "pattern" (and "minLength", "maxLength", "format") from disc_map when pinning "const" ensures extended subtypes get the same clean {"type": "string", "const": "<tag>"} discriminator as variants lowered in lower_conditional_unions.
| let Some(base_template) = defs | ||
| .get(&base_def_name) | ||
| .or_else(|| defs.get(&parent_union)) | ||
| .cloned() |
There was a problem hiding this comment.
Closed unions lose properties and required before register_extended_subtypes runs:
Here .or_else(|| defs.get(&parent_union)) (along with the fallback loop in collect_lowered_union_discriminators) is designed to support closed unions where <ParentUnion>Base was not synthesized.
However, lower_conditional_unions (lines 334–348) already removes "properties" and "required" from defs[&parent_union], leaving only {"title": "<ParentUnion>", "anyOf": [...]}. If a decentralized subtype extends a closed union, base_template (defs.get(&parent_union)) has no properties or required, so inherit_parent_into_union_variant strips {"$ref": "#/$defs/<ParentUnion>"} from allOf without inheriting any parent fields.
| { | ||
| out.insert(name.clone(), disc_key.clone()); | ||
| break; | ||
| } |
There was a problem hiding this comment.
Closed-union discriminator detection order:
For unions without <name>Base, this fallback inspects the first referenced branch and picks the first property (in alphabetical BTreeMap order) that has a const or single-value enum. If a variant defines another const property that sorts before the discriminator key (e.g., "api_version": {"const": "1.0"} before "type": {"const": "..."}), or if a non-discriminator anyOf union references an object with a const field, it will record the wrong discriminator property.
Checking that all $ref branches share a const/single-enum property key (or passing the lowered union discriminator map from lower_conditional_unions) avoids false matches.
| continue; | ||
| }; | ||
| if branch_obj.contains_key("$ref") || branch_obj.contains_key("if") { | ||
| kept.push(branch); |
There was a problem hiding this comment.
Preserving non-$ref / non-if composition branches in fold_inline_allof_branches_into_subtype:
In Stage 6, flatten_single_object_allof preserves allOf branches containing ["$ref", "if", "then", "else", "anyOf", "oneOf", "allOf", "contains"].
Here, fold_inline_allof_branches_into_subtype only guards branch_obj.contains_key("$ref") || branch_obj.contains_key("if"). If a subtype has an allOf branch with "anyOf", "oneOf", or "not" (e.g., {"anyOf": [{"required": ["a"]}, {"required": ["b"]}]}), Stage 6 keeps it in allOf, and then fold_inline_allof_branches_into_subtype silently drops it because it has neither "$ref"/"if" nor "properties". Guarding the same composition keywords (and merging dependentRequired if present) prevents dropping constraint branches.
Working...
Summary
Implements Task 8 (PR 3c) of the two-layer
ucp-schemacode-generation pipeline: cross-file decentralized subtype discovery and registration (register_extended_subtypesinsrc/codegen/reify.rs), stacked on #87.Changes
src/codegen/reify.rs):$defsafterlower_conditional_unionsfor schemas whoseallOfreferences a lowered union<ParentUnion>(anyOf/oneOf) and defines aconst(or single-valueenum) tag on<ParentUnion>'s discriminator property (either at top-levelproperties.<disc>or in an inlineallOfbranch).allOfbranches into the subtype, merges<ParentUnion>Base(or<ParentUnion>) properties andrequiredinto the subtype, removes{"$ref": "#/$defs/<ParentUnion>"}fromallOf, and strips"default"from the subtype's discriminator property (invariantOU-4).<ParentUnion>Base.properties.<disc>.not.enum(invariantOU-2) and inserts{"$ref": "#/$defs/<Subtype>"}into<ParentUnion>.anyOfimmediately before the trailing{"$ref": "#/$defs/<ParentUnion>Base"}fallback branch (invariantOU-1).PaymentInstrument->CardPaymentInstrument) untouched soflatten_single_object_allofflattens them directly.Stacked PR Series Roadmap
$refrewriting (src/codegen/normalizer.rs)$refalignment (src/codegen/normalizer.rs)generate_types()library API (src/codegen/{mod,reachability,hoist,compose}.rs)generate-typessubcommand (src/bin/ucp-schema.rs,tests/generate_types_test.rs).well-known/ucp--profilescoping (src/codegen/profile.rs)src/codegen/normalizer.rs,src/codegen/reify.rs)<Union>Base) (src/codegen/reify.rs)src/codegen/reify.rs)generate_models.sh,postprocess_models.py,src/ucp_sdk/models/)generate_models.sh,scripts/generate-zod-from-types.mjs,src/spec_generated.ts)ucp-schema generate-openapi,src/openapi/mod.rs)Test Plan
cargo fmt --checkcargo test --all-targetscargo clippy --all-targets -- -D warnings