From 2ecbdb5479a1efd72f2d4bdef525a55fc76b4b36 Mon Sep 17 00:00:00 2001 From: frantuma Date: Fri, 26 Oct 2018 12:30:54 +0200 Subject: [PATCH] ref #2992 - add support for LocalTime and custom system and primitive types --- .../swagger/v3/core/util/PrimitiveType.java | 90 ++++++++++++++++++ .../swagger/v3/core/util/ReflectionUtils.java | 9 +- .../v3/core/resolving/Ticket2992Test.java | 91 +++++++++++++++++++ .../resolving/resources/TestObject2992.java | 73 +++++++++++++++ 4 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket2992Test.java create mode 100644 modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/resources/TestObject2992.java 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 f43d9ba581..e9f01bd7fd 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 @@ -18,7 +18,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; /** * The PrimitiveType enumeration defines a mapping of limited set @@ -126,6 +128,12 @@ public DateTimeSchema createProperty() { return new DateTimeSchema(); } }, + PARTIAL_TIME(java.time.LocalTime.class, "partial-time") { + @Override + public Schema createProperty() { + return new StringSchema().format("partial-time"); + } + }, FILE(java.io.File.class, "file") { @Override public FileSchema createProperty() { @@ -146,6 +154,25 @@ public Schema createProperty() { * Joda lib. */ private static final Map EXTERNAL_CLASSES; + + /** + * Adds support for custom mapping of classes to primitive types + */ + private static Map customClasses = new ConcurrentHashMap(); + + /** + * class qualified names prefixes to be considered as "system" types + */ + private static Set systemPrefixes = new ConcurrentHashMap().newKeySet(); + /** + * class qualified names NOT to be considered as "system" types + */ + private static Set nonSystemTypes = new ConcurrentHashMap().newKeySet(); + /** + * package names NOT to be considered as "system" types + */ + private static Set nonSystemTypePackages = new ConcurrentHashMap().newKeySet(); + /** * Alternative names for primitive types that have to be supported for * backward compatibility. @@ -157,6 +184,10 @@ public Schema createProperty() { public static final Map datatypeMappings; static { + systemPrefixes.add("java."); + systemPrefixes.add("javax."); + nonSystemTypes.add("java.time.LocalTime"); + final Map dms = new HashMap<>(); dms.put("integer_int32", "integer"); dms.put("integer_", "integer"); @@ -173,6 +204,7 @@ public Schema createProperty() { dms.put("string_uuid", "uuid"); dms.put("string_date", "date"); dms.put("string_date-time", "date-time"); + dms.put("string_partial-time", "partial-time"); dms.put("string_password", "password"); dms.put("boolean", "boolean"); dms.put("object_", "object"); @@ -231,16 +263,63 @@ private PrimitiveType(Class keyClass, String commonName) { this.commonName = commonName; } + /** + * Adds support for custom mapping of classes to primitive types + * + * @return Map of custom classes to primitive type + * @since 2.0.6 + */ + public static Map customClasses() { + return customClasses; + } + + /** + * class qualified names prefixes to be considered as "system" types + * + * @return Mutable set of class qualified names prefixes to be considered as "system" types + * @since 2.0.6 + */ + public static Set systemPrefixes() { + return systemPrefixes; + } + + /** + * class qualified names NOT to be considered as "system" types + * + * @return Mutable set of class qualified names NOT to be considered as "system" types + * @since 2.0.6 + */ + public static Set nonSystemTypes() { + return nonSystemTypes; + } + + /** + * package names NOT to be considered as "system" types + * + * @return Mutable set of package names NOT to be considered as "system" types + * @since 2.0.6 + */ + public static Set nonSystemTypePackages() { + return nonSystemTypePackages; + } + public static PrimitiveType fromType(Type type) { final Class raw = TypeFactory.defaultInstance().constructType(type).getRawClass(); final PrimitiveType key = KEY_CLASSES.get(raw); if (key != null) { return key; } + + final PrimitiveType custom = customClasses.get(raw.getName()); + if (custom != null) { + return custom; + } + final PrimitiveType external = EXTERNAL_CLASSES.get(raw.getName()); if (external != null) { return external; } + for (Map.Entry, PrimitiveType> entry : BASE_CLASSES.entrySet()) { if (entry.getKey().isAssignableFrom(raw)) { return entry.getValue(); @@ -302,4 +381,15 @@ private static class DateStub { private DateStub() { } } + + /** + * Convenience method to map LocalTime to string primitive with rfc3339 format partial-time. + * See https://xml2rfc.tools.ietf.org/public/rfc/html/rfc3339.html#anchor14 + * + * @since 2.0.6 + */ + public static void enablePartialTime() { + customClasses().put("org.joda.time.LocalTime", PrimitiveType.PARTIAL_TIME); + customClasses().put("java.time.LocalTime", PrimitiveType.PARTIAL_TIME); + } } diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ReflectionUtils.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ReflectionUtils.java index 168a4f7750..cab7c34f5c 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ReflectionUtils.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ReflectionUtils.java @@ -360,8 +360,13 @@ public static boolean isVoid(Type type) { public static boolean isSystemType(JavaType type) { // used while resolving container types to skip resolving system types; possibly extend by checking classloader // and/or other packages - if (type.getRawClass().getName().startsWith("java")) { - return true; + for (String systemPrefix: PrimitiveType.systemPrefixes()) { + if (type.getRawClass().getName().startsWith(systemPrefix)) { + if ( !PrimitiveType.nonSystemTypes().contains(type.getRawClass().getName()) && + !PrimitiveType.nonSystemTypePackages().contains(type.getRawClass().getPackage().getName())) { + return true; + } + } } if (type.isArrayType()) { return true; diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket2992Test.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket2992Test.java new file mode 100644 index 0000000000..4e5ed46db0 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket2992Test.java @@ -0,0 +1,91 @@ +package io.swagger.v3.core.resolving; + +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.core.converter.ModelConverterContextImpl; +import io.swagger.v3.core.jackson.ModelResolver; +import io.swagger.v3.core.matchers.SerializationMatchers; +import io.swagger.v3.core.resolving.resources.TestObject2992; +import io.swagger.v3.core.util.PrimitiveType; +import io.swagger.v3.oas.models.media.Schema; +import org.testng.annotations.Test; + +public class Ticket2992Test extends SwaggerTestBase { + + @Test + public void testLocalTime() throws Exception { + + final ModelResolver modelResolver = new ModelResolver(mapper()); + + ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver); + + Schema model = context + .resolve(new AnnotatedType(TestObject2992.class)); + + SerializationMatchers.assertEqualsToYaml(context.getDefinedModels(), "LocalTime:\n" + + " type: object\n" + + " properties:\n" + + " hour:\n" + + " type: integer\n" + + " format: int32\n" + + " minute:\n" + + " type: integer\n" + + " format: int32\n" + + " second:\n" + + " type: integer\n" + + " format: int32\n" + + " nano:\n" + + " type: integer\n" + + " format: int32\n" + + "TestObject2992:\n" + + " type: object\n" + + " properties:\n" + + " name:\n" + + " type: string\n" + + " a:\n" + + " $ref: '#/components/schemas/LocalTime'\n" + + " b:\n" + + " $ref: '#/components/schemas/LocalTime'\n" + + " c:\n" + + " $ref: '#/components/schemas/LocalTime'\n" + + " d:\n" + + " type: string\n" + + " format: date-time\n" + + " e:\n" + + " type: string\n" + + " format: date-time\n" + + " f:\n" + + " type: string\n" + + " format: date-time"); + + PrimitiveType.enablePartialTime(); + context = new ModelConverterContextImpl(modelResolver); + + context + .resolve(new AnnotatedType(TestObject2992.class)); + + SerializationMatchers.assertEqualsToYaml(context.getDefinedModels(), "TestObject2992:\n" + + " type: object\n" + + " properties:\n" + + " name:\n" + + " type: string\n" + + " a:\n" + + " type: string\n" + + " format: partial-time\n" + + " b:\n" + + " type: string\n" + + " format: partial-time\n" + + " c:\n" + + " type: string\n" + + " format: partial-time\n" + + " d:\n" + + " type: string\n" + + " format: date-time\n" + + " e:\n" + + " type: string\n" + + " format: date-time\n" + + " f:\n" + + " type: string\n" + + " format: date-time"); + } + +} diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/resources/TestObject2992.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/resources/TestObject2992.java new file mode 100644 index 0000000000..d73c52513d --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/resources/TestObject2992.java @@ -0,0 +1,73 @@ +package io.swagger.v3.core.resolving.resources; + +import java.time.LocalDateTime; +import java.time.LocalTime; + +public class TestObject2992 { + + private String name; + private LocalTime a; + private LocalTime b; + private LocalTime c; + + private LocalDateTime d; + private LocalDateTime e; + private LocalDateTime f; + + + public LocalTime getA() { + return a; + } + + public void setA(LocalTime a) { + this.a = a; + } + + public LocalTime getB() { + return b; + } + + public void setB(LocalTime b) { + this.b = b; + } + + public LocalTime getC() { + return c; + } + + public void setC(LocalTime c) { + this.c = c; + } + + public LocalDateTime getD() { + return d; + } + + public void setD(LocalDateTime d) { + this.d = d; + } + + public LocalDateTime getE() { + return e; + } + + public void setE(LocalDateTime e) { + this.e = e; + } + + public LocalDateTime getF() { + return f; + } + + public void setF(LocalDateTime f) { + this.f = f; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +}