Skip to content

Commit

Permalink
fix to remove schema duplicates when resolveFully and then flatten
Browse files Browse the repository at this point in the history
  • Loading branch information
gracekarina committed May 26, 2022
1 parent 59b104b commit 8a25594
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<String, Schema> 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) {
Expand Down Expand Up @@ -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<Parameter> parameters)
{
if (parameters == null){
Expand Down Expand Up @@ -480,32 +494,20 @@ public void flattenProperties(Map<String, Schema> 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);
Expand Down Expand Up @@ -551,6 +553,34 @@ public void flattenProperties(Map<String, Schema> 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<String, Schema> propsToUpdate, Map<String, Schema> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,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;
Expand Down Expand Up @@ -834,7 +835,7 @@ 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public SwaggerParseResult readLocation(String url, List<AuthorizationValue> auth
return output;
}
}

return output;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.swagger.parser;

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.media.ArraySchema;
Expand All @@ -11,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;
Expand All @@ -31,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();
Expand Down
199 changes: 199 additions & 0 deletions modules/swagger-parser/src/test/resources/petStore1599.yaml
Original file line number Diff line number Diff line change
@@ -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: [email protected]
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'

0 comments on commit 8a25594

Please sign in to comment.