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;
+ }
+}