Skip to content

Commit a5b5c3c

Browse files
committed
fix to remove schema duplicates when resolveFully and then flatten
1 parent 59b104b commit a5b5c3c

File tree

5 files changed

+277
-30
lines changed

5 files changed

+277
-30
lines changed

modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/InlineModelResolver.java

+58-28
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package io.swagger.v3.parser.util;
22

3-
import java.util.ArrayList;
4-
import java.util.HashMap;
5-
import java.util.List;
6-
import java.util.Map;
3+
import java.util.*;
74

85
import org.slf4j.Logger;
96
import org.slf4j.LoggerFactory;
@@ -102,10 +99,20 @@ private void flattenBody(String pathname, RequestBody body)
10299
String genericName = pathBody(pathname);
103100
if (model.getProperties() != null && model.getProperties().size() > 0) {
104101
flattenProperties(model.getProperties(), pathname);
105-
String modelName = resolveModelName(model.getTitle(), genericName);
106-
mediaType.setSchema(new Schema().$ref(modelName));
107-
addGenerated(modelName, model);
108-
openAPI.getComponents().addSchemas(modelName, model);
102+
if(openAPI.getComponents().getSchemas() == null ) {
103+
createBodySchemaReference(mediaType, model, genericName);
104+
} else if (!openAPI.getComponents().getSchemas().containsValue(model)) {
105+
createBodySchemaReference(mediaType, model, genericName);
106+
} else {
107+
//Look at Components.schemas and use the reference name
108+
String modelName = "";
109+
for (Map.Entry<String, Schema> component : openAPI.getComponents().getSchemas().entrySet()) {
110+
if (component.getValue().equals(model)) {
111+
modelName = component.getKey();
112+
}
113+
}
114+
mediaType.setSchema(new Schema().$ref(modelName));
115+
}
109116
} else if (model instanceof ComposedSchema) {
110117
flattenComposedSchema(model, pathname);
111118
if (model.get$ref() == null) {
@@ -145,6 +152,13 @@ private void flattenBody(String pathname, RequestBody body)
145152
}
146153
}
147154

155+
private void createBodySchemaReference(MediaType mediaType, Schema model, String genericName) {
156+
String modelName = resolveModelName(model.getTitle(), genericName);
157+
mediaType.setSchema(new Schema().$ref(modelName));
158+
addGenerated(modelName, model);
159+
openAPI.getComponents().addSchemas(modelName, model);
160+
}
161+
148162
private void flattenParams(String pathname, List<Parameter> parameters)
149163
{
150164
if (parameters == null){
@@ -480,32 +494,20 @@ public void flattenProperties(Map<String, Schema> properties, String path) {
480494
for (String key : properties.keySet()) {
481495
Schema property = properties.get(key);
482496
if (isObjectSchema(property) && property.getProperties() != null && property.getProperties().size() > 0) {
483-
String modelName = resolveModelName(property.getTitle(), path + "_" + key);
484-
Schema model = createModelFromProperty(property, modelName);
485-
String existing = matchGenerated(model);
486-
if (existing != null) {
487-
propsToUpdate.put(key, new Schema().$ref(existing));
488-
} else {
489-
propsToUpdate.put(key, new Schema().$ref(RefType.SCHEMAS.getInternalPrefix()+modelName));
490-
modelsToAdd.put(modelName, model);
491-
addGenerated(modelName, model);
492-
openAPI.getComponents().addSchemas(modelName, model);
497+
if(openAPI.getComponents().getSchemas() == null){
498+
createSchemaProperty(path, propsToUpdate, modelsToAdd, key, property);
499+
}else if (!openAPI.getComponents().getSchemas().containsValue(property)) {
500+
createSchemaProperty(path, propsToUpdate, modelsToAdd, key, property);
493501
}
494502
} else if (property instanceof ArraySchema) {
495503
ArraySchema ap = (ArraySchema) property;
496504
Schema inner = ap.getItems();
497505
if (isObjectSchema(inner)) {
498506
if (inner.getProperties() != null && inner.getProperties().size() > 0) {
499-
flattenProperties(inner.getProperties(), path);
500-
String modelName = resolveModelName(inner.getTitle(), path + "_" + key);
501-
Schema innerModel = createModelFromProperty(inner, modelName);
502-
String existing = matchGenerated(innerModel);
503-
if (existing != null) {
504-
ap.setItems(new Schema().$ref(existing));
505-
} else {
506-
ap.setItems(new Schema().$ref(modelName));
507-
addGenerated(modelName, innerModel);
508-
openAPI.getComponents().addSchemas(modelName, innerModel);
507+
if(openAPI.getComponents().getSchemas() == null) {
508+
createArraySchemaProperty(path, key, ap, inner);
509+
}else if (!openAPI.getComponents().getSchemas().containsValue(inner)) {
510+
createArraySchemaProperty(path, key, ap, inner);
509511
}
510512
}else if (inner instanceof ComposedSchema && this.flattenComposedSchemas) {
511513
flattenComposedSchema(inner,key);
@@ -551,6 +553,34 @@ public void flattenProperties(Map<String, Schema> properties, String path) {
551553
}
552554
}
553555

556+
private void createArraySchemaProperty(String path, String key, ArraySchema ap, Schema inner) {
557+
flattenProperties(inner.getProperties(), path);
558+
String modelName = resolveModelName(inner.getTitle(), path + "_" + key);
559+
Schema innerModel = createModelFromProperty(inner, modelName);
560+
String existing = matchGenerated(innerModel);
561+
if (existing != null) {
562+
ap.setItems(new Schema().$ref(existing));
563+
} else {
564+
ap.setItems(new Schema().$ref(modelName));
565+
addGenerated(modelName, innerModel);
566+
openAPI.getComponents().addSchemas(modelName, innerModel);
567+
}
568+
}
569+
570+
private void createSchemaProperty(String path, Map<String, Schema> propsToUpdate, Map<String, Schema> modelsToAdd, String key, Schema property) {
571+
String modelName = resolveModelName(property.getTitle(), path + "_" + key);
572+
Schema model = createModelFromProperty(property, modelName);
573+
String existing = matchGenerated(model);
574+
if (existing != null) {
575+
propsToUpdate.put(key, new Schema().$ref(existing));
576+
} else {
577+
propsToUpdate.put(key, new Schema().$ref(RefType.SCHEMAS.getInternalPrefix() + modelName));
578+
modelsToAdd.put(modelName, model);
579+
addGenerated(modelName, model);
580+
openAPI.getComponents().addSchemas(modelName, model);
581+
}
582+
}
583+
554584
private void flattenComposedSchema(Schema inner, String key) {
555585

556586
ComposedSchema composedSchema = (ComposedSchema) inner;

modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/InlineModelResolverTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.List;
1515
import java.util.Map;
1616

17+
import io.swagger.v3.core.util.Yaml;
1718
import org.testng.annotations.Test;
1819

1920
import io.swagger.v3.oas.models.Components;
@@ -834,7 +835,7 @@ public void resolveInlineArrayRequestBody() throws Exception {
834835
.requestBody(new RequestBody()
835836
.content(new Content().addMediaType("*/*",new MediaType()
836837
.schema(arraySchema))))));
837-
838+
Yaml.prettyPrint(openAPI);
838839
new InlineModelResolver().flatten(openAPI);
839840

840841
RequestBody body = openAPI.getPaths().get("/hello").getGet().getRequestBody();

modules/swagger-parser/src/main/java/io/swagger/parser/OpenAPIParser.java

-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ public SwaggerParseResult readLocation(String url, List<AuthorizationValue> auth
1818
return output;
1919
}
2020
}
21-
2221
return output;
2322
}
2423

modules/swagger-parser/src/test/java/io/swagger/parser/OpenAPIParserTest.java

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.swagger.parser;
22

3+
import io.swagger.v3.core.util.Yaml;
34
import io.swagger.v3.oas.models.Components;
45
import io.swagger.v3.oas.models.OpenAPI;
56
import io.swagger.v3.oas.models.media.ArraySchema;
@@ -11,6 +12,7 @@
1112
import io.swagger.v3.oas.models.parameters.Parameter;
1213
import io.swagger.v3.oas.models.parameters.RequestBody;
1314

15+
import io.swagger.v3.parser.OpenAPIV3Parser;
1416
import io.swagger.v3.parser.core.models.ParseOptions;
1517
import io.swagger.v3.parser.core.models.SwaggerParseResult;
1618
import io.swagger.v3.core.util.Json;
@@ -31,6 +33,22 @@
3133

3234
public class OpenAPIParserTest {
3335

36+
@Test
37+
public void testIssue_1599() {
38+
OpenAPIParser openAPIParser = new OpenAPIParser();
39+
ParseOptions options = new ParseOptions();
40+
options.setResolve(true);
41+
options.setResolveFully(true);
42+
options.setFlatten(true);
43+
SwaggerParseResult swaggerParseResult = openAPIParser.readLocation("petStore1599.yaml", null, options);
44+
assertNotNull(swaggerParseResult.getOpenAPI());
45+
OpenAPI openAPI = swaggerParseResult.getOpenAPI();
46+
assertTrue(openAPI.getComponents().getSchemas().size() == 5);
47+
assertNull(openAPI.getComponents().getSchemas().get("pet_category"));
48+
assertNull(openAPI.getComponents().getSchemas().get("pet_body"));
49+
assertNull(((Schema)openAPI.getComponents().getSchemas().get("Pet").getProperties().get("category")).get$ref());
50+
}
51+
3452
@Test
3553
public void testNPE_1685() {
3654
OpenAPIParser openAPIParser = new OpenAPIParser();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
swagger: '2.0'
2+
info:
3+
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.'
4+
version: 1.0.0
5+
title: Swagger Petstore
6+
termsOfService: 'http://swagger.io/terms/'
7+
contact:
8+
9+
license:
10+
name: Apache 2.0
11+
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
12+
host: petstore.swagger.io
13+
basePath: /v2
14+
tags:
15+
- name: pet
16+
description: Everything about your Pets
17+
externalDocs:
18+
description: Find out more
19+
url: 'http://swagger.io'
20+
- name: store
21+
description: Access to Petstore orders
22+
- name: user
23+
description: Operations about user
24+
externalDocs:
25+
description: Find out more about our store
26+
url: 'http://swagger.io'
27+
schemes:
28+
- https
29+
- http
30+
paths:
31+
/pet:
32+
post:
33+
tags:
34+
- pet
35+
summary: Add a new pet to the store
36+
description: ''
37+
operationId: addPet
38+
consumes:
39+
- application/json
40+
- application/xml
41+
produces:
42+
- application/xml
43+
- application/json
44+
parameters:
45+
- in: body
46+
name: body
47+
description: Pet object that needs to be added to the store
48+
required: true
49+
schema:
50+
$ref: '#/definitions/Pet'
51+
responses:
52+
'200':
53+
description: Succesful request
54+
'405':
55+
description: Invalid input
56+
schema:
57+
type: object
58+
properties:
59+
code:
60+
type: integer
61+
format: int32
62+
type:
63+
type: string
64+
message:
65+
type: string
66+
security:
67+
- petstore_auth:
68+
- 'write:pets'
69+
- 'read:pets'
70+
put:
71+
tags:
72+
- pet
73+
summary: Update an existing pet
74+
description: ''
75+
operationId: updatePet
76+
consumes:
77+
- application/json
78+
- application/xml
79+
produces:
80+
- application/xml
81+
- application/json
82+
parameters:
83+
- in: body
84+
name: body
85+
description: Pet object that needs to be added to the store
86+
required: true
87+
schema:
88+
$ref: '#/definitions/Pet'
89+
responses:
90+
'200':
91+
description: Succesful request
92+
'400':
93+
description: Invalid ID supplied
94+
'404':
95+
description: Pet not found
96+
'405':
97+
description: Validation exception
98+
security:
99+
- petstore_auth:
100+
- 'write:pets'
101+
- 'read:pets'
102+
- api_key: []
103+
securityDefinitions:
104+
petstore_auth:
105+
type: oauth2
106+
authorizationUrl: 'https://petstore.swagger.io/oauth/authorize'
107+
flow: implicit
108+
scopes:
109+
'write:pets': modify pets in your account
110+
'read:pets': read your pets
111+
api_key:
112+
type: apiKey
113+
name: api_key
114+
in: header
115+
definitions:
116+
Category:
117+
type: object
118+
properties:
119+
id:
120+
type: integer
121+
format: int64
122+
name:
123+
type: string
124+
xml:
125+
name: Category
126+
Tag:
127+
type: object
128+
properties:
129+
id:
130+
type: integer
131+
format: int64
132+
name:
133+
type: string
134+
xml:
135+
name: Tag
136+
Pet:
137+
type: object
138+
required:
139+
- name
140+
- photoUrls
141+
properties:
142+
id:
143+
type: integer
144+
format: int64
145+
category:
146+
$ref: '#/definitions/Category'
147+
name:
148+
type: string
149+
example: doggie
150+
photoUrls:
151+
type: array
152+
xml:
153+
name: photoUrl
154+
wrapped: true
155+
items:
156+
type: string
157+
tags:
158+
type: array
159+
xml:
160+
name: tag
161+
wrapped: true
162+
items:
163+
$ref: '#/definitions/Tag'
164+
status:
165+
type: string
166+
description: pet status in the store
167+
enum:
168+
- available
169+
- pending
170+
- sold
171+
xml:
172+
name: Pet
173+
example:
174+
id: 1000
175+
category:
176+
id: 1000000
177+
name: category1
178+
name: Toby
179+
photoUrls:
180+
- www
181+
- xxx
182+
tags:
183+
- id: 999
184+
name: puppy
185+
- id: 888
186+
name: brown
187+
ApiResponse:
188+
type: object
189+
properties:
190+
code:
191+
type: integer
192+
format: int32
193+
type:
194+
type: string
195+
message:
196+
type: string
197+
externalDocs:
198+
description: Find out more about Swagger
199+
url: 'http://swagger.io'

0 commit comments

Comments
 (0)