From d920241eced5d68a617e40df8db96cc611a390cb Mon Sep 17 00:00:00 2001 From: frantuma Date: Mon, 14 Nov 2022 13:58:08 +0100 Subject: [PATCH] enhanced support for additionalProperties --- .../io/swagger/jackson/ModelSerializer.java | 38 +++++++++ .../swagger/jackson/PropertySerializer.java | 38 +++++++++ .../io/swagger/util/ModelDeserializer.java | 4 + .../io/swagger/util/ObjectMapperFactory.java | 28 +++++++ .../io/swagger/util/PropertyDeserializer.java | 17 ++++ .../java/io/swagger/BooleanModelTest.java | 84 +++++++++++++++++++ .../java/io/swagger/models/AbstractModel.java | 26 +++++- .../io/swagger/models/BooleanValueModel.java | 28 +++++++ .../main/java/io/swagger/models/Model.java | 6 ++ .../main/java/io/swagger/models/RefModel.java | 13 ++- .../models/properties/AbstractProperty.java | 16 ++++ .../properties/BooleanValueProperty.java | 8 ++ .../swagger/models/properties/Property.java | 6 ++ .../models/properties/PropertyBuilder.java | 3 +- 14 files changed, 309 insertions(+), 6 deletions(-) create mode 100644 modules/swagger-core/src/main/java/io/swagger/jackson/ModelSerializer.java create mode 100644 modules/swagger-core/src/main/java/io/swagger/jackson/PropertySerializer.java create mode 100644 modules/swagger-core/src/test/java/io/swagger/BooleanModelTest.java create mode 100644 modules/swagger-models/src/main/java/io/swagger/models/BooleanValueModel.java create mode 100644 modules/swagger-models/src/main/java/io/swagger/models/properties/BooleanValueProperty.java diff --git a/modules/swagger-core/src/main/java/io/swagger/jackson/ModelSerializer.java b/modules/swagger-core/src/main/java/io/swagger/jackson/ModelSerializer.java new file mode 100644 index 0000000000..4c51ef8d10 --- /dev/null +++ b/modules/swagger-core/src/main/java/io/swagger/jackson/ModelSerializer.java @@ -0,0 +1,38 @@ +package io.swagger.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ResolvableSerializer; +import io.swagger.models.Model; + +import java.io.IOException; + +public class ModelSerializer extends JsonSerializer implements ResolvableSerializer { + + private JsonSerializer defaultSerializer; + + public ModelSerializer(JsonSerializer serializer) { + defaultSerializer = serializer; + } + + @Override + public void resolve(SerializerProvider serializerProvider) throws JsonMappingException { + if (defaultSerializer instanceof ResolvableSerializer) { + ((ResolvableSerializer) defaultSerializer).resolve(serializerProvider); + } + } + + @Override + public void serialize( + Model value, JsonGenerator jgen, SerializerProvider provider) + throws IOException { + + if (value.getBooleanValue() != null) { + jgen.writeBoolean(value.getBooleanValue()); + } else { + defaultSerializer.serialize(value, jgen, provider); + } + } +} diff --git a/modules/swagger-core/src/main/java/io/swagger/jackson/PropertySerializer.java b/modules/swagger-core/src/main/java/io/swagger/jackson/PropertySerializer.java new file mode 100644 index 0000000000..0a498959bc --- /dev/null +++ b/modules/swagger-core/src/main/java/io/swagger/jackson/PropertySerializer.java @@ -0,0 +1,38 @@ +package io.swagger.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ResolvableSerializer; +import io.swagger.models.properties.Property; + +import java.io.IOException; + +public class PropertySerializer extends JsonSerializer implements ResolvableSerializer { + + private JsonSerializer defaultSerializer; + + public PropertySerializer(JsonSerializer serializer) { + defaultSerializer = serializer; + } + + @Override + public void resolve(SerializerProvider serializerProvider) throws JsonMappingException { + if (defaultSerializer instanceof ResolvableSerializer) { + ((ResolvableSerializer) defaultSerializer).resolve(serializerProvider); + } + } + + @Override + public void serialize( + Property value, JsonGenerator jgen, SerializerProvider provider) + throws IOException { + + if (value.getBooleanValue() != null) { + jgen.writeBoolean(value.getBooleanValue()); + } else { + defaultSerializer.serialize(value, jgen, provider); + } + } +} diff --git a/modules/swagger-core/src/main/java/io/swagger/util/ModelDeserializer.java b/modules/swagger-core/src/main/java/io/swagger/util/ModelDeserializer.java index ab6075e1d8..cbf1f05b86 100644 --- a/modules/swagger-core/src/main/java/io/swagger/util/ModelDeserializer.java +++ b/modules/swagger-core/src/main/java/io/swagger/util/ModelDeserializer.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.TextNode; import io.swagger.models.ArrayModel; +import io.swagger.models.BooleanValueModel; import io.swagger.models.ComposedModel; import io.swagger.models.Model; import io.swagger.models.ModelImpl; @@ -21,6 +22,9 @@ public class ModelDeserializer extends JsonDeserializer { public Model deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonNode node = jp.getCodec().readTree(jp); + if (node.isBoolean()) { + return new BooleanValueModel(node.asBoolean()); + } JsonNode sub = node.get("$ref"); JsonNode allOf = node.get("allOf"); diff --git a/modules/swagger-core/src/main/java/io/swagger/util/ObjectMapperFactory.java b/modules/swagger-core/src/main/java/io/swagger/util/ObjectMapperFactory.java index 5aa84ec4e8..6e7016a999 100644 --- a/modules/swagger-core/src/main/java/io/swagger/util/ObjectMapperFactory.java +++ b/modules/swagger-core/src/main/java/io/swagger/util/ObjectMapperFactory.java @@ -2,15 +2,24 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.swagger.jackson.ModelSerializer; +import io.swagger.jackson.PropertySerializer; import io.swagger.jackson.mixin.OperationResponseMixin; import io.swagger.jackson.mixin.ResponseSchemaMixin; +import io.swagger.models.Model; import io.swagger.models.Operation; import io.swagger.models.Response; +import io.swagger.models.properties.Property; public class ObjectMapperFactory { @@ -33,6 +42,25 @@ protected static ObjectMapper createYaml(boolean includePathDeserializer, boolea private static ObjectMapper create(JsonFactory jsonFactory, boolean includePathDeserializer, boolean includeResponseDeserializer) { ObjectMapper mapper = jsonFactory == null ? new ObjectMapper() : new ObjectMapper(jsonFactory); + mapper.registerModule(new SimpleModule() { + @Override + public void setupModule(SetupContext context) { + super.setupModule(context); + context.addBeanSerializerModifier(new BeanSerializerModifier() { + @Override + public JsonSerializer modifySerializer( + SerializationConfig config, BeanDescription desc, JsonSerializer serializer) { + if (Property.class.isAssignableFrom(desc.getBeanClass())) { + return new PropertySerializer((JsonSerializer) serializer); + } else if (Model.class.isAssignableFrom(desc.getBeanClass())) { + return new ModelSerializer((JsonSerializer) serializer); + } + return serializer; + } + }); + } + }); + Module deserializerModule = new DeserializationModule(includePathDeserializer, includeResponseDeserializer); mapper.registerModule(deserializerModule); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); diff --git a/modules/swagger-core/src/main/java/io/swagger/util/PropertyDeserializer.java b/modules/swagger-core/src/main/java/io/swagger/util/PropertyDeserializer.java index 986acc891d..97901f0b35 100644 --- a/modules/swagger-core/src/main/java/io/swagger/util/PropertyDeserializer.java +++ b/modules/swagger-core/src/main/java/io/swagger/util/PropertyDeserializer.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.databind.node.TextNode; import io.swagger.models.Xml; import io.swagger.models.properties.ArrayProperty; +import io.swagger.models.properties.BooleanValueProperty; import io.swagger.models.properties.ComposedProperty; import io.swagger.models.properties.MapProperty; import io.swagger.models.properties.ObjectProperty; @@ -201,6 +202,10 @@ private Map argsFromNode(JsonNode node) { } Property propertyFromNode(JsonNode node) { + + if (node.isBoolean()) { + return new BooleanValueProperty(node.asBoolean()); + } final String type = getString(node, PropertyBuilder.PropertyId.TYPE); final String title = getString(node, PropertyBuilder.PropertyId.TITLE); final String format = getString(node, PropertyBuilder.PropertyId.FORMAT); @@ -237,6 +242,18 @@ Property propertyFromNode(JsonNode node) { mapProperty.setReadOnly(readOnly); return mapProperty; } + } else if (detailNode != null && detailNode.getNodeType().equals(JsonNodeType.BOOLEAN)) { + Property items = new BooleanValueProperty(detailNode.asBoolean()); + MapProperty mapProperty = new MapProperty(items) + .description(description) + .title(title) + .xml(xml); + mapProperty.setExample(example); + mapProperty.setMinProperties(getInteger(node, PropertyBuilder.PropertyId.MIN_PROPERTIES)); + mapProperty.setMaxProperties(getInteger(node, PropertyBuilder.PropertyId.MAX_PROPERTIES)); + mapProperty.setVendorExtensionMap(getVendorExtensions(node)); + mapProperty.setReadOnly(readOnly); + return mapProperty; } else { JsonNode allOfNode = node.get("allOf"); detailNode = node.get("properties"); diff --git a/modules/swagger-core/src/test/java/io/swagger/BooleanModelTest.java b/modules/swagger-core/src/test/java/io/swagger/BooleanModelTest.java new file mode 100644 index 0000000000..6e63ba0569 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/BooleanModelTest.java @@ -0,0 +1,84 @@ +package io.swagger; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.swagger.matchers.SerializationMatchers; +import io.swagger.models.ModelImpl; +import io.swagger.models.Swagger; +import io.swagger.util.Yaml; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +public class BooleanModelTest { + @Test + public void testBooleanProperty() throws JsonProcessingException { + String yaml = "swagger: '2.0'\n" + + "info:\n" + + " title: Some API\n" + + " description: >-\n" + + " This is the Some Api\n" + + " version: 2.0.0\n" + + "basePath: /somepath\n" + + "schemes:\n" + + " - https\n" + + "consumes:\n" + + " - application/json\n" + + "produces:\n" + + " - application/json\n" + + "paths:\n" + + " /somepath:\n" + + " get:\n" + + " description: >\n" + + " my description\n" + + " operationId: MyGet\n" + + " responses:\n" + + " '200':\n" + + " $ref: '#/responses/Response'\n" + + "responses:\n" + + " Response:\n" + + " description: Response\n" + + " schema:\n" + + " type: object\n" + + " required:\n" + + " - Report\n" + + " properties:\n" + + " Report:\n" + + " type: string\n" + + " additionalProperties: false"; + + Swagger swagger = Yaml.mapper().readValue(yaml, Swagger.class); + assertEquals(((ModelImpl)swagger.getResponses().get("Response").getResponseSchema()).getAdditionalProperties().getBooleanValue().booleanValue(), false); + SerializationMatchers.assertEqualsToYaml(swagger, "swagger: \"2.0\"\n" + + "info:\n" + + " description: \"This is the Some Api\"\n" + + " version: \"2.0.0\"\n" + + " title: \"Some API\"\n" + + "basePath: \"/somepath\"\n" + + "schemes:\n" + + "- \"https\"\n" + + "consumes:\n" + + "- \"application/json\"\n" + + "produces:\n" + + "- \"application/json\"\n" + + "paths:\n" + + " /somepath:\n" + + " get:\n" + + " description: \"my description\\n\"\n" + + " operationId: \"MyGet\"\n" + + " parameters: []\n" + + " responses:\n" + + " \"200\":\n" + + " $ref: \"#/responses/Response\"\n" + + "responses:\n" + + " Response:\n" + + " description: \"Response\"\n" + + " schema:\n" + + " type: \"object\"\n" + + " required:\n" + + " - \"Report\"\n" + + " properties:\n" + + " Report:\n" + + " type: \"string\"\n" + + " additionalProperties: false"); + } +} diff --git a/modules/swagger-models/src/main/java/io/swagger/models/AbstractModel.java b/modules/swagger-models/src/main/java/io/swagger/models/AbstractModel.java index 957b75ec4d..0caa41f37f 100644 --- a/modules/swagger-models/src/main/java/io/swagger/models/AbstractModel.java +++ b/modules/swagger-models/src/main/java/io/swagger/models/AbstractModel.java @@ -30,6 +30,17 @@ public abstract class AbstractModel implements Model { protected Map properties; protected List required; + protected Boolean booleanValue; + + @JsonIgnore + public Boolean getBooleanValue() { + return booleanValue; + } + + public void setBooleanValue(Boolean booleanValue) { + this.booleanValue = booleanValue; + } + @Override public ExternalDocs getExternalDocs() { return externalDocs; @@ -128,7 +139,7 @@ public void setPattern(String pattern) { public void setVendorExtensions(Map vendorExtensions) { this.vendorExtensions = vendorExtensions; } - + public Map getProperties() { return properties; } @@ -140,7 +151,7 @@ public void setProperties(Map properties) { } } } - + public void addProperty(String key, Property property) { if (property == null) { return; @@ -157,7 +168,7 @@ public void addProperty(String key, Property property) { } properties.put(key, property); } - + public List getRequired() { List output = new ArrayList(); if (properties != null) { @@ -216,6 +227,7 @@ public void cloneTo(Object clone) { } else { cloned.xml = (Xml) xml.clone(); } + cloned.booleanValue = booleanValue; } public Object clone() { @@ -242,6 +254,7 @@ public int hashCode() { result = prime * result + (pattern != null ? pattern.hashCode() : 0); result = prime * result + (properties != null ? properties.hashCode() : 0); result = prime * result + (required != null ? required.hashCode() : 0); + result = prime * result + (booleanValue != null ? booleanValue.hashCode() : 0); return result; } @@ -315,7 +328,7 @@ public boolean equals(Object obj) { } if (pattern != null ? !pattern.equals(other.pattern) : other.pattern != null) { return false; - } + } if (required != null ? !required.equals(other.required) : other.required != null) { return false; } @@ -323,7 +336,12 @@ public boolean equals(Object obj) { return false; } + if (booleanValue != null ? !booleanValue.equals(other.booleanValue) : other.booleanValue != null) { + return false; + } + return maximum != null ? maximum.equals(other.maximum) : other.maximum == null; + } @JsonIgnore diff --git a/modules/swagger-models/src/main/java/io/swagger/models/BooleanValueModel.java b/modules/swagger-models/src/main/java/io/swagger/models/BooleanValueModel.java new file mode 100644 index 0000000000..5a6d3f0d29 --- /dev/null +++ b/modules/swagger-models/src/main/java/io/swagger/models/BooleanValueModel.java @@ -0,0 +1,28 @@ +package io.swagger.models; + +public class BooleanValueModel extends AbstractModel { + + public BooleanValueModel() {} + public BooleanValueModel(Boolean booleanValue) { + this.setBooleanValue(booleanValue); + } + @Override + public String getDescription() { + return null; + } + + @Override + public void setDescription(String description) { + + } + + @Override + public Object getExample() { + return null; + } + + @Override + public void setExample(Object example) { + + } +} diff --git a/modules/swagger-models/src/main/java/io/swagger/models/Model.java b/modules/swagger-models/src/main/java/io/swagger/models/Model.java index 75eef7cfa1..b6c20df9ea 100644 --- a/modules/swagger-models/src/main/java/io/swagger/models/Model.java +++ b/modules/swagger-models/src/main/java/io/swagger/models/Model.java @@ -1,5 +1,6 @@ package io.swagger.models; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.models.properties.Property; import java.util.Map; @@ -30,4 +31,9 @@ public interface Model { Object clone(); Map getVendorExtensions(); + + @JsonIgnore + Boolean getBooleanValue(); + + void setBooleanValue(Boolean booleanValue); } diff --git a/modules/swagger-models/src/main/java/io/swagger/models/RefModel.java b/modules/swagger-models/src/main/java/io/swagger/models/RefModel.java index a56d995d19..81503f55f1 100644 --- a/modules/swagger-models/src/main/java/io/swagger/models/RefModel.java +++ b/modules/swagger-models/src/main/java/io/swagger/models/RefModel.java @@ -127,6 +127,17 @@ public Map getVendorExtensions() { return null; } + @Override + @JsonIgnore + public Boolean getBooleanValue() { + return null; + } + + @Override + public void setBooleanValue(Boolean booleanValue) { + + } + @Override public int hashCode() { final int prime = 31; @@ -202,4 +213,4 @@ public String getReference() { public void setReference(String reference) { this.genericRef = new GenericRef(RefType.DEFINITION, reference); } -} \ No newline at end of file +} diff --git a/modules/swagger-models/src/main/java/io/swagger/models/properties/AbstractProperty.java b/modules/swagger-models/src/main/java/io/swagger/models/properties/AbstractProperty.java index dc6c10cc2c..e0b6f8d190 100644 --- a/modules/swagger-models/src/main/java/io/swagger/models/properties/AbstractProperty.java +++ b/modules/swagger-models/src/main/java/io/swagger/models/properties/AbstractProperty.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.models.Xml; import java.util.LinkedHashMap; @@ -21,6 +22,7 @@ public abstract class AbstractProperty implements Property, Cloneable { protected Boolean allowEmptyValue; protected String access; protected Map vendorExtensions = new LinkedHashMap(); + protected Boolean booleanValue; @Override public Property rename(String newName) { @@ -33,6 +35,15 @@ public Property rename(String newName) { } } + @JsonIgnore + public Boolean getBooleanValue() { + return booleanValue; + } + + public void setBooleanValue(Boolean booleanValue) { + this.booleanValue = booleanValue; + } + public Property title(String title) { this.setTitle(title); return this; @@ -96,6 +107,10 @@ public boolean equals(Object o) { return false; } + if (booleanValue != null ? !booleanValue.equals(that.booleanValue) : that.booleanValue != null) { + return false; + } + return vendorExtensions != null ? vendorExtensions.equals(that.vendorExtensions) : that.vendorExtensions == null; } @@ -115,6 +130,7 @@ public int hashCode() { result = 31 * result + (allowEmptyValue != null ? allowEmptyValue.hashCode() : 0); result = 31 * result + (access != null ? access.hashCode() : 0); result = 31 * result + (vendorExtensions != null ? vendorExtensions.hashCode() : 0); + result = 31 * result + (booleanValue != null ? booleanValue.hashCode() : 0); return result; } diff --git a/modules/swagger-models/src/main/java/io/swagger/models/properties/BooleanValueProperty.java b/modules/swagger-models/src/main/java/io/swagger/models/properties/BooleanValueProperty.java new file mode 100644 index 0000000000..69d071223c --- /dev/null +++ b/modules/swagger-models/src/main/java/io/swagger/models/properties/BooleanValueProperty.java @@ -0,0 +1,8 @@ +package io.swagger.models.properties; + +public class BooleanValueProperty extends AbstractProperty implements Property { + public BooleanValueProperty() {} + public BooleanValueProperty(Boolean booleanValue) { + this.booleanValue = booleanValue; + } +} diff --git a/modules/swagger-models/src/main/java/io/swagger/models/properties/Property.java b/modules/swagger-models/src/main/java/io/swagger/models/properties/Property.java index 26f76bc9e5..81613bd3eb 100644 --- a/modules/swagger-models/src/main/java/io/swagger/models/properties/Property.java +++ b/modules/swagger-models/src/main/java/io/swagger/models/properties/Property.java @@ -76,4 +76,10 @@ public interface Property { * @return new shallow copy of the property */ Property rename(String newName); + + @JsonIgnore + Boolean getBooleanValue(); + + void setBooleanValue(Boolean booleanValue); + } diff --git a/modules/swagger-models/src/main/java/io/swagger/models/properties/PropertyBuilder.java b/modules/swagger-models/src/main/java/io/swagger/models/properties/PropertyBuilder.java index 9e3ffb6c28..20449f7143 100644 --- a/modules/swagger-models/src/main/java/io/swagger/models/properties/PropertyBuilder.java +++ b/modules/swagger-models/src/main/java/io/swagger/models/properties/PropertyBuilder.java @@ -99,7 +99,8 @@ public enum PropertyId { REQUIRED("required"), VENDOR_EXTENSIONS("vendorExtensions"), ALLOW_EMPTY_VALUE("allowEmptyValue"), - MULTIPLE_OF("multipleOf"); + MULTIPLE_OF("multipleOf"), + ADDITIONAL_PROPERTIES("additionalProperties"); private String propertyName;