diff --git a/openapi-common/src/main/java/io/micronaut/openapi/swagger/core/util/PrimitiveType.java b/openapi-common/src/main/java/io/micronaut/openapi/swagger/core/util/PrimitiveType.java index 055f7329ea..39fcd8e0a8 100644 --- a/openapi-common/src/main/java/io/micronaut/openapi/swagger/core/util/PrimitiveType.java +++ b/openapi-common/src/main/java/io/micronaut/openapi/swagger/core/util/PrimitiveType.java @@ -15,36 +15,51 @@ */ package io.micronaut.openapi.swagger.core.util; +import com.fasterxml.jackson.databind.type.TypeFactory; +import io.swagger.v3.oas.models.media.BinarySchema; +import io.swagger.v3.oas.models.media.BooleanSchema; +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.FileSchema; +import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.NumberSchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.media.UUIDSchema; + import java.io.File; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.charset.Charset; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.MonthDay; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.TimeZone; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; -import io.swagger.v3.oas.models.media.BinarySchema; -import io.swagger.v3.oas.models.media.BooleanSchema; -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.FileSchema; -import io.swagger.v3.oas.models.media.IntegerSchema; -import io.swagger.v3.oas.models.media.NumberSchema; -import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.oas.models.media.StringSchema; -import io.swagger.v3.oas.models.media.UUIDSchema; - -import com.fasterxml.jackson.databind.type.TypeFactory; - import static java.util.Map.entry; /** @@ -58,22 +73,22 @@ public enum PrimitiveType { STRING(String.class, "string") { @Override - public Schema createProperty() { + public Schema createProperty() { return new StringSchema(); } }, BOOLEAN(Boolean.class, "boolean") { @Override - public Schema createProperty() { + public Schema createProperty() { return new BooleanSchema(); } }, BYTE(Byte.class, "byte") { @Override - public Schema createProperty() { + public Schema createProperty() { if ( - (System.getProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY) != null && System.getProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY).equals(Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString())) || - (System.getenv(Schema.BINARY_STRING_CONVERSION_PROPERTY) != null && System.getenv(Schema.BINARY_STRING_CONVERSION_PROPERTY).equals(Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString()))) { + (System.getProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY) != null && System.getProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY).equals(Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString())) || + (System.getenv(Schema.BINARY_STRING_CONVERSION_PROPERTY) != null && System.getenv(Schema.BINARY_STRING_CONVERSION_PROPERTY).equals(Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString()))) { return new StringSchema().format("byte"); } return new ByteArraySchema(); @@ -81,10 +96,10 @@ public Schema createProperty() { }, BINARY(Byte.class, "binary") { @Override - public Schema createProperty() { + public Schema createProperty() { if ( - (System.getProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY) != null && System.getProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY).equals(Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString())) || - (System.getenv(Schema.BINARY_STRING_CONVERSION_PROPERTY) != null && System.getenv(Schema.BINARY_STRING_CONVERSION_PROPERTY).equals(Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString()))) { + (System.getProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY) != null && System.getProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY).equals(Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString())) || + (System.getenv(Schema.BINARY_STRING_CONVERSION_PROPERTY) != null && System.getenv(Schema.BINARY_STRING_CONVERSION_PROPERTY).equals(Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString()))) { return new StringSchema().format("binary"); } return new BinarySchema(); @@ -92,19 +107,19 @@ public Schema createProperty() { }, URI(java.net.URI.class, "uri") { @Override - public Schema createProperty() { + public Schema createProperty() { return new StringSchema().format("uri"); } }, URL(java.net.URL.class, "url") { @Override - public Schema createProperty() { + public Schema createProperty() { return new StringSchema().format("url"); } }, EMAIL(String.class, "email") { @Override - public Schema createProperty() { + public Schema createProperty() { return new StringSchema().format("email"); } }, @@ -122,37 +137,37 @@ public IntegerSchema createProperty() { }, LONG(Long.class, "long") { @Override - public Schema createProperty() { + public Schema createProperty() { return new IntegerSchema().format("int64"); } }, FLOAT(Float.class, "float") { @Override - public Schema createProperty() { + public Schema createProperty() { return new NumberSchema().format("float"); } }, DOUBLE(Double.class, "double") { @Override - public Schema createProperty() { + public Schema createProperty() { return new NumberSchema().format("double"); } }, INTEGER(BigInteger.class) { @Override - public Schema createProperty() { + public Schema createProperty() { return new IntegerSchema().format(null); } }, DECIMAL(BigDecimal.class, "number") { @Override - public Schema createProperty() { + public Schema createProperty() { return new NumberSchema(); } }, NUMBER(Number.class, "number") { @Override - public Schema createProperty() { + public Schema createProperty() { return new NumberSchema(); } }, @@ -170,7 +185,7 @@ public DateTimeSchema createProperty() { }, PARTIAL_TIME(LocalTime.class, "partial-time") { @Override - public Schema createProperty() { + public Schema createProperty() { return new StringSchema().format("partial-time"); } }, @@ -182,8 +197,8 @@ public FileSchema createProperty() { }, OBJECT(Object.class) { @Override - public Schema createProperty() { - return new Schema().type("object"); + public Schema createProperty() { + return new Schema<>().type("object"); } }; @@ -238,7 +253,6 @@ public Schema createProperty() { static { systemPrefixes.add("java."); systemPrefixes.add("javax."); - nonSystemTypes.add("java.time.LocalTime"); datatypeMappings = Map.ofEntries( entry("integer_int32", "integer"), @@ -262,9 +276,9 @@ public Schema createProperty() { entry("object_", "object") ); - final Map, PrimitiveType> keyClasses = new HashMap<>(); + final var keyClasses = new HashMap, PrimitiveType>(); addKeys(keyClasses, BOOLEAN, Boolean.class, Boolean.TYPE); - addKeys(keyClasses, STRING, String.class, Character.class, Character.TYPE); + addKeys(keyClasses, STRING, String.class, Character.class, CharSequence.class, Character.TYPE); addKeys(keyClasses, BYTE, Byte.class, Byte.TYPE); addKeys(keyClasses, URL, java.net.URL.class); addKeys(keyClasses, URI, java.net.URI.class); @@ -280,31 +294,48 @@ public Schema createProperty() { addKeys(keyClasses, DATE_TIME, Date.class); addKeys(keyClasses, FILE, File.class); addKeys(keyClasses, OBJECT, Object.class); + + // OpenAPI specification haven't 'format' for these 'java.time' classes and other 'java.util' classes + addKeys(keyClasses, STRING, + Duration.class, + Period.class, + LocalTime.class, + OffsetTime.class, + YearMonth.class, + Year.class, + MonthDay.class, + ZoneId.class, + ZoneOffset.class, + TimeZone.class, + Charset.class, + Locale.class + ); + KEY_CLASSES = Collections.unmodifiableMap(keyClasses); - final Map, Collection> multiKeyClasses = new HashMap<>(); + final var multiKeyClasses = new HashMap, Collection>(); addMultiKeys(multiKeyClasses, BYTE, byte[].class); addMultiKeys(multiKeyClasses, BINARY, byte[].class); MULTI_KEY_CLASSES = Collections.unmodifiableMap(multiKeyClasses); - final Map, PrimitiveType> baseClasses = new HashMap<>(); + final var baseClasses = new HashMap, PrimitiveType>(); addKeys(baseClasses, DATE_TIME, Date.class, Calendar.class); BASE_CLASSES = Collections.unmodifiableMap(baseClasses); - final Map externalClasses = new HashMap<>(); - addKeys(externalClasses, DATE, "org.joda.time.LocalDate", "java.time.LocalDate"); + final var externalClasses = new HashMap(); + addKeys(externalClasses, DATE, "org.joda.time.LocalDate", LocalDate.class.getName()); addKeys(externalClasses, DATE_TIME, - "java.time.LocalDateTime", - "java.time.ZonedDateTime", - "java.time.OffsetDateTime", + LocalDateTime.class.getName(), + ZonedDateTime.class.getName(), + OffsetDateTime.class.getName(), + Instant.class.getName(), "javax.xml.datatype.XMLGregorianCalendar", "org.joda.time.LocalDateTime", "org.joda.time.ReadableDateTime", - "org.joda.time.DateTime", - "java.time.Instant"); + "org.joda.time.DateTime"); EXTERNAL_CLASSES = Collections.unmodifiableMap(externalClasses); - final Map names = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + var names = new TreeMap(String.CASE_INSENSITIVE_ORDER); for (PrimitiveType item : values()) { final String name = item.commonName; if (name != null) { @@ -330,7 +361,6 @@ public Schema createProperty() { * Adds support for custom mapping of classes to primitive types * * @return Set of custom classes to primitive type - * * @since 2.0.6 */ public static Set customExcludedClasses() { @@ -341,7 +371,6 @@ public static Set customExcludedClasses() { * Adds support for custom mapping of classes to primitive types * * @return Set of custom classes to primitive type - * * @since 2.1.2 */ public static Set customExcludedExternalClasses() { @@ -352,7 +381,6 @@ public static Set customExcludedExternalClasses() { * 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() { @@ -363,7 +391,6 @@ public static Map 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() { @@ -374,7 +401,6 @@ public static Set 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() { @@ -385,7 +411,6 @@ public static Set 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() { @@ -399,10 +424,10 @@ public static PrimitiveType fromTypeAndFormat(Type type, String format) { return fromType(type); } else { return keys - .stream() - .filter(t -> t.getCommonName().equalsIgnoreCase(format)) - .findAny() - .orElse(null); + .stream() + .filter(t -> t.getCommonName().equalsIgnoreCase(format)) + .findAny() + .orElse(null); } } @@ -457,12 +482,12 @@ public static PrimitiveType fromTypeAndFormat(String type, String format) { return fromName(datatypeMappings.get(String.format("%s_%s", type != null && !type.isBlank() ? type : "", format != null && !format.isBlank() ? format : ""))); } - public static Schema createProperty(Type type) { + public static Schema createProperty(Type type) { final PrimitiveType item = fromType(type); return item == null ? null : item.createProperty(); } - public static Schema createProperty(String name) { + public static Schema createProperty(String name) { final PrimitiveType item = fromName(name); return item == null ? null : item.createProperty(); } @@ -480,7 +505,7 @@ public String getCommonName() { return commonName; } - public abstract Schema createProperty(); + public abstract Schema createProperty(); @SafeVarargs private static void addKeys(Map map, PrimitiveType type, K... keys) { diff --git a/openapi-generator/src/main/java/io/micronaut/openapi/generator/AbstractMicronautKotlinCodegen.java b/openapi-generator/src/main/java/io/micronaut/openapi/generator/AbstractMicronautKotlinCodegen.java index fde06a78fe..d55a46fb33 100644 --- a/openapi-generator/src/main/java/io/micronaut/openapi/generator/AbstractMicronautKotlinCodegen.java +++ b/openapi-generator/src/main/java/io/micronaut/openapi/generator/AbstractMicronautKotlinCodegen.java @@ -969,7 +969,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List checkEnumJsonValueType(VisitorContext context if (returnType.isEnum()) { return checkEnumJsonValueType(context, (EnumElement) returnType, null, null); } - result = ConvertUtils.getTypeAndFormatByClass(returnType.getName(), returnType.isArray()); + result = ConvertUtils.getTypeAndFormatByClass(returnType.getName(), returnType.isArray(), returnType); } if (result == null) { @@ -664,7 +664,7 @@ public static Pair checkEnumJsonValueType(VisitorContext context if (fieldType.isEnum()) { return checkEnumJsonValueType(context, (EnumElement) fieldType, null, null); } - result = ConvertUtils.getTypeAndFormatByClass(fieldType.getName(), fieldType.isArray()); + result = ConvertUtils.getTypeAndFormatByClass(fieldType.getName(), fieldType.isArray(), fieldType); } } if (result == null && isProtobufGenerated(type)) { @@ -678,10 +678,11 @@ public static Pair checkEnumJsonValueType(VisitorContext context * * @param className java class name * @param isArray is it array + * @param classEl class element * * @return pair with openapi type and format */ - public static Pair getTypeAndFormatByClass(String className, boolean isArray) { + public static Pair getTypeAndFormatByClass(String className, boolean isArray, @Nullable ClassElement classEl) { if (className == null) { return Pair.of(TYPE_OBJECT, null); } @@ -742,6 +743,12 @@ public static Pair getTypeAndFormatByClass(String className, boo } else if (LocalTime.class.getName().equals(className)) { return Pair.of(TYPE_STRING, "partial-time"); } else { + if (classEl != null && ElementUtils.isContainerType(classEl)) { + var typeArg = classEl.getFirstTypeArgument().orElse(null); + if (typeArg != null) { + return getTypeAndFormatByClass(typeArg.getName(), typeArg.isArray(), typeArg); + } + } return Pair.of(TYPE_OBJECT, null); } } diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/ElementUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/ElementUtils.java index 86d142d6a0..0c3826760d 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/ElementUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/ElementUtils.java @@ -22,6 +22,7 @@ import io.micronaut.core.annotation.AnnotationMetadata; import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.Internal; +import io.micronaut.core.reflect.ClassUtils; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.annotation.Header; @@ -42,6 +43,15 @@ import java.lang.annotation.Annotation; import java.nio.ByteBuffer; import java.security.Principal; +import java.time.LocalTime; +import java.time.MonthDay; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.ZoneOffset; import java.util.List; import java.util.Map; import java.util.Optional; @@ -242,6 +252,20 @@ public static boolean isIgnoredParameter(TypedElement parameter) { || isIgnoredParameterType(parameter.getType()); } + public static boolean isJavaBasicType(String typeName) { + return ClassUtils.isJavaBasicType(typeName) + || LocalTime.class.getName().equals(typeName) + || OffsetTime.class.getName().equals(typeName) + || OffsetDateTime.class.getName().equals(typeName) + || Period.class.getName().equals(typeName) + || YearMonth.class.getName().equals(typeName) + || Year.class.getName().equals(typeName) + || MonthDay.class.getName().equals(typeName) + || ZoneId.class.getName().equals(typeName) + || ZoneOffset.class.getName().equals(typeName) + ; + } + public static boolean isIgnoredParameterType(ClassElement parameterType) { return parameterType == null || parameterType.isAssignable(Principal.class) diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java index eb745ccd1d..a2bb4f46b8 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java @@ -59,7 +59,6 @@ import io.micronaut.inject.ast.WildcardElement; import io.micronaut.inject.visitor.VisitorContext; import io.micronaut.openapi.OpenApiUtils; -import io.micronaut.openapi.annotation.OpenAPIExtraSchema; import io.micronaut.openapi.javadoc.JavadocDescription; import io.micronaut.openapi.swagger.core.util.PrimitiveType; import io.swagger.v3.oas.annotations.Hidden; @@ -200,6 +199,7 @@ import static io.micronaut.openapi.visitor.SchemaUtils.EMPTY_SCHEMA; import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_ARRAY; import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_OBJECT; +import static io.micronaut.openapi.visitor.SchemaUtils.appendSchema; import static io.micronaut.openapi.visitor.SchemaUtils.getSchemaByRef; import static io.micronaut.openapi.visitor.SchemaUtils.processExtensions; import static io.micronaut.openapi.visitor.SchemaUtils.setAllowableValues; @@ -271,12 +271,12 @@ public static Schema readSchema(AnnotationValue typeAndFormat; - if (typedEl instanceof EnumElement enumEl) { + if (classEl instanceof EnumElement enumEl) { typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, elFormat); } else { - typeAndFormat = ConvertUtils.getTypeAndFormatByClass(typedEl.getName(), typedEl.isArray()); + typeAndFormat = ConvertUtils.getTypeAndFormatByClass(classEl.getName(), classEl.isArray(), classEl); } elType = typeAndFormat.getFirst(); schema.setType(elType); @@ -335,12 +335,10 @@ public static Schema getSchemaDefinition(OpenAPI openAPI, schemaValue = type.getDeclaredAnnotation(io.swagger.v3.oas.annotations.media.Schema.class); } - var isExtraSchema = type.getAnnotation(OpenAPIExtraSchema.class) != null; - Schema schema; Map schemas = SchemaUtils.resolveSchemas(openAPI); if (schemaValue == null) { - final boolean isBasicType = ClassUtils.isJavaBasicType(type.getName()); + final boolean isBasicType = ElementUtils.isJavaBasicType(type.getName()); final PrimitiveType primitiveType; if (isBasicType) { primitiveType = ClassUtils.forName(type.getName(), SchemaDefinitionUtils.class.getClassLoader()) @@ -589,7 +587,7 @@ public static List getEnumValues(EnumElement type, String schemaType, St * @return The schema or null if it cannot be resolved */ @Nullable - public static Schema resolveSchema(@Nullable Element definingElement, ClassElement type, VisitorContext context, List mediaTypes, @Nullable ClassElement jsonViewClass) { + public static Schema resolveSchema(@Nullable Element definingElement, @Nullable ClassElement type, VisitorContext context, List mediaTypes, @Nullable ClassElement jsonViewClass) { return resolveSchema(Utils.resolveOpenApi(context), definingElement, type, context, mediaTypes, jsonViewClass, null, null); } @@ -607,7 +605,7 @@ public static Schema resolveSchema(@Nullable Element definingElement, ClassEl * @return The schema or null if it cannot be resolved */ @Nullable - public static Schema resolveSchema(OpenAPI openApi, @Nullable Element definingElement, ClassElement type, VisitorContext context, + public static Schema resolveSchema(OpenAPI openApi, @Nullable Element definingElement, @Nullable ClassElement type, VisitorContext context, List mediaTypes, @Nullable ClassElement jsonViewClass, JavadocDescription fieldJavadoc, JavadocDescription classJavadoc) { @@ -1395,14 +1393,17 @@ public static Schema bindSchemaAnnotationValue(VisitorContext context, TypedE AnnotationValue schemaAnn, @Nullable ClassElement jsonViewClass) { - ClassElement classElement = element.getType(); + ClassElement classEl = element.getType(); + if (ElementUtils.isContainerType(classEl)) { + classEl = classEl.getFirstTypeArgument().orElse(context.getClassElement("java.lang.Object").orElse(classEl)); + } Pair typeAndFormat; - if (classElement.isIterable()) { + if (classEl.isIterable()) { typeAndFormat = Pair.of(TYPE_ARRAY, null); - } else if (classElement instanceof EnumElement enumEl) { + } else if (classEl instanceof EnumElement enumEl) { typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, null); } else { - typeAndFormat = ConvertUtils.getTypeAndFormatByClass(classElement.getName(), classElement.isArray()); + typeAndFormat = ConvertUtils.getTypeAndFormatByClass(classEl.getName(), classEl.isArray(), classEl); } JsonNode schemaJson = toJson(schemaAnn.getValues(), context, jsonViewClass); @@ -1509,7 +1510,7 @@ public static void processSchemaProperty(VisitorContext context, TypedElement el if (elementType instanceof EnumElement enumEl) { typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, elFormat); } else { - typeAndFormat = ConvertUtils.getTypeAndFormatByClass(elementType.getName(), elementType.isArray()); + typeAndFormat = ConvertUtils.getTypeAndFormatByClass(elementType.getName(), elementType.isArray(), elementType); } elType = typeAndFormat.getFirst(); if (elFormat == null) { @@ -1868,7 +1869,7 @@ private static void processSchemaAnn(Schema schemaToBind, VisitorContext context } else if (element instanceof EnumElement enumEl) { typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, null); } else { - typeAndFormat = ConvertUtils.getTypeAndFormatByClass(classElement.getName(), classElement.isArray()); + typeAndFormat = ConvertUtils.getTypeAndFormatByClass(classElement.getName(), classElement.isArray(), classElement); } } var elType = type != null ? type : typeAndFormat != null ? typeAndFormat.getFirst() : null; @@ -2455,6 +2456,11 @@ private static Schema doBindSchemaAnnotationValue(VisitorContext context, Typ AnnotationValue schemaAnn, @Nullable ClassElement jsonViewClass) { + if (schemaAnn != null && schemaAnn.annotationClassValue(PROP_IMPLEMENTATION).isEmpty()) { + var resolvedSchema = resolveSchema(element, element != null ? element.getType() : null, context, List.of(), null); + schemaToBind = appendSchema(schemaToBind, resolvedSchema); + } + // need to set placeholders to set correct values and types to example field schemaJson = resolvePlaceholders(schemaJson, s -> expandProperties(s, getExpandableProperties(context), context)); try { @@ -2480,7 +2486,7 @@ private static Schema doBindSchemaAnnotationValue(VisitorContext context, Typ if (typeEl instanceof EnumElement enumEl) { typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, elFormat); } else { - typeAndFormat = ConvertUtils.getTypeAndFormatByClass(typeEl.getName(), typeEl.isArray()); + typeAndFormat = ConvertUtils.getTypeAndFormatByClass(typeEl.getName(), typeEl.isArray(), typeEl); } elType = typeAndFormat.getFirst(); if (elFormat == null) { @@ -2492,6 +2498,7 @@ private static Schema doBindSchemaAnnotationValue(VisitorContext context, Typ setDefaultValueObject(schemaToBind, defaultValue, element, elType, elFormat, false, context); } setAllowableValues(schemaToBind, allowableValues, element, elType, elFormat, context); + return schemaToBind; } diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaUtils.java index fe3ed97f02..f399bad0b4 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaUtils.java @@ -665,6 +665,218 @@ public static Schema mergeSchema(Schema s1, Schema s2) { return finalSchema; } + public static Schema appendSchema(Schema s1, Schema s2) { + if (s1 == null) { + return s2; + } + if (s2 == null) { + return null; + } + if (s1.equals(s2)) { + return s1; + } + + if ((s1.getType() == null || TYPE_OBJECT.equals(s1.getType())) && s2.getType() != null && !TYPE_OBJECT.equals(s2.getType())) { + s1.setType(s2.getType()); + if (s1.getFormat() == null && s2.getFormat() != null) { + s1.setFormat(s2.getFormat()); + } + } + if (s1.getName() == null && s2.getName() != null) { + s1.setName(s2.getName()); + } + if (s1.getTitle() == null && s2.getTitle() != null) { + s1.setTitle(s2.getTitle()); + } + if (s1.getMultipleOf() == null && s2.getMultipleOf() != null) { + s1.setMultipleOf(s2.getMultipleOf()); + } + if (s1.getMaximum() == null && s2.getMaximum() != null) { + s1.setMaximum(s2.getMaximum()); + } + if (s1.getExclusiveMaximum() == null && s2.getExclusiveMaximum() != null) { + s1.setExclusiveMaximum(s2.getExclusiveMaximum()); + } + if (s1.getMinimum() == null && s2.getMinimum() != null) { + s1.setMinimum(s2.getMinimum()); + } + if (s1.getExclusiveMinimum() == null && s2.getExclusiveMinimum() != null) { + s1.setExclusiveMinimum(s2.getExclusiveMinimum()); + } + if (s1.getMaxLength() == null && s2.getMaxLength() != null) { + s1.setMaxLength(s2.getMaxLength()); + } + if (s1.getMinLength() == null && s2.getMinLength() != null) { + s1.setMinLength(s2.getMinLength()); + } + if (s1.getPattern() == null && s2.getPattern() != null) { + s1.setPattern(s2.getPattern()); + } + if (s1.getMaxItems() == null && s2.getMaxItems() != null) { + s1.setMaxItems(s2.getMaxItems()); + } + if (s1.getMinItems() == null && s2.getMinItems() != null) { + s1.setMinItems(s2.getMinItems()); + } + if (s1.getUniqueItems() == null && s2.getUniqueItems() != null) { + s1.setUniqueItems(s2.getUniqueItems()); + } + if (s1.getMaxProperties() == null && s2.getMaxProperties() != null) { + s1.setMaxProperties(s2.getMaxProperties()); + } + if (s1.getMinProperties() == null && s2.getMinProperties() != null) { + s1.setMinProperties(s2.getMinProperties()); + } + if (s1.getRequired() == null && s2.getRequired() != null) { + s1.setRequired(s2.getRequired()); + } + if (s1.getType() == null && s2.getType() != null) { + s1.setType(s2.getType()); + } + if (s1.getNot() == null && s2.getNot() != null) { + s1.setNot(s2.getNot()); + } + if (s1.getProperties() == null && s2.getProperties() != null) { + s1.setProperties(s2.getProperties()); + } + if (s1.getAdditionalProperties() == null && s2.getAdditionalProperties() != null) { + s1.setAdditionalProperties(s2.getAdditionalProperties()); + } + if (s1.getDescription() == null && s2.getDescription() != null) { + s1.setDescription(s2.getDescription()); + } + if (s1.get$ref() == null && s2.get$ref() != null) { + s1.set$ref(s2.get$ref()); + } + if (s1.getNullable() == null && s2.getNullable() != null) { + s1.setNullable(s2.getNullable()); + } + if (s1.getReadOnly() == null && s2.getReadOnly() != null) { + s1.setReadOnly(s2.getReadOnly()); + } + if (s1.getWriteOnly() == null && s2.getWriteOnly() != null) { + s1.setWriteOnly(s2.getWriteOnly()); + } + if (s1.getExample() == null && s2.getExample() != null) { + s1.setExample(s2.getExample()); + } + if (s1.getExternalDocs() == null && s2.getExternalDocs() != null) { + s1.setExternalDocs(s2.getExternalDocs()); + } + if (s1.getDeprecated() == null && s2.getDeprecated() != null) { + s1.setDeprecated(s2.getDeprecated()); + } + if (s1.getXml() == null && s2.getXml() != null) { + s1.setXml(s2.getXml()); + } + if (s1.getExtensions() == null && s2.getExtensions() != null) { + s1.setExtensions(s2.getExtensions()); + } + if (s1.getDiscriminator() == null && s2.getDiscriminator() != null) { + s1.setDiscriminator(s2.getDiscriminator()); + } + if (s1.getPrefixItems() == null && s2.getPrefixItems() != null) { + s1.setPrefixItems(s2.getPrefixItems()); + } + if (s1.getElse() == null && s2.getElse() != null) { + s1.setElse(s2.getElse()); + } + if (s1.getAnyOf() == null && s2.getAnyOf() != null) { + s1.setAnyOf(s2.getAnyOf()); + } + if (s1.getOneOf() == null && s2.getOneOf() != null) { + s1.setOneOf(s2.getOneOf()); + } + if (s1.getItems() == null && s2.getItems() != null) { + s1.setItems(s2.getItems()); + } + if (s1.getTypes() == null && s2.getTypes() != null) { + s1.setTypes(s2.getTypes()); + } + if (s1.getPatternProperties() == null && s2.getPatternProperties() != null) { + s1.setPatternProperties(s2.getPatternProperties()); + } + if (s1.getExclusiveMaximumValue() == null && s2.getExclusiveMaximumValue() != null) { + s1.setExclusiveMaximumValue(s2.getExclusiveMaximumValue()); + } + if (s1.getExclusiveMinimumValue() == null && s2.getExclusiveMinimumValue() != null) { + s1.setExclusiveMinimumValue(s2.getExclusiveMinimumValue()); + } + if (s1.getContains() == null && s2.getContains() != null) { + s1.setContains(s2.getContains()); + } + if (s1.get$id() == null && s2.get$id() != null) { + s1.set$id(s2.get$id()); + } + if (s1.get$schema() == null && s2.get$schema() != null) { + s1.set$schema(s2.get$schema()); + } + if (s1.get$anchor() == null && s2.get$anchor() != null) { + s1.set$anchor(s2.get$anchor()); + } + if (s1.get$vocabulary() == null && s2.get$vocabulary() != null) { + s1.set$vocabulary(s2.get$vocabulary()); + } + if (s1.get$dynamicAnchor() == null && s2.get$dynamicAnchor() != null) { + s1.set$dynamicAnchor(s2.get$dynamicAnchor()); + } + if (s1.getContentEncoding() == null && s2.getContentEncoding() != null) { + s1.setContentEncoding(s2.getContentEncoding()); + } + if (s1.getContentMediaType() == null && s2.getContentMediaType() != null) { + s1.setContentMediaType(s2.getContentMediaType()); + } + if (s1.getContentSchema() == null && s2.getContentSchema() != null) { + s1.setContentSchema(s2.getContentSchema()); + } + if (s1.getPropertyNames() == null && s2.getPropertyNames() != null) { + s1.setPropertyNames(s2.getPropertyNames()); + } + if (s1.getUnevaluatedProperties() == null && s2.getUnevaluatedProperties() != null) { + s1.setUnevaluatedProperties(s2.getUnevaluatedProperties()); + } + if (s1.getMaxContains() == null && s2.getMaxContains() != null) { + s1.setMaxContains(s2.getMaxContains()); + } + if (s1.getMinContains() == null && s2.getMinContains() != null) { + s1.setMinContains(s2.getMinContains()); + } + if (s1.getAdditionalItems() == null && s2.getAdditionalItems() != null) { + s1.setAdditionalItems(s2.getAdditionalItems()); + } + if (s1.getUnevaluatedItems() == null && s2.getUnevaluatedItems() != null) { + s1.setUnevaluatedItems(s2.getUnevaluatedItems()); + } + if (s1.getIf() == null && s2.getIf() != null) { + s1.setIf(s2.getIf()); + } + if (s1.getThen() == null && s2.getThen() != null) { + s1.setThen(s2.getThen()); + } + if (s1.getDependentSchemas() == null && s2.getDependentSchemas() != null) { + s1.setDependentSchemas(s2.getDependentSchemas()); + } + if (s1.getDependentRequired() == null && s2.getDependentRequired() != null) { + s1.setDependentRequired(s2.getDependentRequired()); + } + if (s1.get$comment() == null && s2.get$comment() != null) { + s1.set$comment(s2.get$comment()); + } + if (s1.getExamples() == null && s2.getExamples() != null) { + s1.setExamples((List) s2.getExamples()); + } + if (s1.getBooleanSchemaValue() == null && s2.getBooleanSchemaValue() != null) { + s1.setBooleanSchemaValue(s2.getBooleanSchemaValue()); + } + if (s1.getJsonSchema() == null && s2.getJsonSchema() != null) { + s1.setJsonSchema(s2.getJsonSchema()); + } + if (s1.getJsonSchemaImpl() == null && s2.getJsonSchemaImpl() != null) { + s1.setJsonSchemaImpl(s2.getJsonSchemaImpl()); + } + return s1; + } + /** * Copy information from one {@link OpenAPI} object to another. * diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/UrlUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/UrlUtils.java index cd0e0bca48..ed01dd07db 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/UrlUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/UrlUtils.java @@ -41,6 +41,12 @@ public final class UrlUtils { private UrlUtils() { } + /** + * Construct all possible URL variants by parsed segments. + * + * @param segments url template segments + * @return all possible URL variants by parsed segments. + */ public static List buildUrls(List segments) { var results = new ArrayList(); @@ -105,6 +111,12 @@ private static void appendSegment(Segment segment, Segment prevSegment, List parsePathSegments(String pathString) { var segments = new ArrayList(); @@ -179,12 +191,21 @@ private static void addConstValue(String constValue, List segments) { } } + /** + * Segment of urlTemplate. + * + * @param type segment type + * @param value value + */ public record Segment( SegmentType type, String value ) { } + /** + * Type of segment. + */ public enum SegmentType { REQ_VAR, OPT_VAR, diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterSchemaSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterSchemaSpec.groovy index eeba5ceeba..fdd790873f 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterSchemaSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterSchemaSpec.groovy @@ -6,6 +6,260 @@ import io.swagger.v3.oas.models.Operation class OpenApiParameterSchemaSpec extends AbstractOpenApiTypeElementSpec { + void "test basic java classes"() { + + when: + buildBeanDefinition('test.MyBean', ''' +package test; + +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.QueryValue; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.net.URL; +import java.nio.charset.Charset; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.MonthDay; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; +import java.util.UUID; +import java.lang.Number; + +@Controller +class OpenApiController { + + @Get("/Period") + public Period getPeriod(@QueryValue Period param) { + return null; + } + + @Get("/Duration") + public Duration getDuration(@QueryValue Duration param) { + return null; + } + + @Get("/LocalDate") + public LocalDate getLocalDate(@QueryValue LocalDate param) { + return null; + } + + @Get("/Instant") + public Instant getInstant(@QueryValue Instant param) { + return null; + } + + @Get("/ZonedDateTime") + public ZonedDateTime getZonedDateTime(@QueryValue ZonedDateTime param) { + return null; + } + + @Get("/Date") + public Date getDate(@QueryValue Date param) { + return null; + } + + @Get("/OffsetDateTime") + public OffsetDateTime getOffsetDateTime(@QueryValue OffsetDateTime param) { + return null; + } + + @Get("/OffsetTime") + public OffsetTime getOffsetTime(@QueryValue OffsetTime param) { + return null; + } + + @Get("/LocalTime") + public LocalTime getLocalTime(@QueryValue LocalTime param) { + return null; + } + + @Get("/YearMonth") + public YearMonth getYearMonth(@QueryValue YearMonth param) { + return null; + } + + @Get("/Year") + public Year getYear(@QueryValue Year param) { + return null; + } + + @Get("/MonthDay") + public MonthDay getMonthDay(@QueryValue MonthDay param) { + return null; + } + + @Get("/ZoneId") + public ZoneId getZoneId(@QueryValue ZoneId param) { + return null; + } + + @Get("/ZoneOffset") + public ZoneOffset getZoneOffset(@QueryValue ZoneOffset param) { + return null; + } + + @Get("/UUID") + public UUID getUUID(@QueryValue UUID param) { + return null; + } + + @Get("/BigDecimal") + public BigDecimal getBigDecimal(@QueryValue BigDecimal param) { + return null; + } + + @Get("/BigInteger") + public BigInteger getBigInteger(@QueryValue BigInteger param) { + return null; + } + + @Get("/URL") + public URL getURL(@QueryValue URL param) { + return null; + } + + @Get("/URI") + public URI getURI(@QueryValue URI param) { + return null; + } + + @Get("/TimeZone") + public TimeZone getTimeZone(@QueryValue TimeZone param) { + return null; + } + + @Get("/Charset") + public Charset getCharset(@QueryValue Charset param) { + return null; + } + + @Get("/Locale") + public Locale getLocale(@QueryValue Locale param) { + return null; + } + + @Get("/Number") + public Number getNumber(@QueryValue Number param) { + return null; + } +} + +@jakarta.inject.Singleton +class MyBean {} +''') + then: "the state is correct" + Utils.testReference != null + + when: "The OpenAPI is retrieved" + OpenAPI openAPI = Utils.testReference + def paths = openAPI.paths + + then: + paths + + paths."/BigDecimal".get.parameters[0].schema.type == 'number' + paths."/BigDecimal".get.responses."200".content."application/json".schema.type == 'number' + + paths."/BigInteger".get.parameters[0].schema.type == 'integer' + paths."/BigInteger".get.responses."200".content."application/json".schema.type == 'integer' + + paths."/Charset".get.parameters[0].schema.type == 'string' + paths."/Charset".get.responses."200".content."application/json".schema.type == 'string' + + paths."/Date".get.parameters[0].schema.type == 'string' + paths."/Date".get.parameters[0].schema.format == 'date-time' + paths."/Date".get.responses."200".content."application/json".schema.type == 'string' + paths."/Date".get.responses."200".content."application/json".schema.format == 'date-time' + + paths."/Duration".get.parameters[0].schema.type == 'string' + paths."/Duration".get.responses."200".content."application/json".schema.type == 'string' + + paths."/Instant".get.parameters[0].schema.type == 'string' + paths."/Instant".get.parameters[0].schema.format == 'date-time' + paths."/Instant".get.responses."200".content."application/json".schema.type == 'string' + paths."/Instant".get.responses."200".content."application/json".schema.format == 'date-time' + + paths."/LocalDate".get.parameters[0].schema.type == 'string' + paths."/LocalDate".get.parameters[0].schema.format == 'date' + paths."/LocalDate".get.responses."200".content."application/json".schema.type == 'string' + paths."/LocalDate".get.responses."200".content."application/json".schema.format == 'date' + + paths."/LocalTime".get.parameters[0].schema.type == 'string' + paths."/LocalTime".get.parameters[0].schema.format == 'partial-time' + paths."/LocalTime".get.responses."200".content."application/json".schema.type == 'string' + paths."/LocalTime".get.responses."200".content."application/json".schema.format == 'partial-time' + + paths."/Locale".get.parameters[0].schema.type == 'string' + paths."/Locale".get.responses."200".content."application/json".schema.type == 'string' + + paths."/MonthDay".get.parameters[0].schema.type == 'string' + paths."/MonthDay".get.responses."200".content."application/json".schema.type == 'string' + + paths."/Number".get.parameters[0].schema.type == 'number' + paths."/Number".get.responses."200".content."application/json".schema.type == 'number' + + paths."/OffsetDateTime".get.parameters[0].schema.type == 'string' + paths."/OffsetDateTime".get.parameters[0].schema.format == 'date-time' + paths."/OffsetDateTime".get.responses."200".content."application/json".schema.type == 'string' + paths."/OffsetDateTime".get.responses."200".content."application/json".schema.format == 'date-time' + + paths."/OffsetTime".get.parameters[0].schema.type == 'string' + paths."/OffsetTime".get.responses."200".content."application/json".schema.type == 'string' + + paths."/Period".get.parameters[0].schema.type == 'string' + paths."/Period".get.responses."200".content."application/json".schema.type == 'string' + + paths."/TimeZone".get.parameters[0].schema.type == 'string' + paths."/TimeZone".get.responses."200".content."application/json".schema.type == 'string' + + paths."/URI".get.parameters[0].schema.type == 'string' + paths."/URI".get.parameters[0].schema.format == 'uri' + paths."/URI".get.responses."200".content."application/json".schema.type == 'string' + paths."/URI".get.responses."200".content."application/json".schema.format == 'uri' + + paths."/URL".get.parameters[0].schema.type == 'string' + paths."/URL".get.parameters[0].schema.format == 'url' + paths."/URL".get.responses."200".content."application/json".schema.type == 'string' + paths."/URL".get.responses."200".content."application/json".schema.format == 'url' + + paths."/UUID".get.parameters[0].schema.type == 'string' + paths."/UUID".get.parameters[0].schema.format == 'uuid' + paths."/UUID".get.responses."200".content."application/json".schema.type == 'string' + paths."/UUID".get.responses."200".content."application/json".schema.format == 'uuid' + + paths."/Year".get.parameters[0].schema.type == 'string' + paths."/Year".get.responses."200".content."application/json".schema.type == 'string' + + paths."/YearMonth".get.parameters[0].schema.type == 'string' + paths."/YearMonth".get.responses."200".content."application/json".schema.type == 'string' + + paths."/ZoneId".get.parameters[0].schema.type == 'string' + paths."/ZoneId".get.responses."200".content."application/json".schema.type == 'string' + + paths."/ZoneOffset".get.parameters[0].schema.type == 'string' + paths."/ZoneOffset".get.responses."200".content."application/json".schema.type == 'string' + + paths."/ZonedDateTime".get.parameters[0].schema.type == 'string' + paths."/ZonedDateTime".get.parameters[0].schema.format == 'date-time' + paths."/ZonedDateTime".get.responses."200".content."application/json".schema.type == 'string' + paths."/ZonedDateTime".get.responses."200".content."application/json".schema.format == 'date-time' + } + void "test parameter with schema"() { when: @@ -15,7 +269,7 @@ package test; import io.micronaut.http.HttpResponse; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.Post; +import io.micronaut.http.annotation.Patch;import io.micronaut.http.annotation.Post; import io.micronaut.http.annotation.Put; import io.micronaut.http.annotation.QueryValue; import io.swagger.v3.oas.annotations.Parameter; @@ -27,6 +281,11 @@ import java.util.Optional; @Controller("/path") class OpenApiController { + @Patch + public HttpResponse processSync0(@QueryValue Optional period) { + return HttpResponse.ok(); + } + @Get public HttpResponse processSync( @Parameter(schema = @Schema(implementation = String.class)) @QueryValue Optional period) { @@ -54,11 +313,20 @@ class MyBean {} when: "The OpenAPI is retrieved" OpenAPI openAPI = Utils.testReference + Operation operationPatch = openAPI.paths."/path".patch Operation operation = openAPI.paths."/path".get Operation operationPost = openAPI.paths."/path".post Operation operationPut = openAPI.paths."/path".put then: + operationPatch + operationPatch.operationId == "processSync0" + operationPatch.parameters + operationPatch.parameters[0].name == "period" + operationPatch.parameters[0].in == "query" + operationPatch.parameters[0].schema + operationPatch.parameters[0].schema.type == "string" + operation operation.operationId == "processSync" operation.parameters @@ -111,4 +379,78 @@ class MyBean {} operationPatch.parameters[0].schema.type == 'string' operationPatch.parameters[0].schema.format == 'uuid' } + + + void "test parameters schema with annotation"() { + given: + buildBeanDefinition('test.MyBean', ''' +package test; + +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; +import io.micronaut.http.annotation.QueryValue; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.inject.Singleton; + +@Controller +class HelloController { + + @Operation(summary = "Test schema") + @Post(value = "/test") + public String test( + @Parameter(description = "test request parameter", schema = @Schema(implementation = String.class, allowableValues = {"foo", "bar"})) @QueryValue("test") + String test) { + return "test"; + } + + @Operation(summary = "Test2 schema") + @Post(value = "/test2") + public String test2( + @Parameter(description = "test request parameter", schema = @Schema(allowableValues = {"1", "2"})) @QueryValue("test") + Integer test) { + return "test2"; + } +} + +@Singleton +class MyBean {} +''') + when: "The OpenAPI is retrieved" + OpenAPI openAPI = Utils.testReference + + then: "the state is correct" + openAPI != null + + when: + Operation testOp = openAPI.paths.get("/test").post + Operation test2Op = openAPI.paths.get("/test2").post + + then: + testOp + testOp.parameters + testOp.parameters[0].name == 'test' + testOp.parameters[0].in == 'query' + testOp.parameters[0].description == 'test request parameter' + testOp.parameters[0].required + testOp.parameters[0].schema + testOp.parameters[0].schema.type == 'string' + testOp.parameters[0].schema.enum + testOp.parameters[0].schema.enum[0] == 'foo' + testOp.parameters[0].schema.enum[1] == 'bar' + + test2Op + test2Op.parameters + test2Op.parameters[0].name == 'test' + test2Op.parameters[0].in == 'query' + test2Op.parameters[0].description == 'test request parameter' + test2Op.parameters[0].required + test2Op.parameters[0].schema + test2Op.parameters[0].schema.type == 'integer' + test2Op.parameters[0].schema.format == 'int32' + test2Op.parameters[0].schema.enum + test2Op.parameters[0].schema.enum[0] == 1 + test2Op.parameters[0].schema.enum[1] == 2 + } }