diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java index 651a31e3f5..7c80f9108b 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java @@ -958,6 +958,9 @@ public PathItem getPathItem(ObjectNode obj, String location, ParseResult result) } else { pathItem.set$ref(ref.textValue()); } + if(ref.textValue().startsWith("#/components") && !(ref.textValue().startsWith("#/components/pathItems"))) { + result.warning(location, "$ref target "+ref.textValue() +" is not of expected type PathItem"); + } if(result.isOpenapi31()){ String value = getString("summary", obj, false, location, result); if (StringUtils.isNotBlank(value)) { @@ -1568,6 +1571,9 @@ public Link getLink(ObjectNode linkNode, String location, ParseResult result) { } else { link.set$ref(ref.textValue()); } + if(ref.textValue().startsWith("#/components") && !(ref.textValue().startsWith("#/components/links"))) { + result.warning(location, "$ref target "+ref.textValue() +" is not of expected type Link"); + } if (result.isOpenapi31()) { String desc = getString("description", linkNode, false, location, result); if (StringUtils.isNotBlank(desc)) { @@ -1692,6 +1698,9 @@ public Callback getCallback(ObjectNode node, String location, ParseResult result } else { callback.set$ref(ref.textValue()); } + if(ref.textValue().startsWith("#/components") && !(ref.textValue().startsWith("#/components/callbacks"))) { + result.warning(location, "$ref target "+ref.textValue() +" is not of expected type Callback"); + } return callback; } else { result.invalidType(location, "$ref", "string", node); @@ -1938,10 +1947,13 @@ public Parameter getParameter(ObjectNode obj, String location, ParseResult resul parameter = new Parameter(); String mungedRef = mungedRef(ref.textValue()); if (mungedRef != null) { - parameter.set$ref(mungedRef); - } else { - parameter.set$ref(ref.textValue()); - } + parameter.set$ref(mungedRef); + }else { + parameter.set$ref(ref.textValue()); + } + if(ref.textValue().startsWith("#/components") && !(ref.textValue().startsWith("#/components/parameters") || ref.textValue().startsWith("#/components/headers"))) { + result.warning(location, "$ref target "+ref.textValue() +" is not of expected type Parameter/Header"); + } if (result.isOpenapi31()) { String desc = getString("description", obj, false, location, result); if (StringUtils.isNotBlank(desc)) { @@ -2148,6 +2160,10 @@ public Header getHeader(ObjectNode headerNode, String location, ParseResult resu } else { header.set$ref(ref.textValue()); } + if(ref.textValue().startsWith("#/components") && !(ref.textValue().startsWith("#/components/parameters") || ref.textValue().startsWith("#/components/headers"))) { + result.warning(location, "$ref target "+ref.textValue() +" is not of expected type Header/Parameter"); + } + if (result.isOpenapi31()) { String desc = getString("description", headerNode, false, location, result); if (StringUtils.isNotBlank(desc)) { @@ -2310,6 +2326,9 @@ public SecurityScheme getSecurityScheme(ObjectNode node, String location, ParseR } else { securityScheme.set$ref(ref.textValue()); } + if(ref.textValue().startsWith("#/components") && !(ref.textValue().startsWith("#/components/securitySchemes"))) { + result.warning(location, "$ref target "+ref.textValue() +" is not of expected type securitySchemes"); + } if (result.isOpenapi31()) { String desc = getString("description", node, false, location, result); if (StringUtils.isNotBlank(desc)) { @@ -2743,6 +2762,9 @@ at the moment path passed as string (basePath) from upper components can be both if(schema.get$ref().startsWith("#/components/schemas")){// it's internal String refName = schema.get$ref().substring(schema.get$ref().lastIndexOf("/")+1); localSchemaRefs.put(refName,location); + } + if(ref.textValue().startsWith("#/components") && !(ref.textValue().startsWith("#/components/schemas"))) { + result.warning(location, "$ref target "+ref.textValue() +" is not of expected type Schema"); } return schema; } else { @@ -3216,6 +3238,9 @@ public Example getExample(ObjectNode node, String location, ParseResult result) } else { example.set$ref(ref.textValue()); } + if(ref.textValue().startsWith("#/components") && !(ref.textValue().startsWith("#/components/examples"))) { + result.warning(location, "$ref target "+ref.textValue() +" is not of expected type Examples"); + } if(result.isOpenapi31()){ String value = getString("summary", node, false, location, result); if (StringUtils.isNotBlank(value)) { @@ -3355,6 +3380,9 @@ public ApiResponse getResponse(ObjectNode node, String location, ParseResult res } else { apiResponse.set$ref(ref.textValue()); } + if(ref.textValue().startsWith("#/components") && !(ref.textValue().startsWith("#/components/responses"))) { + result.warning(location, "$ref target "+ref.textValue() +" is not of expected type Response"); + } if(result.isOpenapi31()){ String value = getString("description", node, false, location, result); if (StringUtils.isNotBlank(value)) { @@ -3376,7 +3404,7 @@ public ApiResponse getResponse(ObjectNode node, String location, ParseResult res ObjectNode headerObject = getObject("headers", node, false, location, result); if (headerObject != null) { - Map headers = getHeaders(headerObject, location, result, false); + Map headers = getHeaders(headerObject, String.format("%s.%s", location, "headers"), result, false); if (headers != null && headers.size() > 0) { apiResponse.setHeaders(headers); } @@ -3384,7 +3412,7 @@ public ApiResponse getResponse(ObjectNode node, String location, ParseResult res ObjectNode linksObj = getObject("links", node, false, location, result); if (linksObj != null) { - Map links = getLinks(linksObj, location, result, false); + Map links = getLinks(linksObj, String.format("%s.%s", location, "links"), result, false); if (links != null && links.size() > 0) { apiResponse.setLinks(links); } @@ -3605,6 +3633,9 @@ public RequestBody getRequestBody(ObjectNode node, String location, ParseResult } else { body.set$ref(ref.textValue()); } + if(ref.textValue().startsWith("#/components") && !(ref.textValue().startsWith("#/components/requestBodies"))) { + result.warning(location, "$ref target "+ref.textValue() +" is not of expected type RequestBody"); + } if (result.isOpenapi31()) { String desc = getString("description", node, false, location, result); if (StringUtils.isNotBlank(desc)) { @@ -3823,6 +3854,9 @@ public Schema getJsonSchema(JsonNode jsonNode, String location, ParseResult resu } else { schema.set$ref(ref.asText()); } + if(ref.textValue().startsWith("#/components") && !(ref.textValue().startsWith("#/components/schemas"))) { + result.warning(location, "$ref target "+ref.textValue() +" is not of expected type Schema"); + } } else { result.invalidType(location, "$ref", "string", node); } 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 e18eaa0da7..055c2de20d 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 @@ -86,6 +86,26 @@ public class OpenAPIV3ParserTest { protected int serverPort = getDynamicPort(); protected WireMockServer wireMockServer; + @Test + public void testIssue1758() throws Exception{ + ParseOptions options = new ParseOptions(); + SwaggerParseResult result = new OpenAPIV3Parser().readLocation("src/test/resources/issue1758.yaml", null, options); + + Assert.assertNotNull(result); + Assert.assertNotNull(result.getOpenAPI()); + assertEquals(result.getMessages().size(),9); + assertTrue(result.getMessages().contains("paths.'/path1'.$ref target #/components/schemas/xFoo is not of expected type PathItem")); + assertTrue(result.getMessages().contains("paths.'/foo'(get).parameters.$ref target #/components/schemas/xFoo is not of expected type Parameter/Header")); + assertTrue(result.getMessages().contains("paths.'/foo'(get).responses.default.headers.three.$ref target #/components/schemas/xFoo is not of expected type Header/Parameter")); + assertTrue(result.getMessages().contains("paths.'/foo'(get).requestBody.$ref target #/components/schemas/xFoo is not of expected type RequestBody")); + assertTrue(result.getMessages().contains("paths.'/foo'(get).responses.200.links.user.$ref target #/components/schemas/xFoo is not of expected type Link")); + assertTrue(result.getMessages().contains("paths.'/foo'(get).responses.200.content.'application/json'.schema.$ref target #/components/parameters/pet is not of expected type Schema")); + assertTrue(result.getMessages().contains("paths.'/foo'(get).responses.200.content.'application/json'.examples.one.$ref target #/components/schemas/xFoo is not of expected type Examples")); + assertTrue(result.getMessages().contains("paths.'/foo'(get).responses.400.$ref target #/components/schemas/xFoo is not of expected type Response")); + assertTrue(result.getMessages().contains("paths.'/foo'(get).callbacks.$ref target #/components/schemas/xFoo is not of expected type Callback")); + + } + @Test(description = "Test for not setting the schema type as default") public void testNotDefaultSchemaType() { ParseOptions options = new ParseOptions(); @@ -514,7 +534,6 @@ public void testIssueSameRefsDifferentModelValid() { options.setResolveFully(true); final SwaggerParseResult openAPI = parser.readLocation("src/test/resources/same-refs-different-model-valid.yaml", null, options); - Yaml.prettyPrint(openAPI); assertEquals(openAPI.getMessages().size(), 0); } diff --git a/modules/swagger-parser-v3/src/test/resources/issue1758.yaml b/modules/swagger-parser-v3/src/test/resources/issue1758.yaml new file mode 100644 index 0000000000..2e0db04dfe --- /dev/null +++ b/modules/swagger-parser-v3/src/test/resources/issue1758.yaml @@ -0,0 +1,55 @@ +openapi: 3.0.3 +info: + title: Missing validation rule for schemas in Headers. + version: 1.0.0 +servers: +- url: / +paths: + /path1: + $ref: '#/components/schemas/xFoo' + /foo: + get: + description: ok + parameters: + - $ref: '#/components/schemas/xFoo' + requestBody: + $ref: '#/components/schemas/xFoo' + responses: + default: + description: ok + headers: + three: + $ref: '#/components/schemas/xFoo' + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/parameters/pet' + examples: + one: + $ref: '#/components/schemas/xFoo' + links: + user: + $ref: '#/components/schemas/xFoo' + '400': + $ref: '#/components/schemas/xFoo' + callbacks: + mainHook: + $ref: "#/components/schemas/xFoo" +components: + schemas: + xFoo: + type: string + description: This isn't validated correctly + parameters: + pet: + name: X-pet + in: header + required: false + schema: + type: string + format: uuid + \ No newline at end of file