From c5c714eeb829fb1dd90d8c52818aa41e3d552707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannik=20M=C3=B6ll?= Date: Thu, 9 Jan 2025 12:47:25 +0100 Subject: [PATCH] Add DurationSchema for java.time.Duration as PrimitiveType Related to #4404 --- .../v3/core/util/ModelDeserializer.java | 3 + .../swagger/v3/core/util/PrimitiveType.java | 9 +++ .../resolving/JodaDurationConverterTest.java | 36 +++++++++ .../serialization/ModelSerializerTest.java | 10 +++ .../properties/PropertySerializationTest.java | 18 +++++ .../v3/oas/models/media/DurationSchema.java | 79 +++++++++++++++++++ 6 files changed, 155 insertions(+) create mode 100644 modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/JodaDurationConverterTest.java create mode 100644 modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/DurationSchema.java diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ModelDeserializer.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ModelDeserializer.java index 42bfe857b2..0532d4c7a3 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ModelDeserializer.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ModelDeserializer.java @@ -12,6 +12,7 @@ import io.swagger.v3.oas.models.media.ComposedSchema; import io.swagger.v3.oas.models.media.DateSchema; import io.swagger.v3.oas.models.media.DateTimeSchema; +import io.swagger.v3.oas.models.media.DurationSchema; import io.swagger.v3.oas.models.media.EmailSchema; import io.swagger.v3.oas.models.media.IntegerSchema; import io.swagger.v3.oas.models.media.JsonSchema; @@ -75,6 +76,8 @@ public Schema deserialize(JsonParser jp, DeserializationContext ctxt) schema = Json.mapper().convertValue(node, DateSchema.class); } else if ("date-time".equals(format)) { schema = Json.mapper().convertValue(node, DateTimeSchema.class); + } else if ("duration".equals(format)) { + schema = Json.mapper().convertValue(node, DurationSchema.class); } else if ("email".equals(format)) { schema = Json.mapper().convertValue(node, EmailSchema.class); } else if ("password".equals(format)) { diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/PrimitiveType.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/PrimitiveType.java index df32dc2958..c0aa38bb18 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/PrimitiveType.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/PrimitiveType.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.models.media.ByteArraySchema; import io.swagger.v3.oas.models.media.DateSchema; import io.swagger.v3.oas.models.media.DateTimeSchema; +import io.swagger.v3.oas.models.media.DurationSchema; import io.swagger.v3.oas.models.media.FileSchema; import io.swagger.v3.oas.models.media.IntegerSchema; import io.swagger.v3.oas.models.media.NumberSchema; @@ -141,6 +142,12 @@ public DateTimeSchema createProperty() { return new DateTimeSchema(); } }, + DURATION(java.time.Duration.class, "duration") { + @Override + public DurationSchema createProperty() { + return new DurationSchema(); + } + }, PARTIAL_TIME(java.time.LocalTime.class, "partial-time") { @Override public Schema createProperty() { @@ -231,6 +238,7 @@ public Schema createProperty() { dms.put("string_uuid", "uuid"); dms.put("string_date", "date"); dms.put("string_date-time", "date-time"); + dms.put("string_duration", "duration"); dms.put("string_partial-time", "partial-time"); dms.put("string_password", "password"); dms.put("boolean_", "boolean"); @@ -277,6 +285,7 @@ public Schema createProperty() { "org.joda.time.ReadableDateTime", "org.joda.time.DateTime", "java.time.Instant"); + addKeys(externalClasses, DURATION, "org.joda.time.Duration", "java.time.Duration"); EXTERNAL_CLASSES = Collections.unmodifiableMap(externalClasses); final Map names = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/JodaDurationConverterTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/JodaDurationConverterTest.java new file mode 100644 index 0000000000..04047591b1 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/JodaDurationConverterTest.java @@ -0,0 +1,36 @@ +package io.swagger.v3.core.resolving; + +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.oas.models.media.DurationSchema; +import io.swagger.v3.oas.models.media.Schema; +import org.joda.time.Duration; +import org.testng.annotations.Test; + +import java.util.Map; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +public class JodaDurationConverterTest { + + @Test + public void testJodaDuration() { + final Map models = ModelConverters.getInstance().read(ModelWithJodaDuration.class); + assertEquals(models.size(), 1); + + final Schema model = models.get("ModelWithJodaDuration"); + + final Schema durationProperty = (Schema) model.getProperties().get("measuredDuration"); + assertTrue(durationProperty instanceof DurationSchema); + assertTrue(model.getRequired().contains("measuredDuration")); + assertEquals(durationProperty.getDescription(), "Time something took to do"); + + } + + class ModelWithJodaDuration { + + @io.swagger.v3.oas.annotations.media.Schema(description = "Time something took to do", required = true) + public Duration measuredDuration; + } + +} diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/ModelSerializerTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/ModelSerializerTest.java index b13386269c..b5b6f1e643 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/ModelSerializerTest.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/ModelSerializerTest.java @@ -13,6 +13,7 @@ import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.DateSchema; import io.swagger.v3.oas.models.media.DateTimeSchema; +import io.swagger.v3.oas.models.media.DurationSchema; import io.swagger.v3.oas.models.media.IntegerSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; @@ -41,6 +42,7 @@ public void convertModel() throws JsonProcessingException { props.put("longValue", new IntegerSchema().format("int64")); props.put("dateValue", new DateSchema()); props.put("dateTimeValue", new DateTimeSchema()); + props.put("durationValue", new DurationSchema()); pet.setProperties(props); pet.setRequired(Arrays.asList("intValue", "name")); final String json = "{\n" + @@ -63,6 +65,10 @@ public void convertModel() throws JsonProcessingException { " \"dateTimeValue\":{\n" + " \"type\":\"string\",\n" + " \"format\":\"date-time\"\n" + + " },\n" + + " \"durationValue\":{\n" + + " \"type\":\"string\",\n" + + " \"format\":\"duration\"\n" + " }\n" + " }\n" + "}"; @@ -91,6 +97,10 @@ public void deserializeModel() throws IOException { " \"type\":\"string\",\n" + " \"format\":\"date-time\"\n" + " },\n" + + " \"durationValue\":{\n" + + " \"type\":\"string\",\n" + + " \"format\":\"duration\"\n" + + " },\n" + " \"intValue\":{\n" + " \"type\":\"integer\",\n" + " \"format\":\"int32\"\n" + diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/properties/PropertySerializationTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/properties/PropertySerializationTest.java index 719b936be1..11b5740aa3 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/properties/PropertySerializationTest.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/properties/PropertySerializationTest.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.models.media.BooleanSchema; import io.swagger.v3.oas.models.media.DateSchema; import io.swagger.v3.oas.models.media.DateTimeSchema; +import io.swagger.v3.oas.models.media.DurationSchema; import io.swagger.v3.oas.models.media.IntegerSchema; import io.swagger.v3.oas.models.media.MapSchema; import io.swagger.v3.oas.models.media.NumberSchema; @@ -80,6 +81,23 @@ public void deserializeDateTimeProperty() throws IOException { assertEquals(m.writeValueAsString(p), json); } + @Test(description = "it should serialize a DateTimeProperty") + public void serializeDurationProperty() throws IOException { + final DurationSchema p = new DurationSchema(); + final String json = "{\"type\":\"string\",\"format\":\"duration\"}"; + assertEquals(m.writeValueAsString(p), json); + } + + @Test(description = "it should deserialize a DateTimeProperty") + public void deserializeDurationProperty() throws IOException { + final String json = "{\"type\":\"string\",\"format\":\"duration\"}"; + final Schema p = m.readValue(json, Schema.class); + assertEquals(p.getType(), "string"); + assertEquals(p.getFormat(), "duration"); + assertEquals(p.getClass(), DurationSchema.class); + assertEquals(m.writeValueAsString(p), json); + } + @Test(description = "it should serialize a DoubleProperty") public void serializeDoubleProperty() throws IOException { final NumberSchema p = new NumberSchema() diff --git a/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/DurationSchema.java b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/DurationSchema.java new file mode 100644 index 0000000000..389edf6c75 --- /dev/null +++ b/modules/swagger-models/src/main/java/io/swagger/v3/oas/models/media/DurationSchema.java @@ -0,0 +1,79 @@ +package io.swagger.v3.oas.models.media; + +import java.time.Duration; +import java.util.Objects; + +/** + * DurationSchema + */ + +public class DurationSchema extends Schema { + + public DurationSchema() { + super("string", "duration"); + } + + @Override + public DurationSchema type(String type) { + super.setType(type); + return this; + } + + @Override + public DurationSchema format(String format) { + super.setFormat(format); + return this; + } + + @Override + public DurationSchema _default(Duration _default) { + super.setDefault(_default); + return this; + } + + @Override + protected Duration cast(Object value) { + if (value != null) { + try { + if (value instanceof Duration) { + return (Duration) value; + } else if (value instanceof String) { + return Duration.parse((String)value); + } + } catch (Exception e) { + } + } + return null; + } + + public DurationSchema addEnumItem(Duration _enumItem) { + super.addEnumItemObject(_enumItem); + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class DurationSchema {\n"); + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} +