Skip to content

Commit c8b4564

Browse files
author
gcornacchia
committed
Merge branch 'release/1.0.7'
2 parents bdf8dd9 + c7f8ed8 commit c8b4564

15 files changed

Lines changed: 5521 additions & 59 deletions

File tree

.github/badges/branches.svg

Lines changed: 1 addition & 1 deletion
Loading

.github/badges/jacoco.svg

Lines changed: 1 addition & 1 deletion
Loading

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
*.idea*
2+
*.iml
23
target
4+
*.iml
35
*.log
46
*.log*.zip
57
.classpath

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# CHANGELOG
22

3+
1.0.7 - 30/05/2024
4+
- fix bug while handling generation of oneOf construct inside json schema
5+
- fix issue preventing generation of schemas in case of objects without properties, forcing generation with additionalProperties enabled
6+
- fix issue preventing generation of json schemas with swagger files that have objects schemas defined inline rather than inside "components"
7+
38
1.0.6 - 07/07/2023
49
- updated swagger-parser library due to a bug that could not read additionalProperties inside a model.
510

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ Maven plugin that converts swagger 2.0/OAS 3.0.x schema objects into self contai
1212

1313

1414
- Each operation MUST have a non-empty OperationID field
15-
- Each operation MUST not declare inline request/response body but always reference to Object Schema (no error is thrown, only a warn log)
16-
1715

1816

1917

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44
<groupId>it.imolinfo.maven.plugins</groupId>
55
<artifactId>openapi2jsonschema4j</artifactId>
6-
<version>1.0.7-SNAPSHOT</version>
6+
<version>1.0.7</version>
77
<packaging>maven-plugin</packaging>
88

99
<name>OpenAPI2JsonSchema4J</name>

src/main/java/it/imolainformatica/openapi2jsonschema4j/base/BaseJsonSchemaGenerator.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import io.swagger.v3.oas.models.OpenAPI;
1313
import io.swagger.v3.oas.models.Operation;
1414
import io.swagger.v3.oas.models.PathItem;
15+
import io.swagger.v3.oas.models.media.ObjectSchema;
16+
import io.swagger.v3.oas.models.media.ArraySchema;
1517
import io.swagger.v3.oas.models.media.Schema;
1618
import io.swagger.v3.oas.models.responses.ApiResponse;
1719
import io.swagger.v3.parser.core.models.ParseOptions;
@@ -50,7 +52,9 @@ protected void readFromInterface(File interfaceFile) {
5052
SwaggerParseResult result = new OpenAPIParser().readLocation(interfaceFile.getAbsolutePath(),null,po);
5153
OpenAPI swagger = result.getOpenAPI();
5254
Validate.notNull(swagger,"Error during parsing of interface file "+interfaceFile.getAbsolutePath());
53-
objectsDefinitions = swagger.getComponents().getSchemas();
55+
if (swagger.getComponents() != null && swagger.getComponents().getSchemas() != null) {
56+
objectsDefinitions = swagger.getComponents().getSchemas();
57+
}
5458
for (Map.Entry<String, PathItem> entry : swagger.getPaths().entrySet()) {
5559
String k = entry.getKey();
5660
PathItem v = entry.getValue();
@@ -66,11 +70,17 @@ private void analyzeOperation(PathItem v) {
6670
ApiResponse r = op.getResponses().get(key);
6771
if (r.getContent()!=null) {
6872
if (r.getContent().get(APPLICATION_JSON) != null) {
73+
Schema sc = r.getContent().get(APPLICATION_JSON).getSchema();
6974
if (r.getContent().get(APPLICATION_JSON).getSchema().get$ref() != null) {
7075
log.info("code={} responseSchema={}", key, r.getContent().get(APPLICATION_JSON).getSchema().get$ref());
7176
messageObjects.add(r.getContent().get(APPLICATION_JSON).getSchema().get$ref());
7277
} else {
7378
log.warn("code={} response schema is not a referenced definition! type={}", key, r.getContent().get("application/json").getClass());
79+
log.debug("Reference not found, creating it manually");
80+
if (!(sc instanceof ArraySchema)) {
81+
objectsDefinitions.put(op.getOperationId()+"response"+key, sc);
82+
messageObjects.add(op.getOperationId()+"response"+key);
83+
}
7484
}
7585
}
7686
}
@@ -85,9 +95,14 @@ private void findRequestBodySchema(Operation op, Set<String> messageObjects) {
8595
if (sc != null) {
8696
log.info("Request schema={}", sc.get$ref());
8797
if (sc.get$ref()!=null) {
88-
messageObjects.add(sc.get$ref());
98+
messageObjects.add(sc.get$ref());
8999
} else {
90100
log.warn("Request schema is not a referenced definition!");
101+
log.debug("Ref not found, cresting it manually if object");
102+
if (!(sc instanceof ArraySchema)) {
103+
objectsDefinitions.put(op.getOperationId()+"request", sc);
104+
messageObjects.add(op.getOperationId()+"request");
105+
}
91106
}
92107
}
93108
} else {

src/main/java/it/imolainformatica/openapi2jsonschema4j/impl/DraftV4JsonSchemaGenerator.java

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import com.fasterxml.jackson.core.JsonProcessingException;
88
import com.fasterxml.jackson.core.type.TypeReference;
99
import com.fasterxml.jackson.datatype.jsr310.*;
10+
11+
import io.swagger.v3.oas.models.Operation;
1012
import io.swagger.v3.oas.models.media.*;
1113

1214
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -39,6 +41,11 @@ public class DraftV4JsonSchemaGenerator extends BaseJsonSchemaGenerator implemen
3941
private static final String EXTERNALDOCS = "externalDocs";
4042
private static final String DEPRECATED = "deprecated";
4143

44+
private static final String ALLOF = "anyOf";
45+
private static final String ONEOF = "oneOf";
46+
private static final String ANYOF = "anyOf";
47+
48+
4249
private static final String JSONSCHEMA = "jsonSchema";
4350

4451
private static final String TYPES = "types";
@@ -59,35 +66,53 @@ public class DraftV4JsonSchemaGenerator extends BaseJsonSchemaGenerator implemen
5966
public static final String NULL = "null";
6067
private boolean strict;
6168

62-
69+
6370
public DraftV4JsonSchemaGenerator(boolean strict) {
6471
this.strict = strict;
6572
}
6673

6774
private Map<String, JsonNode> generateForObjects() throws Exception {
6875
for (String ref : getMessageObjects()) {
6976
String title = ref.replace(DEFINITIONS2, "");
70-
Map<String, Object> defs = (Map<String, Object>) ((HashMap<String, Schema>) getObjectsDefinitions()).clone();
77+
Map<String, Object> defs = (Map<String, Object>) ((HashMap<String, Schema>) getObjectsDefinitions()).clone();
7178
Schema<Object> ob = (Schema<Object>) defs.get(title);
7279
defs.remove(title);
7380
Map<String, Object> res = new HashMap<String, Object>();
7481
Map<String,Object> schemas = new HashMap<>();
7582
schemas.put(SCHEMAS,defs);
7683
res.put(COMPONENTS, schemas);
77-
res.put(TITLE2, title);
84+
res.put(TITLE2, title);
7885
log.info("Generating json schema for object '{}' of type {}", title,ob.getClass());
7986
if (ob instanceof ObjectSchema) {
8087
res.put(TYPE, ((ObjectSchema) ob).getType());
8188
res.put(PROPERTIES, ob.getProperties());
82-
res.put(REQUIRED,ob.getRequired());
83-
if (((ObjectSchema) ob).getAdditionalProperties()!=null) {
84-
log.info("additionalProperties already exists... {}",((ObjectSchema) ob).getAdditionalProperties());
85-
res.put(ADDITIONAL_PROPERTIES,((ObjectSchema) ob).getAdditionalProperties());
89+
res.put(REQUIRED, ob.getRequired());
90+
91+
if (ob.getProperties() == null || ob.getProperties().isEmpty()) {
92+
res.put(PROPERTIES, new HashMap<String, Schema>());
93+
log.info("Object '{}' has no properties, creating empty properties object.");
94+
res.put(ADDITIONAL_PROPERTIES, true);
95+
log.warn("Forced additionalProperties=true for object {}",title);
8696
} else {
87-
res.put(ADDITIONAL_PROPERTIES, !this.strict);
97+
if (((ObjectSchema) ob).getAdditionalProperties()!=null) {
98+
log.info("additionalProperties already exists... {}",((ObjectSchema) ob).getAdditionalProperties());
99+
res.put(ADDITIONAL_PROPERTIES,((ObjectSchema) ob).getAdditionalProperties());
100+
} else {
101+
res.put(ADDITIONAL_PROPERTIES, !this.strict);
102+
}
88103
}
89104
}
105+
if (ob instanceof ComposedSchema) {
106+
res.put(TYPE, ((ComposedSchema) ob).getType());
107+
res.put(ALLOF, ob.getAllOf());
108+
res.put(ONEOF, ob.getOneOf());
109+
res.put(ANYOF, ob.getAnyOf());
110+
}
90111
if (ob instanceof ArraySchema) {
112+
Schema<?> items = ob.getItems();
113+
if (items instanceof ObjectSchema && items.getProperties() == null) {
114+
log.info("Array items of type object has no properties");
115+
}
91116
res.put(ITEMS, ((ArraySchema) ob).getItems());
92117
res.put(TYPE, ((ArraySchema) ob).getType());
93118
res.put(MIN_ITEMS, ((ArraySchema) ob).getMinItems());
@@ -96,15 +121,14 @@ private Map<String, JsonNode> generateForObjects() throws Exception {
96121
if (ob instanceof MapSchema) {
97122
res.put(PROPERTIES, ((MapSchema) ob).getProperties());
98123
res.put(TYPE, ((MapSchema) ob).getType());
99-
res.put(REQUIRED,ob.getRequired());
100-
res.put(ADDITIONAL_PROPERTIES,((MapSchema) ob).getAdditionalProperties());
124+
res.put(REQUIRED, ob.getRequired());
125+
res.put(ADDITIONAL_PROPERTIES, ((MapSchema) ob).getAdditionalProperties());
101126
}
102127
res.put($SCHEMA, HTTP_JSON_SCHEMA_ORG_DRAFT_04_SCHEMA);
103128
removeUnusedObject(res,ob);
104129
getGeneratedObjects().put(title, postprocess(res));
105130
}
106131
return getGeneratedObjects();
107-
108132
}
109133

110134
private void removeUnusedObject(Map<String, Object> res, Schema<Object> ob) {
@@ -173,8 +197,8 @@ private void navigateModel(String originalRef, List<String> usedDefinition, Map<
173197
if (ms.getAdditionalProperties() instanceof Schema) {
174198
navigateSchema("", (Schema)ms.getAdditionalProperties(), usedDefinition, res);
175199
}
176-
177-
} else if (ob instanceof Schema && ((Schema)ob).get$ref()!=null){
200+
201+
} else if (ob instanceof Schema && ((Schema)ob).get$ref()!=null){
178202
navigateModel(((Schema)ob).get$ref(),usedDefinition,res,null);
179203
}
180204
}
@@ -194,11 +218,16 @@ private void navigateSchema(String propertyName, Schema p, List<String> usedDefi
194218
} else if (p instanceof ArraySchema) {
195219
ArraySchema ap = (ArraySchema) p;
196220
log.debug("Array property={} items={}",ap,ap.getItems());
221+
if(ap.getItems() instanceof ObjectSchema && ap.getItems().getProperties() == null ){
222+
log.info("Array items of type object has no properties");
223+
}
197224
navigateSchema("items",ap.getItems(),usedDefinition,res);
198225
} else if (p instanceof ObjectSchema){
199226
ObjectSchema op = (ObjectSchema) p;
200-
for (String name : op.getProperties().keySet()){
201-
navigateSchema(name,op.getProperties().get(name),usedDefinition,res);
227+
if(op.getProperties() != null) {
228+
for (String name : op.getProperties().keySet()) {
229+
navigateSchema(name, op.getProperties().get(name), usedDefinition, res);
230+
}
202231
}
203232
} else if (p instanceof MapSchema) {
204233
MapSchema mp = (MapSchema)p;
@@ -216,7 +245,7 @@ public class DynamicMixIn {
216245
}
217246

218247
private JsonNode postprocess(Map<String, Object> res) throws Exception {
219-
//devo gestire i valori nullable potenzialmente presenti su oas 3.0
248+
//need to handle all nullable oas3 possible values
220249
res = handleNullableFields(res);
221250
JsonNode jsonNode = removeNonJsonSchemaProperties(res);
222251
process("", jsonNode);
@@ -229,7 +258,7 @@ private JsonNode postprocess(Map<String, Object> res) throws Exception {
229258
}
230259

231260
private JsonNode removeNonJsonSchemaProperties(Map<String, Object> res) throws JsonProcessingException {
232-
261+
233262
iterateMap(res,null);
234263
ObjectMapper mapper = new ObjectMapper();
235264
mapper.setSerializationInclusion(Include.NON_NULL);
@@ -238,14 +267,14 @@ private JsonNode removeNonJsonSchemaProperties(Map<String, Object> res) throws J
238267
JsonNode jsonNode = mapper2.readValue(json, JsonNode.class);
239268
return jsonNode;
240269
}
241-
242-
//rimuove tutte le properties di oas3 non gestite in json schema
270+
271+
//this method remove all unmanaged oas3 json schema props
243272
private void iterateMap(Map<String, Object> res, String father) {
244273
if (res==null)
245274
return;
246275
for (String k : res.keySet()) {
247276
if (res.get(k)!=null) {
248-
log.debug("key={}",k);
277+
log.debug("key={} father={}",k,father);
249278
if (!"properties".equals(father)) {
250279
//devo rimuovere i valori da ignorare (solo se il padre non è un campo 'properties'
251280
if (ignorePropertiesList.contains(k)) {
@@ -259,11 +288,11 @@ private void iterateMap(Map<String, Object> res, String father) {
259288
}
260289
}
261290
}
262-
291+
263292
}
264-
265-
266-
293+
294+
295+
267296

268297
private Map<String, Object> handleNullableFields(Map<String, Object> result) {
269298
ObjectMapper mapper = new ObjectMapper();
@@ -286,33 +315,34 @@ private void process(String prefix, JsonNode currentNode) {
286315
currentNode.fields().forEachRemaining(entry -> process(
287316
!prefix.isEmpty() ? prefix + "-" + entry.getKey() : entry.getKey(), entry.getValue()));
288317
ObjectNode on = ((ObjectNode) currentNode);
289-
if (currentNode.get(TYPE) != null) {
290-
String type = currentNode.get(TYPE).asText();
291-
if ("object".equals(type)) {
292-
if (on.get(ADDITIONAL_PROPERTIES)!=null) {
293-
log.debug("already defined additionalProperties with value {}",on.get(ADDITIONAL_PROPERTIES).asText());
294-
} else {
295-
if (currentNode.get(PROPERTIES)!=null && currentNode.get(PROPERTIES).isEmpty()) {
296-
//devo settare additionalProperties a true come di default se l'oggetto non specifica nessuna property
297-
on.set(ADDITIONAL_PROPERTIES, BooleanNode.valueOf(true));
298-
log.debug("setting additional properties with value {} as this object has empty properties", true);
299-
} else {
300-
on.set(ADDITIONAL_PROPERTIES, BooleanNode.valueOf(!this.strict));
301-
log.debug("setting additional properties with value {}", !this.strict);
302-
}
303-
}
318+
if (currentNode.has(TYPE) && "object".equals(currentNode.get(TYPE).asText())) {
319+
if (on.get(ADDITIONAL_PROPERTIES) != null) {
320+
log.debug("already defined additionalProperties with value {}",on.get(ADDITIONAL_PROPERTIES).asText());
321+
}
322+
if (!currentNode.has(PROPERTIES) || !currentNode.get(PROPERTIES).fields().hasNext()) {
323+
on.put(ADDITIONAL_PROPERTIES, true);
324+
log.debug("setting additional properties with value {}", true);
325+
}else{
326+
on.set(ADDITIONAL_PROPERTIES, BooleanNode.valueOf(!this.strict));
327+
log.debug("setting additional properties with value {}", !this.strict);
304328
}
305329
}
306-
if (currentNode.get(ORIGINAL_REF) != null) {
330+
if (currentNode.has(ORIGINAL_REF)) {
307331
on.remove(ORIGINAL_REF);
308332
log.debug("removing originalRef field");
309333
}
334+
if (currentNode.get(EXAMPLESETFLAG) != null) {
335+
on.remove(EXAMPLESETFLAG);
336+
log.debug("removing exampleSetFlag field");
337+
}
310338
} else {
311339
log.debug(prefix + ": " + currentNode.toString());
312340
}
313341
}
314342

315343

344+
345+
316346
private boolean isValidJsonSchemaSyntax(JsonNode jsonSchemaFile) {
317347
SyntaxValidator synValidator = JsonSchemaFactory.byDefault().getSyntaxValidator();
318348
ProcessingReport report = synValidator.validateSchema(jsonSchemaFile);

0 commit comments

Comments
 (0)