Add schema overlay support to the validation engine - #209
Conversation
Move the CompiledSchema types and the raw-CFN-registry -> compiled-schema transform (`compile_schema` and its helpers) out of the `full`-gated `codegen_schema_validator` module into a new, dependency-free `compiled_schema` module. Previously this transform was private and compiled only under the `full` feature, which pulls in the build pipeline's heavy dependencies (ureq, zip, ...). Extracting it to a standalone module that depends on nothing beyond serde/serde_json lets it be shared: the build-time codegen uses it here, and a follow-up change reuses the exact same transform at runtime so overlay schemas are compiled identically to bundled ones. This is a pure move -- no logic change. The emitted compiled_schemas.json is byte-identical. The only differences from the original code are: - `CompiledSchema` and `compile_schema` are now `pub` (so the module can be used across crates), and - one redundant closure was simplified (`|s| compile_sub(s)` -> `compile_sub`) to satisfy clippy, which now lints this code in the default build since it is no longer behind the `full` feature.
Add an `additionalSchemas` option to `EngineConfig` so callers can merge
extra CloudFormation resource-provider schemas on top of the bundled
schemas before validation. Each overlay extends or overrides the bundled
schema for its resource type: new properties/definitions are deep-merged,
`required` is unioned, and enum values replace the bundled enum for that
property path (mirroring spec2cdk's loadPatchedSpec per-property merge).
This lets templates that use pre-GA CloudFormation properties -- shipped
as CDK temporary schemas before the registry schema is published --
validate without false F3002 ("additional properties not allowed") and
W3030 (enum) violations, removing the need for per-PR acknowledge()
suppressions during simultaneous releases. When a temporary schema is
removed post-GA, validation tightens back to the published spec.
Details:
- validation-engine: add `AdditionalSchemaSource { type_name, schema }`
and `EngineConfig.additional_schemas`. Parsing and type-name
resolution live in `AdditionalSchemaSource::resolve()` (fails hard on
invalid JSON or a missing type name), keeping the validator
constructor infallible.
- schema-validator: add `SchemaValidator::with_additional_schemas(...)`
and `CompiledSchemaStore::apply_overlay(...)`; the new `overlay` module
compiles each overlay via the shared data-source transform and merges
it into the bundled schema.
- bindings (wasm, jvm): thread `additionalSchemas` through the engine
constructors, surfacing overlay errors to the caller.
Existing EngineConfig struct literals (CLI and tests) are updated to
spread defaults for the new field. Adds unit and end-to-end overlay
tests (100% line coverage of the overlay module).
# Conflicts: # src/bindings-jvm/src/lib.rs
|
CI status _ the two red checks are a pre-existing main failure, not from this change The failing jobs are only the binding smoke-test jobs _ test-wasm and test-jvm (all OSes). Everything substantive is green: format, lint (clippy), audit, all Rust test jobs (ubuntu/windows/macos), and CodeQL. Root cause. Both jobs fail on the same assertion, e.g. in bindings-wasm/tests/smoke.test.ts: AssertionError: expected undefined to be 'Bucket' StandardDiagnostic no longer has top-level resourceId / resourceType _ #206 (new Entity struct / logical ids) and #216 moved them into a nested entity { logicalId, entityType, resourceType }. The current serialized type is: interface StandardDiagnostic { The smoke tests, however, haven't been updated since #176 and still assert the removed resourceId / resourceType fields, so they now read undefined. The JVM binding tests fail for the same reason. Why this isn't from this PR. This change only adds additionalSchemas support; it doesn't touch the diagnostics crate, StandardDiagnostic, the entity model, or the WASM/JVM tests. Those files on this branch are identical to main, so Suggested resolution. These smoke tests should be updated to assert entity?.logicalId / entity?.resourceType (WASM) and the equivalent on the JVM side _ ideally in a separate fix owned by the diagnostics refactor. Happy to rebase |
|
Thanks for the PR, this is an useful feature! (Ignore the failing jvm/wasm tests, as long as the rust tests pass its good)
|
- Replace (not append) oneOf/anyOf/allOf/if_then_else when an overlay supplies them, so a complete overlay schema no longer duplicates composition branches. - When an inline overlay extends a $ref property, inline the referenced definition first so its constraints are preserved instead of silently dropped (falls back to dropping only an unresolvable ref). - Reject non-object JSON in AdditionalSchemaSource::resolve() so a bare value like "42" no longer compiles to an empty no-op overlay. Adds tests for each case.
|
Thanks for the thorough review! All three addressed in 1bb1dd8:
cargo build, cargo test, cargo clippy, and cargo fmt --all --check all pass. Thanks again _ the composition-append one in particular would have been a nasty false positive. |
Resolve conflicts from main's diagnostics/data-source updates: - codegen_schema_validator: keep this branch's extraction (the compiled schema types and transform live in data-source::compiled_schema); port main's new `enum_case_insensitive` field and `enumCaseInsensitive` parsing into compiled_schema so codegen output is unchanged. - schema-validator/store: keep both this PR's `apply_overlay` and main's new `insert_schema` test helper.
Review withdrawn by reviewer; feedback will be shared privately.
Summary
Adds an
additionalSchemasoption toEngineConfigthat lets callers merge extra CloudFormation resource-provider schemas on top of the bundled schemas before validation. This provides an extensibility point for validating templates against resource schemas that are not (yet) part of the engine's bundled set.Motivation
The engine validates against the CloudFormation provider schemas compiled into the binary. There is currently no way for an embedder to extend or override those schemas at runtime. As a result, a template that uses a resource property the bundled schema does not know about produces false positives such as:
F3002— "Additional properties are not allowed ('…' was unexpected)"W3030— a value "is not one of […]" for a property whose allowed set has since expandedThis change lets callers supply the corresponding resource-provider schema so those properties validate correctly. Overlays are additive and per-construction: when a caller stops supplying an overlay, validation reverts to the bundled schema with no residual state.
What changed
EngineConfig.additionalSchemas(AdditionalSchemaSource { typeName, schema }): each entry is a complete CloudFormation resource-provider schema (registry JSON) supplied as a string. Overlays are merged onto the bundled schema for the matchingtypeName:propertiesanddefinitionsare deep-merged,requiredis unioned,enumvalues on an overlay property replace the bundled enum for that property path,A
typeNamewith no bundled schema is inserted verbatim.schema-validator: newSchemaValidator::with_additional_schemas(...)andCompiledSchemaStore::apply_overlay(...), plus a newoverlaymodule that compiles each overlay and merges it into the store.Input validation lives in
AdditionalSchemaSource::resolve()and fails fast on invalid JSON or a missing type name, keeping the validator constructor infallible.Refactor (separate commit): the raw-registry → compiled-schema transform and its types were moved out of the build-only (
full-gated) codegen module into a new dependency-freedata-source::compiled_schemamodule, now shared by the build-time codegen and the runtime overlay path. This is a pure move — the emittedcompiled_schemas.jsonis byte-identical, and the module pulls in no dependencies beyondserde/serde_json.Bindings (WASM, JVM): thread
additionalSchemasthrough the engine constructors and surface overlay errors to the caller (an invalid overlay throws/raises at engine construction).TypeScript surface (auto-generated via
tsify):Testing
schema-validator(100% line coverage of theoverlaymodule), covering: adding a new property, enum replacement,requiredunion, deep/nested property and definition merges,$refhandling, schema-level metadata overrides, and hard-fail on invalid input.F3002, and an overlay expanding a property's enum suppresses the correspondingW3030, while the bundled behavior is unchanged when no overlay is supplied.cargo build --workspace,cargo test, andcargo clippypass; the generatedcompiled_schemas.jsonis unchanged by the refactor.Notes
..Default::default()(updated in-repo).