Skip to content

Commit

Permalink
Merge pull request #214 from MikeEdgar/210_handle_optional
Browse files Browse the repository at this point in the history
Handle Optional types
  • Loading branch information
EricWittmann authored Nov 18, 2019
2 parents 8b9d4ff + 870e522 commit 1f93580
Show file tree
Hide file tree
Showing 10 changed files with 548 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,16 @@ public final class OpenApiConstants {
public static final DotName DOTNAME_RESPONSE = DotName.createSimple(Response.class.getName());
public static final DotName DOTNAME_DEPRECATED = DotName.createSimple(Deprecated.class.getName());

public static final DotName DOTNAME_OPTIONAL = DotName.createSimple(java.util.Optional.class.getName());
public static final DotName DOTNAME_OPTIONAL_DOUBLE = DotName.createSimple(java.util.OptionalDouble.class.getName());
public static final DotName DOTNAME_OPTIONAL_INT = DotName.createSimple(java.util.OptionalInt.class.getName());
public static final DotName DOTNAME_OPTIONAL_LONG = DotName.createSimple(java.util.OptionalLong.class.getName());
public static final Set<DotName> DOTNAME_OPTIONALS = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(DOTNAME_OPTIONAL,
DOTNAME_OPTIONAL_DOUBLE,
DOTNAME_OPTIONAL_INT,
DOTNAME_OPTIONAL_LONG)));

public static final DotName COMPLETION_STAGE_NAME = DotName.createSimple(CompletionStage.class.getName());

public static final DotName DOTNAME_JSONB_PROPERTY = DotName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,21 +597,27 @@ private void processJaxRsMethod(OpenAPIImpl openApi, ClassInfo resourceClass, Me

// TODO if the method argument type is Request, don't generate a Schema!

Type requestBodyType = null;
if (annotation.target().kind() == AnnotationTarget.Kind.METHOD_PARAMETER) {
requestBodyType = JandexUtil.getMethodParameterType(method,
annotation.target().asMethodParameter().position());
} else if (annotation.target().kind() == AnnotationTarget.Kind.METHOD) {
requestBodyType = JandexUtil.getRequestBodyParameterClassType(method, extensions);
}

// Only generate the request body schema if the @RequestBody is not a reference and no schema is yet specified
if (requestBody.getRef() == null && !ModelUtil.requestBodyHasSchema(requestBody)) {
Type requestBodyType = null;
if (annotation.target().kind() == AnnotationTarget.Kind.METHOD_PARAMETER) {
requestBodyType = JandexUtil.getMethodParameterType(method,
annotation.target().asMethodParameter().position());
} else if (annotation.target().kind() == AnnotationTarget.Kind.METHOD) {
requestBodyType = JandexUtil.getRequestBodyParameterClassType(method, extensions);
}
if (requestBodyType != null) {
if (requestBodyType != null && requestBody.getRef() == null) {
if (!ModelUtil.requestBodyHasSchema(requestBody)) {
Schema schema = SchemaFactory.typeToSchema(index, requestBodyType, extensions);

if (schema != null) {
ModelUtil.setRequestBodySchema(requestBody, schema, currentConsumes);
}
}

if (requestBody.getRequired() == null && TypeUtil.isOptional(requestBodyType)) {
requestBody.setRequired(Boolean.FALSE);
}
}
}

Expand Down Expand Up @@ -645,6 +651,10 @@ private void processJaxRsMethod(OpenAPIImpl openApi, ClassInfo resourceClass, Me
if (schema != null) {
ModelUtil.setRequestBodySchema(requestBody, schema, currentConsumes);
}

if (requestBody.getRequired() == null && TypeUtil.isOptional(requestBodyType)) {
requestBody.setRequired(Boolean.FALSE);
}
}
}
}
Expand Down Expand Up @@ -921,6 +931,10 @@ private void createResponseFromJaxRsMethod(MethodInfo method, Operation operatio
produces = OpenApiConstants.DEFAULT_MEDIA_TYPES.get();
}

if (schema.getNullable() == null && TypeUtil.isOptional(returnType)) {
schema.setNullable(Boolean.TRUE);
}

for (String producesType : produces) {
MediaType mt = new MediaTypeImpl();
mt.setSchema(schema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,10 +543,8 @@ private List<Parameter> getParameters() {
ModelUtil.setParameterSchema(param, schema);
}

if (param.getDeprecated() == null) {
if (TypeUtil.getAnnotation(context.target, DOTNAME_DEPRECATED) != null) {
param.setDeprecated(Boolean.TRUE);
}
if (param.getDeprecated() == null && TypeUtil.hasAnnotation(context.target, DOTNAME_DEPRECATED)) {
param.setDeprecated(Boolean.TRUE);
}

if (param.getSchema() != null) {
Expand All @@ -565,6 +563,10 @@ private List<Parameter> getParameters() {
}
}

if (param.getRequired() == null && TypeUtil.isOptional(context.targetType)) {
param.setRequired(Boolean.FALSE);
}

parameters.add(param);
}

Expand Down Expand Up @@ -647,6 +649,10 @@ void setSchemaProperties(Schema schema,
}
});

if (paramSchema.getNullable() == null && TypeUtil.isOptional(paramType)) {
paramSchema.setNullable(Boolean.TRUE);
}

if (schema.getProperties() != null) {
paramSchema = mergeObjects(schema.getProperties().get(paramName), paramSchema);
}
Expand Down Expand Up @@ -816,29 +822,15 @@ void readAnnotatedType(AnnotationInstance annotation, AnnotationInstance beanPar
target);
} else if (target != null) {
// This is a @BeanParam or a RESTEasy @MultipartForm
DotName targetName = null;
setMediaType(jaxRsParam);
Type targetType = getType(target);

switch (target.kind()) {
case FIELD:
targetName = target.asField().type().name();
break;
case METHOD:
List<Type> methodParams = target.asMethod().parameters();
if (methodParams.size() == 1) {
// This is a bean property setter
targetName = methodParams.get(0).name();
}
break;
case METHOD_PARAMETER:
targetName = getMethodParameterType(target.asMethodParameter()).name();
break;
default:
break;
if (TypeUtil.isOptional(targetType)) {
targetType = TypeUtil.getOptionalType(targetType);
}

if (targetName != null) {
ClassInfo beanParam = index.getClassByName(targetName);
if (targetType != null) {
ClassInfo beanParam = index.getClassByName(targetType.name());
readParameters(beanParam, annotation);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ Schema processField() {
TypeUtil.applyTypeAttributes(fieldType, typeSchema);

// The registeredTypeSchema will be a reference to typeSchema if registration occurs
registeredTypeSchema = SchemaRegistry.checkRegistration(entityType, typeResolver, typeSchema);
Type registrationType = TypeUtil.isOptional(entityType) ? fieldType : entityType;
registeredTypeSchema = SchemaRegistry.checkRegistration(registrationType, typeResolver, typeSchema);
}

Schema fieldSchema;
Expand All @@ -165,12 +166,16 @@ Schema processField() {
// Handle field annotated with @Schema.
fieldSchema = readSchemaAnnotatedField(propertyKey, schemaAnnotation, fieldType);
} else {
// Use the type's schema for the field as a starting point
fieldSchema = typeSchema;
// Use the type's schema for the field as a starting point (poor man's clone)
fieldSchema = MergeUtil.mergeObjects(new SchemaImpl(), typeSchema);
}

BeanValidationScanner.applyConstraints(annotationTarget, fieldSchema, propertyKey, this);

if (fieldSchema.getNullable() == null && TypeUtil.isOptional(entityType)) {
fieldSchema.setNullable(Boolean.TRUE);
}

// Only when registration was successful (ref is present and the registered type is a different instance)
if (typeSchema != registeredTypeSchema && registeredTypeSchema.getRef() != null) {
// Check if the field specifies something additional or different from the type's schema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ public Type processType() {
return arrayType;
}

if (TypeUtil.isOptional(type)) {
Type optType = TypeUtil.getOptionalType(type);
if (!isTerminalType(optType) && index.containsClass(optType)) {
pushToStack(optType);
}
return optType;
}

if (isA(type, ENUM_TYPE) && index.containsClass(type)) {
MergeUtil.mergeObjects(schema, SchemaFactory.enumToSchema(index, type));
return STRING_TYPE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,11 @@ private static void updateTypeResolvers(Map<String, TypeResolver> properties,
nameStart = methodName.startsWith("is") ? 2 : 3;
}

if (methodName.length() == nameStart) {
// The method's name is "get", "set", or "is" without the property name
return;
}

propertyName = Character.toLowerCase(methodName.charAt(nameStart)) + methodName.substring(nameStart + 1);
TypeResolver resolver;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,11 @@ static Schema readClassSchema(IndexView index, Type type, boolean schemaReferenc
*/
public static Schema typeToSchema(IndexView index, Type type, List<AnnotationScannerExtension> extensions) {
Schema schema = null;
if (type.kind() == Type.Kind.ARRAY) {

if (TypeUtil.isOptional(type)) {
// Recurse using the optional's type
return typeToSchema(index, TypeUtil.getOptionalType(type), extensions);
} else if (type.kind() == Type.Kind.ARRAY) {
schema = new SchemaImpl().type(SchemaType.ARRAY);
ArrayType array = type.asArrayType();
int dimensions = array.dimensions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,50 @@ public static boolean isTerminalType(Type type) {
tf.getSchemaType() != Schema.SchemaType.ARRAY;
}

/**
* Determine if a given type is one of the following types:
*
* <ul>
* <li><code>java.util.Optional</code>
* <li><code>java.util.OptionalDouble</code>
* <li><code>java.util.OptionalInt</code>
* <li><code>java.util.OptionalLong</code>
* </ul>
*
* @param type the type to check
* @return true if the type is one of the four optional types, otherwise false
*/
public static boolean isOptional(Type type) {
return type != null && OpenApiConstants.DOTNAME_OPTIONALS.contains(type.name());
}

/**
* Unwraps the type parameter (generic or primitive) from the given optional
* type.
*
* @param type the type to unwrap
* @return the generic type argument for <code>java.util.Optional</code>, otherwise the optional primitive double, int, or
* long
*/
public static Type getOptionalType(Type type) {
if (type == null) {
return null;
}
if (OpenApiConstants.DOTNAME_OPTIONAL.equals(type.name())) {
return type.asParameterizedType().arguments().get(0);
}
if (OpenApiConstants.DOTNAME_OPTIONAL_DOUBLE.equals(type.name())) {
return PrimitiveType.DOUBLE;
}
if (OpenApiConstants.DOTNAME_OPTIONAL_INT.equals(type.name())) {
return PrimitiveType.INT;
}
if (OpenApiConstants.DOTNAME_OPTIONAL_LONG.equals(type.name())) {
return PrimitiveType.LONG;
}
return null;
}

public static DotName getName(Type type) {
if (type.kind() == Type.Kind.ARRAY) {
return type.asArrayType().component().name();
Expand Down Expand Up @@ -505,6 +549,9 @@ public static AnnotationInstance getSchemaAnnotation(Type type) {
}

public static boolean hasAnnotation(AnnotationTarget target, DotName annotationName) {
if (target == null) {
return false;
}
switch (target.kind()) {
case CLASS:
return target.asClass().classAnnotation(annotationName) != null;
Expand Down
Loading

0 comments on commit 1f93580

Please sign in to comment.