diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/InlineModelResolver.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/InlineModelResolver.java index 741874873c..a5882fcca4 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/InlineModelResolver.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/InlineModelResolver.java @@ -1,9 +1,6 @@ package io.swagger.v3.parser.util; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -102,10 +99,20 @@ private void flattenBody(String pathname, RequestBody body) String genericName = pathBody(pathname); if (model.getProperties() != null && model.getProperties().size() > 0) { flattenProperties(model.getProperties(), pathname); - String modelName = resolveModelName(model.getTitle(), genericName); - mediaType.setSchema(new Schema().$ref(modelName)); - addGenerated(modelName, model); - openAPI.getComponents().addSchemas(modelName, model); + if(openAPI.getComponents().getSchemas() == null ) { + createBodySchemaReference(mediaType, model, genericName); + } else if (!openAPI.getComponents().getSchemas().containsValue(model)) { + createBodySchemaReference(mediaType, model, genericName); + } else { + //Look at Components.schemas and use the reference name + String modelName = ""; + for (Map.Entry component : openAPI.getComponents().getSchemas().entrySet()) { + if (component.getValue().equals(model)) { + modelName = component.getKey(); + } + } + mediaType.setSchema(new Schema().$ref(modelName)); + } } else if (model instanceof ComposedSchema) { flattenComposedSchema(model, pathname); if (model.get$ref() == null) { @@ -145,6 +152,13 @@ private void flattenBody(String pathname, RequestBody body) } } + private void createBodySchemaReference(MediaType mediaType, Schema model, String genericName) { + String modelName = resolveModelName(model.getTitle(), genericName); + mediaType.setSchema(new Schema().$ref(modelName)); + addGenerated(modelName, model); + openAPI.getComponents().addSchemas(modelName, model); + } + private void flattenParams(String pathname, List parameters) { if (parameters == null){ @@ -482,32 +496,20 @@ public void flattenProperties(Map properties, String path) { for (String key : properties.keySet()) { Schema property = properties.get(key); if (isObjectSchema(property) && property.getProperties() != null && property.getProperties().size() > 0) { - String modelName = resolveModelName(property.getTitle(), path + "_" + key); - Schema model = createModelFromProperty(property, modelName); - String existing = matchGenerated(model); - if (existing != null) { - propsToUpdate.put(key, new Schema().$ref(existing)); - } else { - propsToUpdate.put(key, new Schema().$ref(RefType.SCHEMAS.getInternalPrefix()+modelName)); - modelsToAdd.put(modelName, model); - addGenerated(modelName, model); - openAPI.getComponents().addSchemas(modelName, model); + if(openAPI.getComponents().getSchemas() == null){ + createSchemaProperty(path, propsToUpdate, modelsToAdd, key, property); + }else if (!openAPI.getComponents().getSchemas().containsValue(property)) { + createSchemaProperty(path, propsToUpdate, modelsToAdd, key, property); } } else if (property instanceof ArraySchema) { ArraySchema ap = (ArraySchema) property; Schema inner = ap.getItems(); if (isObjectSchema(inner)) { if (inner.getProperties() != null && inner.getProperties().size() > 0) { - flattenProperties(inner.getProperties(), path); - String modelName = resolveModelName(inner.getTitle(), path + "_" + key); - Schema innerModel = createModelFromProperty(inner, modelName); - String existing = matchGenerated(innerModel); - if (existing != null) { - ap.setItems(new Schema().$ref(existing)); - } else { - ap.setItems(new Schema().$ref(modelName)); - addGenerated(modelName, innerModel); - openAPI.getComponents().addSchemas(modelName, innerModel); + if(openAPI.getComponents().getSchemas() == null) { + createArraySchemaProperty(path, key, ap, inner); + }else if (!openAPI.getComponents().getSchemas().containsValue(inner)) { + createArraySchemaProperty(path, key, ap, inner); } }else if (inner instanceof ComposedSchema && this.flattenComposedSchemas) { flattenComposedSchema(inner,key); @@ -553,6 +555,34 @@ public void flattenProperties(Map properties, String path) { } } + private void createArraySchemaProperty(String path, String key, ArraySchema ap, Schema inner) { + flattenProperties(inner.getProperties(), path); + String modelName = resolveModelName(inner.getTitle(), path + "_" + key); + Schema innerModel = createModelFromProperty(inner, modelName); + String existing = matchGenerated(innerModel); + if (existing != null) { + ap.setItems(new Schema().$ref(existing)); + } else { + ap.setItems(new Schema().$ref(modelName)); + addGenerated(modelName, innerModel); + openAPI.getComponents().addSchemas(modelName, innerModel); + } + } + + private void createSchemaProperty(String path, Map propsToUpdate, Map modelsToAdd, String key, Schema property) { + String modelName = resolveModelName(property.getTitle(), path + "_" + key); + Schema model = createModelFromProperty(property, modelName); + String existing = matchGenerated(model); + if (existing != null) { + propsToUpdate.put(key, new Schema().$ref(existing)); + } else { + propsToUpdate.put(key, new Schema().$ref(RefType.SCHEMAS.getInternalPrefix() + modelName)); + modelsToAdd.put(modelName, model); + addGenerated(modelName, model); + openAPI.getComponents().addSchemas(modelName, model); + } + } + private void flattenComposedSchema(Schema inner, String key) { ComposedSchema composedSchema = (ComposedSchema) inner; diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/InlineModelResolverTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/InlineModelResolverTest.java index ba2d40af59..68b614ce2e 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/InlineModelResolverTest.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/InlineModelResolverTest.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; +import io.swagger.v3.core.util.Yaml; import org.testng.annotations.Test; import io.swagger.v3.oas.models.Components; @@ -48,6 +49,8 @@ public void testIssueUnexpectedNullValues() throws Exception { assertTrue(openAPI.getComponents().getSchemas().get("verify_datasets").getExampleSetFlag()); } + + @Test public void testIssue1018() throws Exception { ParseOptions options = new ParseOptions(); @@ -845,7 +848,6 @@ public void resolveInlineArrayRequestBody() throws Exception { .requestBody(new RequestBody() .content(new Content().addMediaType("*/*",new MediaType() .schema(arraySchema)))))); - new InlineModelResolver().flatten(openAPI); RequestBody body = openAPI.getPaths().get("/hello").getGet().getRequestBody(); diff --git a/modules/swagger-parser-v3/src/test/resources/unexpectedNullValues.yaml b/modules/swagger-parser-v3/src/test/resources/unexpectedNullValues.yaml index 15d32faec5..426640b7c2 100644 --- a/modules/swagger-parser-v3/src/test/resources/unexpectedNullValues.yaml +++ b/modules/swagger-parser-v3/src/test/resources/unexpectedNullValues.yaml @@ -32,7 +32,6 @@ paths: country: CHN provider: DataZoo description: DataZoo China National ID - default: description: Returns an array of errors content: @@ -110,14 +109,12 @@ paths: country: CHN provider: DataZoo description: DataZoo China National ID - default: description: Returns an array of errors content: application/json: schema: $ref: '#/components/schemas/Errors' - components: schemas: Dataset: @@ -169,7 +166,7 @@ components: type: object properties: request: - type: string + type: string required: - request required: diff --git a/modules/swagger-parser/src/main/java/io/swagger/parser/OpenAPIParser.java b/modules/swagger-parser/src/main/java/io/swagger/parser/OpenAPIParser.java index f42633cdce..d1db26f251 100644 --- a/modules/swagger-parser/src/main/java/io/swagger/parser/OpenAPIParser.java +++ b/modules/swagger-parser/src/main/java/io/swagger/parser/OpenAPIParser.java @@ -18,7 +18,6 @@ public SwaggerParseResult readLocation(String url, List auth return output; } } - return output; } diff --git a/modules/swagger-parser/src/test/java/io/swagger/parser/OpenAPIParserTest.java b/modules/swagger-parser/src/test/java/io/swagger/parser/OpenAPIParserTest.java index cfcdafe211..7b8f8863a6 100644 --- a/modules/swagger-parser/src/test/java/io/swagger/parser/OpenAPIParserTest.java +++ b/modules/swagger-parser/src/test/java/io/swagger/parser/OpenAPIParserTest.java @@ -12,6 +12,7 @@ import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.parameters.RequestBody; +import io.swagger.v3.parser.OpenAPIV3Parser; import io.swagger.v3.parser.core.models.ParseOptions; import io.swagger.v3.parser.core.models.SwaggerParseResult; import io.swagger.v3.core.util.Json; @@ -32,6 +33,22 @@ public class OpenAPIParserTest { + @Test + public void testIssue_1599() { + OpenAPIParser openAPIParser = new OpenAPIParser(); + ParseOptions options = new ParseOptions(); + options.setResolve(true); + options.setResolveFully(true); + options.setFlatten(true); + SwaggerParseResult swaggerParseResult = openAPIParser.readLocation("petStore1599.yaml", null, options); + assertNotNull(swaggerParseResult.getOpenAPI()); + OpenAPI openAPI = swaggerParseResult.getOpenAPI(); + assertTrue(openAPI.getComponents().getSchemas().size() == 5); + assertNull(openAPI.getComponents().getSchemas().get("pet_category")); + assertNull(openAPI.getComponents().getSchemas().get("pet_body")); + assertNull(((Schema)openAPI.getComponents().getSchemas().get("Pet").getProperties().get("category")).get$ref()); + } + @Test public void testNPE_1685() { OpenAPIParser openAPIParser = new OpenAPIParser(); diff --git a/modules/swagger-parser/src/test/resources/petStore1599.yaml b/modules/swagger-parser/src/test/resources/petStore1599.yaml new file mode 100644 index 0000000000..a1493400d3 --- /dev/null +++ b/modules/swagger-parser/src/test/resources/petStore1599.yaml @@ -0,0 +1,199 @@ +swagger: '2.0' +info: + description: 'This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.' + version: 1.0.0 + title: Swagger Petstore + termsOfService: 'http://swagger.io/terms/' + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +host: petstore.swagger.io +basePath: /v2 +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: 'http://swagger.io' + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: 'http://swagger.io' +schemes: + - https + - http +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + consumes: + - application/json + - application/xml + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Pet object that needs to be added to the store + required: true + schema: + $ref: '#/definitions/Pet' + responses: + '200': + description: Succesful request + '405': + description: Invalid input + schema: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + consumes: + - application/json + - application/xml + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Pet object that needs to be added to the store + required: true + schema: + $ref: '#/definitions/Pet' + responses: + '200': + description: Succesful request + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + - api_key: [] +securityDefinitions: + petstore_auth: + type: oauth2 + authorizationUrl: 'https://petstore.swagger.io/oauth/authorize' + flow: implicit + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header +definitions: + Category: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/definitions/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/definitions/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + example: + id: 1000 + category: + id: 1000000 + name: category1 + name: Toby + photoUrls: + - www + - xxx + tags: + - id: 999 + name: puppy + - id: 888 + name: brown + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' \ No newline at end of file