Skip to content

Commit

Permalink
Merge branch 'release/1.0.7'
Browse files Browse the repository at this point in the history
  • Loading branch information
gcornacchia committed Jun 12, 2024
2 parents bdf8dd9 + c7f8ed8 commit c8b4564
Show file tree
Hide file tree
Showing 15 changed files with 5,521 additions and 59 deletions.
2 changes: 1 addition & 1 deletion .github/badges/branches.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion .github/badges/jacoco.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
*.idea*
*.iml
target
*.iml
*.log
*.log*.zip
.classpath
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGELOG

1.0.7 - 30/05/2024
- fix bug while handling generation of oneOf construct inside json schema
- fix issue preventing generation of schemas in case of objects without properties, forcing generation with additionalProperties enabled
- fix issue preventing generation of json schemas with swagger files that have objects schemas defined inline rather than inside "components"

1.0.6 - 07/07/2023
- updated swagger-parser library due to a bug that could not read additionalProperties inside a model.

Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ Maven plugin that converts swagger 2.0/OAS 3.0.x schema objects into self contai


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




Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>it.imolinfo.maven.plugins</groupId>
<artifactId>openapi2jsonschema4j</artifactId>
<version>1.0.7-SNAPSHOT</version>
<version>1.0.7</version>
<packaging>maven-plugin</packaging>

<name>OpenAPI2JsonSchema4J</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.parser.core.models.ParseOptions;
Expand Down Expand Up @@ -50,7 +52,9 @@ protected void readFromInterface(File interfaceFile) {
SwaggerParseResult result = new OpenAPIParser().readLocation(interfaceFile.getAbsolutePath(),null,po);
OpenAPI swagger = result.getOpenAPI();
Validate.notNull(swagger,"Error during parsing of interface file "+interfaceFile.getAbsolutePath());
objectsDefinitions = swagger.getComponents().getSchemas();
if (swagger.getComponents() != null && swagger.getComponents().getSchemas() != null) {
objectsDefinitions = swagger.getComponents().getSchemas();
}
for (Map.Entry<String, PathItem> entry : swagger.getPaths().entrySet()) {
String k = entry.getKey();
PathItem v = entry.getValue();
Expand All @@ -66,11 +70,17 @@ private void analyzeOperation(PathItem v) {
ApiResponse r = op.getResponses().get(key);
if (r.getContent()!=null) {
if (r.getContent().get(APPLICATION_JSON) != null) {
Schema sc = r.getContent().get(APPLICATION_JSON).getSchema();
if (r.getContent().get(APPLICATION_JSON).getSchema().get$ref() != null) {
log.info("code={} responseSchema={}", key, r.getContent().get(APPLICATION_JSON).getSchema().get$ref());
messageObjects.add(r.getContent().get(APPLICATION_JSON).getSchema().get$ref());
} else {
log.warn("code={} response schema is not a referenced definition! type={}", key, r.getContent().get("application/json").getClass());
log.debug("Reference not found, creating it manually");
if (!(sc instanceof ArraySchema)) {
objectsDefinitions.put(op.getOperationId()+"response"+key, sc);
messageObjects.add(op.getOperationId()+"response"+key);
}
}
}
}
Expand All @@ -85,9 +95,14 @@ private void findRequestBodySchema(Operation op, Set<String> messageObjects) {
if (sc != null) {
log.info("Request schema={}", sc.get$ref());
if (sc.get$ref()!=null) {
messageObjects.add(sc.get$ref());
messageObjects.add(sc.get$ref());
} else {
log.warn("Request schema is not a referenced definition!");
log.debug("Ref not found, cresting it manually if object");
if (!(sc instanceof ArraySchema)) {
objectsDefinitions.put(op.getOperationId()+"request", sc);
messageObjects.add(op.getOperationId()+"request");
}
}
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.datatype.jsr310.*;

import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.*;

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

private static final String ALLOF = "anyOf";
private static final String ONEOF = "oneOf";
private static final String ANYOF = "anyOf";


private static final String JSONSCHEMA = "jsonSchema";

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


public DraftV4JsonSchemaGenerator(boolean strict) {
this.strict = strict;
}

private Map<String, JsonNode> generateForObjects() throws Exception {
for (String ref : getMessageObjects()) {
String title = ref.replace(DEFINITIONS2, "");
Map<String, Object> defs = (Map<String, Object>) ((HashMap<String, Schema>) getObjectsDefinitions()).clone();
Map<String, Object> defs = (Map<String, Object>) ((HashMap<String, Schema>) getObjectsDefinitions()).clone();
Schema<Object> ob = (Schema<Object>) defs.get(title);
defs.remove(title);
Map<String, Object> res = new HashMap<String, Object>();
Map<String,Object> schemas = new HashMap<>();
schemas.put(SCHEMAS,defs);
res.put(COMPONENTS, schemas);
res.put(TITLE2, title);
res.put(TITLE2, title);
log.info("Generating json schema for object '{}' of type {}", title,ob.getClass());
if (ob instanceof ObjectSchema) {
res.put(TYPE, ((ObjectSchema) ob).getType());
res.put(PROPERTIES, ob.getProperties());
res.put(REQUIRED,ob.getRequired());
if (((ObjectSchema) ob).getAdditionalProperties()!=null) {
log.info("additionalProperties already exists... {}",((ObjectSchema) ob).getAdditionalProperties());
res.put(ADDITIONAL_PROPERTIES,((ObjectSchema) ob).getAdditionalProperties());
res.put(REQUIRED, ob.getRequired());

if (ob.getProperties() == null || ob.getProperties().isEmpty()) {
res.put(PROPERTIES, new HashMap<String, Schema>());
log.info("Object '{}' has no properties, creating empty properties object.");
res.put(ADDITIONAL_PROPERTIES, true);
log.warn("Forced additionalProperties=true for object {}",title);
} else {
res.put(ADDITIONAL_PROPERTIES, !this.strict);
if (((ObjectSchema) ob).getAdditionalProperties()!=null) {
log.info("additionalProperties already exists... {}",((ObjectSchema) ob).getAdditionalProperties());
res.put(ADDITIONAL_PROPERTIES,((ObjectSchema) ob).getAdditionalProperties());
} else {
res.put(ADDITIONAL_PROPERTIES, !this.strict);
}
}
}
if (ob instanceof ComposedSchema) {
res.put(TYPE, ((ComposedSchema) ob).getType());
res.put(ALLOF, ob.getAllOf());
res.put(ONEOF, ob.getOneOf());
res.put(ANYOF, ob.getAnyOf());
}
if (ob instanceof ArraySchema) {
Schema<?> items = ob.getItems();
if (items instanceof ObjectSchema && items.getProperties() == null) {
log.info("Array items of type object has no properties");
}
res.put(ITEMS, ((ArraySchema) ob).getItems());
res.put(TYPE, ((ArraySchema) ob).getType());
res.put(MIN_ITEMS, ((ArraySchema) ob).getMinItems());
Expand All @@ -96,15 +121,14 @@ private Map<String, JsonNode> generateForObjects() throws Exception {
if (ob instanceof MapSchema) {
res.put(PROPERTIES, ((MapSchema) ob).getProperties());
res.put(TYPE, ((MapSchema) ob).getType());
res.put(REQUIRED,ob.getRequired());
res.put(ADDITIONAL_PROPERTIES,((MapSchema) ob).getAdditionalProperties());
res.put(REQUIRED, ob.getRequired());
res.put(ADDITIONAL_PROPERTIES, ((MapSchema) ob).getAdditionalProperties());
}
res.put($SCHEMA, HTTP_JSON_SCHEMA_ORG_DRAFT_04_SCHEMA);
removeUnusedObject(res,ob);
getGeneratedObjects().put(title, postprocess(res));
}
return getGeneratedObjects();

}

private void removeUnusedObject(Map<String, Object> res, Schema<Object> ob) {
Expand Down Expand Up @@ -173,8 +197,8 @@ private void navigateModel(String originalRef, List<String> usedDefinition, Map<
if (ms.getAdditionalProperties() instanceof Schema) {
navigateSchema("", (Schema)ms.getAdditionalProperties(), usedDefinition, res);
}
} else if (ob instanceof Schema && ((Schema)ob).get$ref()!=null){

} else if (ob instanceof Schema && ((Schema)ob).get$ref()!=null){
navigateModel(((Schema)ob).get$ref(),usedDefinition,res,null);
}
}
Expand All @@ -194,11 +218,16 @@ private void navigateSchema(String propertyName, Schema p, List<String> usedDefi
} else if (p instanceof ArraySchema) {
ArraySchema ap = (ArraySchema) p;
log.debug("Array property={} items={}",ap,ap.getItems());
if(ap.getItems() instanceof ObjectSchema && ap.getItems().getProperties() == null ){
log.info("Array items of type object has no properties");
}
navigateSchema("items",ap.getItems(),usedDefinition,res);
} else if (p instanceof ObjectSchema){
ObjectSchema op = (ObjectSchema) p;
for (String name : op.getProperties().keySet()){
navigateSchema(name,op.getProperties().get(name),usedDefinition,res);
if(op.getProperties() != null) {
for (String name : op.getProperties().keySet()) {
navigateSchema(name, op.getProperties().get(name), usedDefinition, res);
}
}
} else if (p instanceof MapSchema) {
MapSchema mp = (MapSchema)p;
Expand All @@ -216,7 +245,7 @@ public class DynamicMixIn {
}

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

private JsonNode removeNonJsonSchemaProperties(Map<String, Object> res) throws JsonProcessingException {

iterateMap(res,null);
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
Expand All @@ -238,14 +267,14 @@ private JsonNode removeNonJsonSchemaProperties(Map<String, Object> res) throws J
JsonNode jsonNode = mapper2.readValue(json, JsonNode.class);
return jsonNode;
}
//rimuove tutte le properties di oas3 non gestite in json schema

//this method remove all unmanaged oas3 json schema props
private void iterateMap(Map<String, Object> res, String father) {
if (res==null)
return;
for (String k : res.keySet()) {
if (res.get(k)!=null) {
log.debug("key={}",k);
log.debug("key={} father={}",k,father);
if (!"properties".equals(father)) {
//devo rimuovere i valori da ignorare (solo se il padre non è un campo 'properties'
if (ignorePropertiesList.contains(k)) {
Expand All @@ -259,11 +288,11 @@ private void iterateMap(Map<String, Object> res, String father) {
}
}
}

}




private Map<String, Object> handleNullableFields(Map<String, Object> result) {
ObjectMapper mapper = new ObjectMapper();
Expand All @@ -286,33 +315,34 @@ private void process(String prefix, JsonNode currentNode) {
currentNode.fields().forEachRemaining(entry -> process(
!prefix.isEmpty() ? prefix + "-" + entry.getKey() : entry.getKey(), entry.getValue()));
ObjectNode on = ((ObjectNode) currentNode);
if (currentNode.get(TYPE) != null) {
String type = currentNode.get(TYPE).asText();
if ("object".equals(type)) {
if (on.get(ADDITIONAL_PROPERTIES)!=null) {
log.debug("already defined additionalProperties with value {}",on.get(ADDITIONAL_PROPERTIES).asText());
} else {
if (currentNode.get(PROPERTIES)!=null && currentNode.get(PROPERTIES).isEmpty()) {
//devo settare additionalProperties a true come di default se l'oggetto non specifica nessuna property
on.set(ADDITIONAL_PROPERTIES, BooleanNode.valueOf(true));
log.debug("setting additional properties with value {} as this object has empty properties", true);
} else {
on.set(ADDITIONAL_PROPERTIES, BooleanNode.valueOf(!this.strict));
log.debug("setting additional properties with value {}", !this.strict);
}
}
if (currentNode.has(TYPE) && "object".equals(currentNode.get(TYPE).asText())) {
if (on.get(ADDITIONAL_PROPERTIES) != null) {
log.debug("already defined additionalProperties with value {}",on.get(ADDITIONAL_PROPERTIES).asText());
}
if (!currentNode.has(PROPERTIES) || !currentNode.get(PROPERTIES).fields().hasNext()) {
on.put(ADDITIONAL_PROPERTIES, true);
log.debug("setting additional properties with value {}", true);
}else{
on.set(ADDITIONAL_PROPERTIES, BooleanNode.valueOf(!this.strict));
log.debug("setting additional properties with value {}", !this.strict);
}
}
if (currentNode.get(ORIGINAL_REF) != null) {
if (currentNode.has(ORIGINAL_REF)) {
on.remove(ORIGINAL_REF);
log.debug("removing originalRef field");
}
if (currentNode.get(EXAMPLESETFLAG) != null) {
on.remove(EXAMPLESETFLAG);
log.debug("removing exampleSetFlag field");
}
} else {
log.debug(prefix + ": " + currentNode.toString());
}
}




private boolean isValidJsonSchemaSyntax(JsonNode jsonSchemaFile) {
SyntaxValidator synValidator = JsonSchemaFactory.byDefault().getSyntaxValidator();
ProcessingReport report = synValidator.validateSchema(jsonSchemaFile);
Expand Down
Loading

0 comments on commit c8b4564

Please sign in to comment.