Repository navigation
feat(codegen): add capability reachability, composition, and generate_types() API - #83
Conversation
Implement Task 4 (PR 1d) of the ucp-schema codegen pipeline: - Wire Commands::GenerateTypes in src/bin/ucp-schema.rs with --profile, --capability, --extension, --schema-dir (alias --schema-local-base), --schema-remote-base, --output, and --pretty. - Implement CliExitCode for CodegenError and generalize write_json_output to <T: serde::Serialize + ?Sized>. - Add CLI integration tests in tests/generate_types_test.rs covering stdout pretty/compact output, file output, repeatable/comma-delimited flags, and error exit codes. Stacked PR Roadmap: - PR 1a (#81): Identifier qualification, UCP keyword stripping, $ref rewriting - PR 1b (#82): Directional schema slicing and $ref alignment - PR 1c (#83): Capability/extension reachability, composition, and generate_types() library API - PR 1d (this PR): CLI generate-types subcommand - PR 2: Self-contained .well-known/ucp --profile scoping - PR 3: Inline conditional variant hoisting and ordered anyOf union lowering - PR 4: OpenAPI 3.1 REST binding assembly (generate-openapi)
Implement Task 4 (PR 1d) of the ucp-schema codegen pipeline: - Wire Commands::GenerateTypes in src/bin/ucp-schema.rs with --profile, --capability, --extension, --schema-dir (alias --schema-local-base), --schema-remote-base, --output, and --pretty. - Implement CliExitCode for CodegenError and generalize write_json_output to <T: serde::Serialize + ?Sized>. - Add CLI integration tests in tests/generate_types_test.rs covering stdout pretty/compact output, file output, repeatable/comma-delimited flags, and error exit codes. Stacked PR Roadmap: - PR 1a (#81): Identifier qualification, UCP keyword stripping, $ref rewriting - PR 1b (#82): Directional schema slicing and $ref alignment - PR 1c (#83): Capability/extension reachability, composition, and generate_types() library API - PR 1d (this PR): CLI generate-types subcommand - PR 2: Self-contained .well-known/ucp --profile scoping - PR 3: Inline conditional variant hoisting and ordered anyOf union lowering - PR 4: OpenAPI 3.1 REST binding assembly (generate-openapi)
Implement Task 4 (PR 1d) of the ucp-schema codegen pipeline: - Wire Commands::GenerateTypes in src/bin/ucp-schema.rs with --profile, --capability, --extension, --schema-dir (alias --schema-local-base), --schema-remote-base, --output, and --pretty. - Implement CliExitCode for CodegenError and generalize write_json_output to <T: serde::Serialize + ?Sized>. - Add CLI integration tests in tests/generate_types_test.rs covering stdout pretty/compact output, file output, repeatable/comma-delimited flags, and error exit codes. Stacked PR Roadmap: - PR 1a (#81): Identifier qualification, UCP keyword stripping, $ref rewriting - PR 1b (#82): Directional schema slicing and $ref alignment - PR 1c (#83): Capability/extension reachability, composition, and generate_types() library API - PR 1d (this PR): CLI generate-types subcommand - PR 2: Self-contained .well-known/ucp --profile scoping - PR 3: Inline conditional variant hoisting and ordered anyOf union lowering - PR 4: OpenAPI 3.1 REST binding assembly (generate-openapi)
b6fcada to
88c578b
Compare
Implement Task 4 (PR 1d) of the ucp-schema codegen pipeline: - Wire Commands::GenerateTypes in src/bin/ucp-schema.rs with --profile, --capability, --extension, --schema-dir (alias --schema-local-base), --schema-remote-base, --output, and --pretty. - Implement CliExitCode for CodegenError and generalize write_json_output to <T: serde::Serialize + ?Sized>. - Add CLI integration tests in tests/generate_types_test.rs covering stdout pretty/compact output, file output, repeatable/comma-delimited flags, and error exit codes. Stacked PR Roadmap: - PR 1a (#81): Identifier qualification, UCP keyword stripping, $ref rewriting - PR 1b (#82): Directional schema slicing and $ref alignment - PR 1c (#83): Capability/extension reachability, composition, and generate_types() library API - PR 1d (this PR): CLI generate-types subcommand - PR 2: Self-contained .well-known/ucp --profile scoping - PR 3: Inline conditional variant hoisting and ordered anyOf union lowering - PR 4: OpenAPI 3.1 REST binding assembly (generate-openapi)
19346d4 to
1ab4348
Compare
Implement Task 4 (PR 1d) of the ucp-schema codegen pipeline: - Wire Commands::GenerateTypes in src/bin/ucp-schema.rs with --profile, --capability, --extension, --schema-dir (alias --schema-local-base), --schema-remote-base, --output, and --pretty. - Implement CliExitCode for CodegenError and generalize write_json_output to <T: serde::Serialize + ?Sized>. - Add CLI integration tests in tests/generate_types_test.rs covering stdout pretty/compact output, file output, repeatable/comma-delimited flags, and error exit codes. Stacked PR Roadmap: - PR 1a (#81): Identifier qualification, UCP keyword stripping, $ref rewriting - PR 1b (#82): Directional schema slicing and $ref alignment - PR 1c (#83): Capability/extension reachability, composition, and generate_types() library API - PR 1d (this PR): CLI generate-types subcommand - PR 2: Self-contained .well-known/ucp --profile scoping - PR 3: Inline conditional variant hoisting and ordered anyOf union lowering - PR 4: OpenAPI 3.1 REST binding assembly (generate-openapi)
Implement Task 4 (PR 1d) of the ucp-schema codegen pipeline: - Wire Commands::GenerateTypes in src/bin/ucp-schema.rs with --profile, --capability, --extension, --schema-dir (alias --schema-local-base), --schema-remote-base, --output, and --pretty. - Implement CliExitCode for CodegenError and generalize write_json_output to <T: serde::Serialize + ?Sized>. - Add CLI integration tests in tests/generate_types_test.rs covering stdout pretty/compact output, file output, repeatable/comma-delimited flags, and error exit codes. Stacked PR Roadmap: - PR 1a (#81): Identifier qualification, UCP keyword stripping, $ref rewriting - PR 1b (#82): Directional schema slicing and $ref alignment - PR 1c (#83): Capability/extension reachability, composition, and generate_types() library API - PR 1d (this PR): CLI generate-types subcommand - PR 2: Self-contained .well-known/ucp --profile scoping - PR 3: Inline conditional variant hoisting and ordered anyOf union lowering - PR 4: OpenAPI 3.1 REST binding assembly (generate-openapi)
a85a726 to
8cac42b
Compare
88c578b to
4ed1ea1
Compare
4ed1ea1 to
7b74335
Compare
8cac42b to
9d93d16
Compare
7b74335 to
9380b3c
Compare
9d93d16 to
33ec2f4
Compare
9380b3c to
e626830
Compare
33ec2f4 to
31aae2f
Compare
damaz91
left a comment
There was a problem hiding this comment.
Great work on the reachability, $defs hoisting, and extension composition stages!
Left a few inline comments on open edge cases and clippy::nonminimal_bool lints that are still present at the top of the stack.
(Note: Also noticed a few items here—such as normalize_lexical_path dropping leading .. on relative paths, recursive allOf handling in merge_extension_into_schema, removing unused schema_remote_base, force_slice on response-only capabilities in Stage 5, and relaxing required on extension-contributed overlay properties—that are already addressed up-stack in #85, #86, and #88, so skipping those here. Minor nit: the Stage 1 bullet in the PR description still mentions short-name checkout matching, which was removed in d1ca9e4.)
31aae2f to
b1e72bf
Compare
e626830 to
dd19a22
Compare
…_types() API Implement Task 3 (PR 1c) of the ucp-schema codegen pipeline: - Stage 1 (src/codegen/reachability.rs): Schema discovery, short/reverse-domain capability and extension selection, extension reclassification, ambient protocol root seeding, and transitive $ref reachability closure with inactive extension $defs pruning. - Stage 2 (src/codegen/hoist.rs): Upfront $defs hoisting, pure re-export skipping, self-named role container extraction (*PlatformSchema, *BusinessSchema, *ResponseSchema), and collision-aware parent qualification. - Stage 3 (src/codegen/compose.rs): Active extension composition into root capabilities, in-place standalone sub-type overlays (Buyer, Payment, PaymentInstrument, Cart->Checkout), and container capability operations (both nested $defs and oneOf/allOf shapes). - Stages 5, 6, 8 & Public API (src/codegen/mod.rs, src/lib.rs): Directional slicing, $ref alignment, empty-object pruning (retaining ErrorResponse), and public GenerateTypesOptions, TypesBundleDoc, CompiledTypes, CodegenError, compile_types(), and generate_types() APIs. Stacked PR Roadmap: - PR 1a (#81): Identifier qualification, UCP keyword stripping, $ref rewriting - PR 1b (#82): Directional schema slicing and $ref alignment - PR 1c (this PR): Capability/extension reachability, composition, and generate_types() library API - PR 2: CLI generate-types subcommand and --profile loading - PR 3: Inline conditional variant hoisting and ordered anyOf union lowering - PR 4: OpenAPI 3.1 REST binding assembly (generate-openapi)
…onse-only allOf branches in request slices
…uilder, and guard --profile
…nd extension matching
…st/UpdateRequest slices
…itional items, and prune dead defs
…inactive_local_defs
b1e72bf to
ef101e9
Compare
…in ext_queries, and simplify bools
ef101e9 to
bebd94f
Compare
damaz91
left a comment
There was a problem hiding this comment.
Thanks for addressing all comments - LGTM!
|
|
||
| pub(super) fn collision_qualified_name(parent_pascal: &str, def_key: &str) -> String { | ||
| let def_pascal = to_pascal_case(def_key); | ||
| if def_pascal.starts_with(parent_pascal) { |
There was a problem hiding this comment.
One potential edge case to keeping def_pascal unchanged if it already starts with the parent's name would be if the def_key truly collides with a standalone type - this would result in unintentional overwrites if we are not being careful.
An example in shopping/fulfillment.json:
- The def keys include
fulfillment_option,fulfilment_group,fulfillment_method, etc., which match exactly with the actual schema file names inshopping/types/*. - Although today the def definitions contain pure 1:1 $ref exports to the type schemas and would be a no problem, if we ever add any new properties to the def keys directly in the future, this function would actually output the exact same qualified name for these def keys as the types schemas (and cause any unintentional overwrites down the line).
Maybe always returning the else-clause may be better here so we guarantee a new name is always returned (even if it may lead to duplicated parent prefixes like FulfillmentFulfillmentMethod)..?
There was a problem hiding this comment.
Great catch! Fixed in d685121: collision_qualified_name now unconditionally prefixes parent_pascal (format!("{parent_pascal}{}", to_pascal_case(def_key))) so any local $defs entry that collides with a standalone type or sibling $defs key is guaranteed to receive a distinct name.
| .and_then(Value::as_str) | ||
| .filter(|s| is_reverse_domain_name(s)) | ||
| .map(str::to_string); | ||
| let in_types_dir = path.components().any(|c| c.as_os_str() == "types"); |
There was a problem hiding this comment.
Another edge case: It looks like path holds the local absolute path, so if for whatever reason the schema files are located under a root path that already includes types (e.g., /home/user/types/ucp/), then this check will result in in_types_dir = true for all the files.
When we then do the is_capability check below, it will always return false and then all the capability related processing (e.g., request type resolution, extension composition, etc.) will be skipped.
The same edge case applies to the in_transports_dir check as well.
Maybe we should do another mutation on top of file_path to strip out the prefix before storing it back into LoadedSchema::path?
There was a problem hiding this comment.
Good call! Fixed in d685121: LoadedSchema::from_file now checks the immediate parent directory name (path.parent().and_then(Path::file_name)) for "types" and "transports", so ancestor workspace directories with those names no longer misclassify capability schemas.
| }; | ||
| for key in ["$ref", "ucp_request", "ucp_response", "description"] { | ||
| if let Some(val) = ext_obj.get(key) { | ||
| base_obj.insert(key.to_string(), val.clone()); |
There was a problem hiding this comment.
One small thing here is that this would always result in an overwrite, especially given extensions are processed via active_ext_indices (basically the last processed extension's value for these keys will remain).
While for most of the extensions we have today, this wouldn't be a big problem they shouldn't' be operating on the same base property - the only exception would be any payments related extensions (e.g., if I have both split payments and payment terms extensions active, since both of them modify on top of payment property in capabilities like Checkout, the description would effectively be overwritten).
There was a problem hiding this comment.
Makes sense! Overwriting description and ucp_request when an extension refines a base capability property is needed for single-property refinements (such as buyer_consent.json refining Checkout.properties.buyer.description and ucp_request.complete), and today the payment extensions touch disjoint levels (payment_split_payments.json refines payment.properties.instruments, which is redirected onto Payment, while payment_terms.json sets payment.description and merges terms / selected_term_id into Payment). If future extensions ever supply competing descriptions on the same base property, we can concatenate or prioritize them in a follow-up.
… dir for types/transports
Implement Task 4 (PR 1d) of the ucp-schema codegen pipeline: - Wire Commands::GenerateTypes in src/bin/ucp-schema.rs with --profile, --capability, --extension, --schema-dir (alias --schema-local-base), --schema-remote-base, --output, and --pretty. - Implement CliExitCode for CodegenError and generalize write_json_output to <T: serde::Serialize + ?Sized>. - Add CLI integration tests in tests/generate_types_test.rs covering stdout pretty/compact output, file output, repeatable/comma-delimited flags, and error exit codes.
Summary
Implements Task 3 (PR 1c) of the two-layer
ucp-schemacode-generation pipeline: capability/extension reachability discovery,$defshoisting & collision qualification, extension composition, and the publiccompile_types()/generate_types()library API.Pipeline Stages Added
src/codegen/reachability.rs):.jsonschemas in--schema-dirusing schema structure (name/versioncapability identity,extendsmetadata,ucp_request/ucp_responsedirectional annotations, and$defsreverse-domain parent inference), supports reverse-domain FQDN (dev.ucp.shopping.checkout) matching, reclassifies extension schemas passed via--capability, rejects non-extension capabilities passed via--extension, seeds ambient protocol roots (profile,ucp,capability,service,payment_handler,error_response), and computes the transitive$refclosure while pruning inactive extension$defs.src/codegen/hoist.rs):$defsentries into the top-level$defsbundle, skips pure re-exports, extracts self-named capability role schemas (*PlatformSchema,*BusinessSchema,*ResponseSchema), and parent-qualifies colliding local$defs(PaymentAp2MandateErrorCode,JsonrpcErrorResponse,A2aMessageMessage).src/codegen/compose.rs):Buyer,Payment,PaymentInstrument,Cart->Checkout), and container capability operations (CatalogSearch*,CatalogLookup*, supporting both nested$defsandoneOf/allOfextension shapes).src/codegen/mod.rs,src/lib.rs):$refalignment, and empty-object pruning (retainingErrorResponseand definitions referenced via$refanywhere in$defsor rootproperties), and exposesGenerateTypesOptions,TypesBundleDoc,CompiledTypes,CodegenError,compile_types(), andgenerate_types().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