From cea0a13b906a708102947f95b9b09d631ff60976 Mon Sep 17 00:00:00 2001 From: Travis Newhouse <12889757+travisnewhouse@users.noreply.github.com> Date: Tue, 24 Dec 2024 02:20:21 -0800 Subject: [PATCH] openapi2conv: convert references in nested additionalProperties schemas (#1047) * Convert references in nested additionalProperties schemas When the schema specified in additionalProperties contains another nested schema with additionalProperties, the references must be converted at all of levels of the nested schemas. * make toV3AdditionalProperties a non-exported function --- openapi2conv/openapi2_conv.go | 27 ++++-- openapi2conv/openapi2_conv_test.go | 128 +++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 5 deletions(-) diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go index ec7646d2e..8aa452551 100644 --- a/openapi2conv/openapi2_conv.go +++ b/openapi2conv/openapi2_conv.go @@ -513,11 +513,7 @@ func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef { MaxProps: schema.Value.MaxProps, AllOf: make(openapi3.SchemaRefs, len(schema.Value.AllOf)), Properties: make(openapi3.Schemas), - AdditionalProperties: schema.Value.AdditionalProperties, - } - - if schema.Value.AdditionalProperties.Schema != nil { - v3Schema.AdditionalProperties.Schema.Ref = ToV3Ref(schema.Value.AdditionalProperties.Schema.Ref) + AdditionalProperties: toV3AdditionalProperties(schema.Value.AdditionalProperties), } if schema.Value.Discriminator != "" { @@ -551,6 +547,27 @@ func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef { } } +func toV3AdditionalProperties(from openapi3.AdditionalProperties) openapi3.AdditionalProperties { + return openapi3.AdditionalProperties{ + Has: from.Has, + Schema: convertRefsInV3SchemaRef(from.Schema), + } +} + +func convertRefsInV3SchemaRef(from *openapi3.SchemaRef) *openapi3.SchemaRef { + if from == nil { + return nil + } + to := *from + to.Ref = ToV3Ref(to.Ref) + if to.Value != nil { + v := *from.Value + to.Value = &v + to.Value.AdditionalProperties = toV3AdditionalProperties(to.Value.AdditionalProperties) + } + return &to +} + var ref2To3 = map[string]string{ "#/definitions/": "#/components/schemas/", "#/responses/": "#/components/responses/", diff --git a/openapi2conv/openapi2_conv_test.go b/openapi2conv/openapi2_conv_test.go index 317772152..659ccb69e 100644 --- a/openapi2conv/openapi2_conv_test.go +++ b/openapi2conv/openapi2_conv_test.go @@ -65,6 +65,134 @@ func TestConvOpenAPIV2ToV3(t *testing.T) { require.JSONEq(t, exampleV3, string(data)) } +func TestConvOpenAPIV2ToV3WithAdditionalPropertiesSchemaRef(t *testing.T) { + v2 := []byte(` +{ + "basePath": "/v2", + "host": "test.example.com", + "info": { + "title": "MyAPI", + "version": "0.1" + }, + "paths": { + "/foo": { + "get": { + "operationId": "getFoo", + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "returns all information", + "schema":{ + "type":"object", + "additionalProperties":{ + "$ref":"#/definitions/Foo" + } + } + } + }, + "summary": "get foo" + } + } + }, + "definitions": { + "Foo": { + "type": "object", + "properties": { + "a": { + "type": "string" + } + } + } + }, + "schemes": [ + "http" + ], + "swagger": "2.0" +} +`) + + var doc2 openapi2.T + err := json.Unmarshal(v2, &doc2) + require.NoError(t, err) + + doc3, err := ToV3(&doc2) + require.NoError(t, err) + err = doc3.Validate(context.Background()) + require.NoError(t, err) + + responseSchema := doc3.Paths.Value("/foo").Get.Responses.Value("200").Value.Content.Get("application/json").Schema.Value + require.Equal(t, &openapi3.Types{"object"}, responseSchema.Type) + require.Equal(t, "#/components/schemas/Foo", responseSchema.AdditionalProperties.Schema.Ref) +} + +func TestConvOpenAPIV2ToV3WithNestedAdditionalPropertiesSchemaRef(t *testing.T) { + v2 := []byte(` +{ + "basePath": "/v2", + "host": "test.example.com", + "info": { + "title": "MyAPI", + "version": "0.1" + }, + "paths": { + "/foo": { + "get": { + "operationId": "getFoo", + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "returns all information", + "schema":{ + "type":"object", + "additionalProperties":{ + "type":"object", + "additionalProperties":{ + "$ref":"#/definitions/Foo" + } + } + } + } + }, + "summary": "get foo" + } + } + }, + "definitions": { + "Foo": { + "type": "object", + "properties": { + "a": { + "type": "string" + } + } + } + }, + "schemes": [ + "http" + ], + "swagger": "2.0" +} +`) + + var doc2 openapi2.T + err := json.Unmarshal(v2, &doc2) + require.NoError(t, err) + + doc3, err := ToV3(&doc2) + require.NoError(t, err) + err = doc3.Validate(context.Background()) + require.NoError(t, err) + + responseSchema := doc3.Paths.Value("/foo").Get.Responses.Value("200").Value.Content.Get("application/json").Schema.Value + require.Equal(t, &openapi3.Types{"object"}, responseSchema.Type) + require.Equal(t, &openapi3.Types{"object"}, responseSchema.AdditionalProperties.Schema.Value.Type) + require.Equal(t, "#/components/schemas/Foo", responseSchema.AdditionalProperties.Schema.Value.AdditionalProperties.Schema.Ref) +} + const exampleV2 = ` { "basePath": "/v2",