From a1dec31caf594783fb05c5f1c0692bd3cc21aa60 Mon Sep 17 00:00:00 2001 From: gracekarina Date: Wed, 15 Feb 2023 20:04:01 -0500 Subject: [PATCH 1/2] dereferencing internal parameters and responses from internal paths --- .../io/swagger/v3/parser/ResolverCache.java | 52 ++++++- .../parser/processors/ParameterProcessor.java | 4 +- .../v3/parser/test/OpenAPIV3ParserTest.java | 15 +- .../internalParametersAsNumbers/swagger.yaml | 147 ++++++++++++++++++ 4 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 modules/swagger-parser-v3/src/test/resources/internalParametersAsNumbers/swagger.yaml diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java index 382ede57be..67eb9a4924 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java @@ -1,7 +1,9 @@ package io.swagger.v3.parser; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import io.swagger.v3.core.util.Yaml; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.callbacks.Callback; @@ -109,7 +111,7 @@ public ResolverCache(OpenAPI openApi, List auths, String par public T loadRef(String ref, RefFormat refFormat, Class expectedType) { if (refFormat == RefFormat.INTERNAL) { //we don't need to go get anything for internal refs - Object loadedRef = loadInternalRef(ref); + Object loadedRef = loadInternalRef(ref, expectedType); try{ return expectedType.cast(loadedRef); @@ -304,9 +306,57 @@ protected String merge(String host, String ref) { } return host + ref; } + private T loadInternalRef(String ref, Class expectedType) { + final String[] refParts = ref.split("#/"); + + if (refParts.length > 2) { + throw new RuntimeException("Invalid ref format: " + ref); + } + + final String file = refParts[0]; + final String definitionPath = refParts.length == 2 ? refParts[1] : null; + + SwaggerParseResult deserializationUtilResult = new SwaggerParseResult(); + String contents = Yaml.pretty(openApi); + //JsonNode tree = ObjectMapper.convertValue(openApi,JsonNode.class); + + JsonNode tree = DeserializationUtils.deserializeIntoTree(contents, file, parseOptions, deserializationUtilResult); + String[] jsonPathElements = definitionPath.split("/"); + for (String jsonPathElement : jsonPathElements) { + if (tree.isArray()) { + try { + tree = tree.get(Integer.valueOf(jsonPathElement)); + } catch (NumberFormatException numberFormatException) { + // + } + } else { + tree = tree.get(unescapePointer(jsonPathElement)); + } + + //if at any point we do find an element we expect, print and error and abort + if (tree == null) { + throw new RuntimeException("Could not find " + definitionPath + " in contents of " + file); + } + } + + T result = null; + if (parseOptions.isValidateExternalRefs()) { + result = deserializeFragment(tree, expectedType, file, definitionPath); + } else { + if (expectedType.equals(Schema.class)) { + OpenAPIDeserializer deserializer = new OpenAPIDeserializer(); + result = (T) deserializer.getSchema((ObjectNode) tree, definitionPath.replace("/", "."), new OpenAPIDeserializer.ParseResult().openapi31(openapi31)); + } else { + result = DeserializationUtils.deserialize(tree, file, expectedType, openapi31); + } + } + + return result; + } + private Object loadInternalRef(String ref) { Object result = null; diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ParameterProcessor.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ParameterProcessor.java index 100846e7c2..4b3d08a067 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ParameterProcessor.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ParameterProcessor.java @@ -87,11 +87,11 @@ public List processParameters(List parameters) { if (parameter.get$ref() != null) { RefFormat refFormat = computeRefFormat(parameter.get$ref()); final Parameter resolvedParameter = cache.loadRef(parameter.get$ref(), refFormat, Parameter.class); - if (parameter.get$ref().startsWith("#") && parameter.get$ref().indexOf("#/components/parameters") <= -1) { + /*if (parameter.get$ref().startsWith("#") && parameter.get$ref().indexOf("#/components/parameters") <= -1) { //TODO: Not possible to add warning during resolve doesn't accept result as an input. Hence commented below line. //result.warning(location, "The parameter should use Reference Object to link to parameters that are defined at the OpenAPI Object's components/parameters."); continue; - } + }*/ if(resolvedParameter == null) { // can't resolve it! diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java index 97eac48a99..d370038d29 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java @@ -167,6 +167,19 @@ public void testIssue1780() { } + @Test + public void testInternalParametersAndResponsesAsNumbers() throws Exception { + ParseOptions options = new ParseOptions(); + options.setResolve(true); + SwaggerParseResult result = new OpenAPIV3Parser().readLocation("src/test/resources/internalParametersAsNumbers/swagger.yaml", null, options); + + Assert.assertNotNull(result); + Assert.assertNotNull(result.getOpenAPI()); + Yaml.prettyPrint(result.getOpenAPI()); + Assert.assertEquals(result.getOpenAPI().getPaths().get("/api/deal/{dealId}").getGet().getParameters().get(0).getName(), "dealId"); + Assert.assertEquals(result.getOpenAPI().getPaths().get("/api/deal/{dealId}").getGet().getResponses().get("200").getDescription(), "Success"); + } + @Test public void testParametersAndResponsesAsNumbers() throws Exception { ParseOptions options = new ParseOptions(); @@ -3360,7 +3373,7 @@ public void testValidateExternalRefsTrue() { OpenAPI openAPI = result.getOpenAPI(); assertNotNull(openAPI); assertNotNull(result.getMessages()); - assertEquals(result.getMessages().size(), 19); + assertEquals(result.getMessages().size(), 20); assertTrue(result.getMessages().contains("attribute components.requestBodies.NewItem.asdasd is unexpected (./ref.yaml)")); assertTrue(result.getMessages().contains("attribute components.requestBodies.NewItem.descasdasdription is unexpected (./ref.yaml)")); assertTrue(result.getMessages().contains("attribute components.responses.GeneralError.descrsaiption is unexpected (./ref.yaml)")); diff --git a/modules/swagger-parser-v3/src/test/resources/internalParametersAsNumbers/swagger.yaml b/modules/swagger-parser-v3/src/test/resources/internalParametersAsNumbers/swagger.yaml new file mode 100644 index 0000000000..7ad7fb1e2a --- /dev/null +++ b/modules/swagger-parser-v3/src/test/resources/internalParametersAsNumbers/swagger.yaml @@ -0,0 +1,147 @@ +openapi: 3.0.1 +servers: + # Added by API Auto Mocking Plugin + - description: SwaggerHub API Auto Mocking + url: https://demo.com +info: + description: api + version: v1 + title: API + contact: + name: John Doe + email: John.Doe@mail.com + license: + name: Apache 2.0 + url: "http://www.apache.org/licenses/LICENSE-2.0.html" +tags: + - name: Payments + description: Request payments + - name: Incentives + description: Retrieve incentives + - name: Deals + description: Save, update and search deals +paths: + /api/deal/{dealId}: + get: + tags: + - Deals + summary: Gets the latest version of a deal (based on TimeStamp) or the specified version or the transactional deal by state + description: "Versions of a deal are ordered by the TimeStamp, if the TimeStamp is not set, the Timestamp is\r\nset to the creation time." + operationId: DealGetVersionOne + parameters: + - $ref: '#/paths/~1GetDeal/get/parameters/0' #access by position + responses: + '200': + $ref: '#/paths/~1GetDeal/get/responses/200' #access by name + '401': + description: Unauthorized + '403': + description: Forbidden + '503': + description: Server Error + /GetDeal: + get: + tags: + - Deal + summary: Gets the latest version of a deal (based on TimeStamp) or the specified version or the transactional deal by state + description: "Versions of a deal are ordered by the TimeStamp, if the TimeStamp is not set, the Timestamp is\r\nset to the creation time." + operationId: DealGetVersionOne + parameters: + - name: dealId + in: path + description: The deal identifier. + required: true + style: simple + explode: false + schema: + maxLength: 250 + type: string + description: The deal identifier. + - name: version + in: query + description: The version of the deal + required: false + style: form + explode: true + schema: + type: string + description: The version of the deal + nullable: true + - name: transform + in: query + description: 'The transform''s name used to format the output data.

Example: vinCRM

' + required: false + style: form + explode: true + schema: + type: string + description: The transform's name used to format the output data + nullable: true + responses: + '200': + description: Success + content: + application/vnd.common.v3+json: + schema: + $ref: '#/components/schemas/GetDealResponse' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + content: + application/vnd.common.v3+json: + schema: + $ref: '#/components/schemas/inlineResponse4041' + '503': + description: Server Error +components: + schemas: + ReferenceKeyModel: + type: object + properties: + name: + type: string + nullable: true + createdByClientId: + type: string + nullable: true + value: + type: string + nullable: true + createdByDateTime: + type: string + format: date-time + additionalProperties: false + GetDealResponse: + type: object + properties: + id: + type: string + description: Deal API specified unique identifier + nullable: true + version: + type: string + description: The current version of the deal represented as a hash of the contract + nullable: true + href: + type: string + description: The href/url for the deal as it was created + nullable: true + references: + type: object + additionalProperties: + uniqueItems: true + type: array + items: + $ref: '#/components/schemas/ReferenceKeyModel' + description: Gets or sets the reference keys. + nullable: true + createdDate: + type: string + description: Gets the creation date and time of the deal version + format: date-time + nullable: true + additionalProperties: false + description: Full deal model \ No newline at end of file From 348d14b3b38dfb8f1247afdc8692fd9c2cada72b Mon Sep 17 00:00:00 2001 From: gracekarina Date: Mon, 27 Feb 2023 19:20:13 -0500 Subject: [PATCH 2/2] derencing internal refs not staticly --- .../io/swagger/v3/parser/ResolverCache.java | 59 +++++++++---------- .../parser/processors/ParameterProcessor.java | 5 -- .../v3/parser/test/ResolverCacheTest.java | 18 ++++-- 3 files changed, 42 insertions(+), 40 deletions(-) diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java index 67eb9a4924..ff84e47063 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java @@ -114,7 +114,10 @@ public T loadRef(String ref, RefFormat refFormat, Class expectedType) { Object loadedRef = loadInternalRef(ref, expectedType); try{ - return expectedType.cast(loadedRef); + if (loadedRef != null){ + return expectedType.cast(loadedRef); + } + return null; } catch (Exception e) { return null; @@ -307,7 +310,7 @@ protected String merge(String host, String ref) { return host + ref; } private T loadInternalRef(String ref, Class expectedType) { - + T result = null; final String[] refParts = ref.split("#/"); if (refParts.length > 2) { @@ -319,41 +322,37 @@ private T loadInternalRef(String ref, Class expectedType) { SwaggerParseResult deserializationUtilResult = new SwaggerParseResult(); String contents = Yaml.pretty(openApi); - //JsonNode tree = ObjectMapper.convertValue(openApi,JsonNode.class); - JsonNode tree = DeserializationUtils.deserializeIntoTree(contents, file, parseOptions, deserializationUtilResult); - - - String[] jsonPathElements = definitionPath.split("/"); - for (String jsonPathElement : jsonPathElements) { - if (tree.isArray()) { - try { - tree = tree.get(Integer.valueOf(jsonPathElement)); - } catch (NumberFormatException numberFormatException) { - // + if (contents != null) { + JsonNode tree = DeserializationUtils.deserializeIntoTree(contents, file, parseOptions, deserializationUtilResult); + String[] jsonPathElements = definitionPath.split("/"); + for (String jsonPathElement : jsonPathElements) { + if (tree.isArray()) { + try { + tree = tree.get(Integer.valueOf(jsonPathElement)); + } catch (NumberFormatException numberFormatException) { + // + } + } else { + tree = tree.get(unescapePointer(jsonPathElement)); } - } else { - tree = tree.get(unescapePointer(jsonPathElement)); - } - //if at any point we do find an element we expect, print and error and abort - if (tree == null) { - throw new RuntimeException("Could not find " + definitionPath + " in contents of " + file); + //if at any point we do find an element we expect, print and error and abort + if (tree == null) { + throw new RuntimeException("Could not find " + definitionPath + " in contents of " + file); + } } - } - - T result = null; - if (parseOptions.isValidateExternalRefs()) { - result = deserializeFragment(tree, expectedType, file, definitionPath); - } else { - if (expectedType.equals(Schema.class)) { - OpenAPIDeserializer deserializer = new OpenAPIDeserializer(); - result = (T) deserializer.getSchema((ObjectNode) tree, definitionPath.replace("/", "."), new OpenAPIDeserializer.ParseResult().openapi31(openapi31)); + if (parseOptions.isValidateExternalRefs()) { + result = deserializeFragment(tree, expectedType, file, definitionPath); } else { - result = DeserializationUtils.deserialize(tree, file, expectedType, openapi31); + if (expectedType.equals(Schema.class)) { + OpenAPIDeserializer deserializer = new OpenAPIDeserializer(); + result = (T) deserializer.getSchema((ObjectNode) tree, definitionPath.replace("/", "."), new OpenAPIDeserializer.ParseResult().openapi31(openapi31)); + } else { + result = DeserializationUtils.deserialize(tree, file, expectedType, openapi31); + } } } - return result; } diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ParameterProcessor.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ParameterProcessor.java index 4b3d08a067..ebe03e11eb 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ParameterProcessor.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ParameterProcessor.java @@ -87,11 +87,6 @@ public List processParameters(List parameters) { if (parameter.get$ref() != null) { RefFormat refFormat = computeRefFormat(parameter.get$ref()); final Parameter resolvedParameter = cache.loadRef(parameter.get$ref(), refFormat, Parameter.class); - /*if (parameter.get$ref().startsWith("#") && parameter.get$ref().indexOf("#/components/parameters") <= -1) { - //TODO: Not possible to add warning during resolve doesn't accept result as an input. Hence commented below line. - //result.warning(location, "The parameter should use Reference Object to link to parameters that are defined at the OpenAPI Object's components/parameters."); - continue; - }*/ if(resolvedParameter == null) { // can't resolve it! diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/ResolverCacheTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/ResolverCacheTest.java index 5cef43201c..71bfb1fc5d 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/ResolverCacheTest.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/ResolverCacheTest.java @@ -8,6 +8,7 @@ import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.core.util.Yaml; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.PathItem; @@ -181,7 +182,8 @@ public void testLoadInternalParameterRef(@Injectable Parameter mockedParameter) } @Test - public void testLoadInternalParameterRefWithSpaces(@Injectable Parameter mockedParameter) throws Exception { + public void testLoadInternalParameterRefWithSpaces() throws Exception { + Parameter mockedParameter = new Parameter(); OpenAPI openAPI = new OpenAPI(); openAPI.components(new Components().addParameters("foo bar", mockedParameter)); @@ -191,8 +193,9 @@ public void testLoadInternalParameterRefWithSpaces(@Injectable Parameter mockedP } @Test - public void testLoadInternalDefinitionRef(@Injectable Schema mockedModel) throws Exception { + public void testLoadInternalDefinitionRef() throws Exception { OpenAPI openAPI = new OpenAPI(); + Schema mockedModel = new Schema(); openAPI.components(new Components().addSchemas("foo", mockedModel)); ResolverCache cache = new ResolverCache(openAPI, auths, null); @@ -204,8 +207,9 @@ public void testLoadInternalDefinitionRef(@Injectable Schema mockedModel) throws } @Test - public void testLoadInternalDefinitionRefWithSpaces(@Injectable Schema mockedModel) throws Exception { + public void testLoadInternalDefinitionRefWithSpaces() throws Exception { OpenAPI openAPI = new OpenAPI(); + Schema mockedModel = new Schema(); openAPI.components(new Components().addSchemas("foo bar", mockedModel)); ResolverCache cache = new ResolverCache(openAPI, auths, null); @@ -214,13 +218,17 @@ public void testLoadInternalDefinitionRefWithSpaces(@Injectable Schema mockedMod } @Test - public void testLoadInternalDefinitionRefWithEscapedCharacters(@Injectable Schema mockedModel) throws Exception { + public void testLoadInternalDefinitionRefWithEscapedCharacters(@Injectable PathItem mockedPath) throws Exception { OpenAPI openAPI = new OpenAPI(); + + Schema mockedModel = new Schema(); + openAPI.path("/test", mockedPath); openAPI.components(new Components().addSchemas("foo~bar/baz~1", mockedModel)); ResolverCache cache = new ResolverCache(openAPI, auths, null); Schema actualResult = cache.loadRef("#/components/schemas/foo~0bar~1baz~01", RefFormat.INTERNAL, Schema.class); - assertEquals(actualResult, mockedModel); + assertEquals( actualResult, mockedModel); + Yaml.pretty(mockedModel); } @Test