feat: handle union types and update patched cdk schema#1172
Draft
feat: handle union types and update patched cdk schema#1172
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files
@@ Coverage Diff @@
## main #1172 +/- ##
=======================================
- Coverage 92.6% 92.2% -0.4%
=======================================
Files 25 25
Lines 5559 5641 +82
Branches 5559 5641 +82
=======================================
+ Hits 5150 5201 +51
- Misses 409 440 +31
... and 1 file with indirect coverage changes Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
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.
Fixes #
The CloudFormation schema files (
cdk-resources.jsonandcdk-types.json) were outdated, causing cdk-from-cfn to reject valid newer CloudFormation properties. For example,AvailabilityZoneRebalancingonAWS::ECS::Servicewould fail with:AvailabilityZoneRebalancing is not a valid property for resource ECSService of type AWS::ECS::ServiceThis blocked
cdk migrateand direct cdk-from-cfn usage for any template using properties added after the schema files were last generated.Additionally, the updated schema introduces
TypeReference::Uniontypes that the existing code did not handle. Union types arise from multiple sources:oneOf/anyOfto express properties that accept multiple value shapes. For example, a property might accept either a structured object or a raw JSON string. Theaws-service-specimporter (import-cloudformation-registry.ts) converts theseoneOf/anyOfconstructs directly into union types viamaybeUnion().aws-service-specperforms "schema unification" when merging type definitions from different sources (CloudFormation Resource Spec, Registry Schema, SAM specs). When different sources describe the same property with different types, it computes a "typed union" widening the type to accept both shapes rather than picking one and losing information.cdk-patched-schema-generatorcreates additional unions when a property has historical type changes. ItsTypeDecider.getType()checks ifpreviousTypesexist for a property and wraps[currentType, ...previousTypes]into a union, ensuring cdk-from-cfn can handle templates written against any version of the type definition.CloudFormation itself has no union concept. Each property has a single type. The unions are entirely a schema-level artifact. Without union support, any resource with union-typed properties would error with
"is not implemented for ResourceValue::Object"during IR translation.Changes
1. Regenerated schema files
Updated both schema files using
cdklabs/cdk-patched-schema-generator, which pulls from@aws-cdk/aws-service-spec(the authoritative source combining CloudFormation Resource Specification, Registry Schema, SAM specs, and patches).The 4 removed resources were deprecated AWS services (
AWS::DataSync::StorageSystem, 3xAWS::IoT1Click::*). The 84 removed types break down as: 4 belonged to those deprecated resources, 75 were misplaced under wrong parent resources and now exist under their correct parents, and 5 were phantom entries for a resource that never existed (AWS::CodeTest::PersistentConfiguration).2. Added union type resolution in
src/ir/resources/mod.rsObject values
The fix resolves unions to a concrete type using a priority order:
Named>Map>Json. This order reflects how much type information each variant provides for code generation.Namedtypes are specific CDK structs with known property names and typed children, producing the richest output.Maptypes know the value type but not the key names.Jsonis fully opaque, passing everything through as-is. We always pick the most informative type available so the generated CDK code is as strongly typed as possible. Only these three can represent an object value. The resolved type is stored in the resultingResourceIr::Objectso that downstream synthesizers emit property names correctly (Namedtypes go through camelCase conversion, whileJsonpreserves names as-is).For multi-Named unions (14 exist in the current schema), resolution uses two strategies:
Typefield (e.g."Api","S3","SQS") that identifies which event type they are. The resolver matches this value against Named variant names (e.g."SQS"matchesSQSEvent). This handles theAWS::Serverless::Functionevent union (14 Named types) and theAWS::Serverless::StateMachineevent union (4 Named types) independently.ContainerPropertiesvsMultiNodeContainerPropertieswhere there's no discriminator field.The union resolution logic is extracted into
ResourceTranslator::resolve_union_for_objectwith detailed documentation explaining the sources of union types and the resolution strategy.For multi-Map unions (3 exist in the current schema, all historical type pairs), the resolver picks the Map variant with the widest value type, preferring a Map whose value is a union over one with a single primitive. This ensures valid values aren't rejected when the current type accepts more shapes than the previous type (e.g.
mapOf(union(boolean, string))overmapOf(boolean)).Array values
The
Arrayarm also needed union handling. Properties likeAWS::CloudWatch::InsightRule.Tagshave typeunionOf: [json, listOf(CfnTag)]. When the template value is an array, the code now extracts theListvariant's item type from the union so that each array element gets properly typed translation (e.g. elements are translated throughCfnTag's property bag instead of falling back to untyped JSON).Examples
Object with primitive-only union —
AWS::IAM::Role.AssumeRolePolicyDocumentSchema type:
unionOf: [unionOf: [json, string], json]Parser produces
ResourceValue::Object→self.value_typeisUnion([Union([Json, String]), Json])→resolve_union_for_object→ zero Named variants → priority chain findsPrimitive(Json)→ property bag isMapOf(Json)→VersionandStatementpass through as-is → synthesizer seesPrimitivestructure → emits names verbatim.Object with multi-Named union + discriminator —
AWS::Serverless::FunctioneventsSchema type: union of 14 Named types (
S3Event,SNSEvent,SQSEvent,ApiEvent, ...)Parser produces
ResourceValue::ObjectforProcessQueue→self.value_typeisUnion(14 Named)→resolve_union_for_object→ 14 Named variants → discriminator: object has"Type": "SQS"→ loop variants →SQSEventcontains"SQS"→ match → returnNamed("...SQSEvent")→ property bag looks upSQSEventProperty→Queuetyped as string,BatchSizeas number → correctly typed CDK code.Object with multi-Named union + property scoring —
AWS::Batch::JobDefinitioncontainerSchema type:
unionOf: [Named(MultiNodeContainerProperties), Named(ContainerProperties)]Parser produces
ResourceValue::Object→ 2 Named variants → discriminator: no"Type"key → property scoring:MultiNodeContainerPropertiesmatchesImage+ResourceRequirements(2),ContainerPropertiesmatchesImage+ResourceRequirements+NetworkConfiguration(3) → pickContainerProperties(3 > 2) → children get correctly typed translation.Array with union type —
AWS::CloudWatch::InsightRule.TagsSchema type:
unionOf: [json, listOf(CfnTag)]Parser produces
ResourceValue::Array→self.value_typeisUnion([Json, List(CfnTag)])→ Array arm findsList(CfnTag)variant → extracts item typeNamed(CfnTag)→ each element translated throughCfnTagproperty bag →KeyandValuetyped as strings → correctly typed CDK code instead of untyped JSON passthrough.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.