From e6d3bb9dab7db0d47bab5d0e272586b8610dc65c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 21 May 2024 14:42:54 -0400 Subject: [PATCH] Expand references in PatternProperties when the property has no type. --- resource_expand.go | 70 ++++++------ resource_expand_test.go | 16 ++- testdata/AWS_ApiGatewayV2_RouteResponse.json | 112 +++++++++++++++++++ 3 files changed, 162 insertions(+), 36 deletions(-) create mode 100644 testdata/AWS_ApiGatewayV2_RouteResponse.json diff --git a/resource_expand.go b/resource_expand.go index 992b46e..7a09118 100644 --- a/resource_expand.go +++ b/resource_expand.go @@ -37,9 +37,10 @@ func (r *Resource) Expand() error { func (r *Resource) ResolveProperties(properties map[string]*Property) error { for propertyName, property := range properties { // For example: + // // "Configuration": { // "$ref": "#/definitions/ClusterConfiguration" - // }, + // } resolved, err := r.ResolveProperty(property) if err != nil { @@ -53,12 +54,13 @@ func (r *Resource) ResolveProperties(properties map[string]*Property) error { switch property.Type.String() { case PropertyTypeArray: // For example: + // // "DefaultCapacityProviderStrategy": { // "type": "array", // "items": { // "$ref": "#/definitions/CapacityProviderStrategyItem" // } - // }, + // } resolved, err = r.ResolveProperty(property.Items) if err != nil { @@ -71,6 +73,7 @@ func (r *Resource) ResolveProperties(properties map[string]*Property) error { if property.Items.Type.String() == PropertyTypeObject { // For example: + // // "Tags": { // "type": "array", // "items": { @@ -88,7 +91,7 @@ func (r *Resource) ResolveProperties(properties map[string]*Property) error { } } - case PropertyTypeObject: + case PropertyTypeObject, "": err = r.UnwrapOneOfProperties(property) if err != nil { @@ -96,6 +99,7 @@ func (r *Resource) ResolveProperties(properties map[string]*Property) error { } // For example: + // // "ClusterConfiguration": { // "type": "object", // "properties": { @@ -103,7 +107,21 @@ func (r *Resource) ResolveProperties(properties map[string]*Property) error { // "$ref": "#/definitions/ExecuteCommandConfiguration" // } // } - // }, + // } + // + // or + // + // "PresignedUrlConfig": { + // "properties": { + // "RoleArn": { + // "$ref": "#/definitions/RoleArn" + // }, + // "ExpiresInSec": { + // "$ref": "#/definitions/ExpiresInSec" + // } + // } + // } + err = r.ResolveProperties(property.Properties) if err != nil { @@ -111,6 +129,7 @@ func (r *Resource) ResolveProperties(properties map[string]*Property) error { } // For example: + // // "LambdaFunctionRecipeSource": { // "type": "object", // "properties": { @@ -123,38 +142,24 @@ func (r *Resource) ResolveProperties(properties map[string]*Property) error { // } // } // } - // }, + // } + // + // or + // + // "RouteParameters": { + // "patternProperties": { + // "": { + // "$ref": "#/definitions/ParameterConstraints" + // } + // }, + // "additionalProperties": false + // } + err = r.ResolveProperties(property.PatternProperties) if err != nil { return fmt.Errorf("resolving %s PatternProperties: %w", propertyName, err) } - - case "": - err = r.UnwrapOneOfProperties(property) - - if err != nil { - return fmt.Errorf("unwrapping %s OneOf Properties: %w", propertyName, err) - } - - if len(property.Properties) > 0 { - // For example: - // "PresignedUrlConfig": { - // "properties": { - // "RoleArn": { - // "$ref": "#/definitions/RoleArn" - // }, - // "ExpiresInSec": { - // "$ref": "#/definitions/ExpiresInSec" - // } - // } - // }, - err = r.ResolveProperties(property.Properties) - - if err != nil { - return fmt.Errorf("resolving %s Properties: %w", propertyName, err) - } - } } } @@ -202,6 +207,7 @@ func (r *Resource) ResolveProperty(property *Property) (bool, error) { func (r *Resource) UnwrapOneOfProperties(property *Property) error { if len(property.Properties) == 0 && len(property.PatternProperties) == 0 && len(property.OneOf) > 0 { // For example: + // // "ContentTransformation": { // "type": "object", // "oneOf": [ @@ -217,7 +223,7 @@ func (r *Resource) UnwrapOneOfProperties(property *Property) error { // ] // } // ] - // }, + // } unwrappedProperties := make(map[string]*Property) for _, propertySubschema := range property.OneOf { diff --git a/resource_expand_test.go b/resource_expand_test.go index 698d2c8..5c6054b 100644 --- a/resource_expand_test.go +++ b/resource_expand_test.go @@ -167,6 +167,13 @@ func TestResourceExpand_PatternProperties(t *testing.T) { PropertyPath: []string{"LambdaFunction", "ComponentDependencies", "VersionRequirement"}, ExpectedPropertyType: cfschema.PropertyTypeString, }, + { + TestDescription: "valid no type specified", + MetaSchemaPath: "provider.definition.schema.v1.json", + ResourceSchemaPath: "AWS_ApiGatewayV2_RouteResponse.json", + PropertyPath: []string{"ResponseParameters", "Required"}, + ExpectedPropertyType: cfschema.PropertyTypeBoolean, + }, } for _, testCase := range testCases { @@ -210,16 +217,17 @@ func TestResourceExpand_PatternProperties(t *testing.T) { } } - if property.Type == nil { - t.Fatalf("unexpected resource property (%s) type, got none", propertyName) + propertyType := cfschema.Type(cfschema.PropertyTypeObject) + if property.Type != nil { + propertyType = *property.Type } if i == len(testCase.PropertyPath)-1 { - if actual, expected := *property.Type, testCase.ExpectedPropertyType; actual != expected { + if actual, expected := propertyType, testCase.ExpectedPropertyType; actual != expected { t.Errorf("expected resource property (%s) type (%s), got: %s", propertyName, expected, actual) } } else { - if actual, expected := *property.Type, cfschema.Type(cfschema.PropertyTypeObject); actual != expected { + if actual, expected := propertyType, cfschema.Type(cfschema.PropertyTypeObject); actual != expected { t.Fatalf("expected resource property (%s) type (%s), got: %s", propertyName, expected, actual) } diff --git a/testdata/AWS_ApiGatewayV2_RouteResponse.json b/testdata/AWS_ApiGatewayV2_RouteResponse.json new file mode 100644 index 0000000..a723c3f --- /dev/null +++ b/testdata/AWS_ApiGatewayV2_RouteResponse.json @@ -0,0 +1,112 @@ +{ + "typeName": "AWS::ApiGatewayV2::RouteResponse", + "description": "The ``AWS::ApiGatewayV2::RouteResponse`` resource creates a route response for a WebSocket API. For more information, see [Set up Route Responses for a WebSocket API in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-route-response.html) in the *API Gateway Developer Guide*.", + "additionalProperties": false, + "properties": { + "RouteResponseKey": { + "type": "string", + "description": "The route response key." + }, + "ResponseParameters": { + "$ref": "#/definitions/RouteParameters", + "description": "The route response parameters." + }, + "RouteId": { + "type": "string", + "description": "The route ID." + }, + "ModelSelectionExpression": { + "type": "string", + "description": "The model selection expression for the route response. Supported only for WebSocket APIs." + }, + "ApiId": { + "type": "string", + "description": "The API identifier." + }, + "ResponseModels": { + "type": "object", + "description": "The response models for the route response." + }, + "RouteResponseId": { + "type": "string", + "description": "" + } + }, + "definitions": { + "ParameterConstraints": { + "type": "object", + "properties": { + "Required": { + "type": "boolean", + "description": "Specifies whether the parameter is required." + } + }, + "required": [ + "Required" + ], + "additionalProperties": false, + "description": "Specifies whether the parameter is required." + }, + "RouteParameters": { + "patternProperties": { + "": { + "$ref": "#/definitions/ParameterConstraints" + } + }, + "additionalProperties": false + } + }, + "required": [ + "RouteResponseKey", + "RouteId", + "ApiId" + ], + "createOnlyProperties": [ + "/properties/ApiId", + "/properties/RouteId" + ], + "readOnlyProperties": [ + "/properties/RouteResponseId" + ], + "primaryIdentifier": [ + "/properties/ApiId", + "/properties/RouteId", + "/properties/RouteResponseId" + ], + "tagging": { + "taggable": false, + "tagOnCreate": false, + "tagUpdatable": false, + "cloudFormationSystemTags": false + }, + "handlers": { + "create": { + "permissions": [ + "apigateway:POST" + ] + }, + "update": { + "permissions": [ + "apigateway:PATCH", + "apigateway:GET", + "apigateway:PUT" + ] + }, + "read": { + "permissions": [ + "apigateway:GET" + ] + }, + "delete": { + "permissions": [ + "apigateway:GET", + "apigateway:DELETE" + ] + }, + "list": { + "permissions": [ + "apigateway:GET" + ] + } + } +}