Skip to content

Commit

Permalink
Merge pull request #3627 from swagger-api/ticket-3624
Browse files Browse the repository at this point in the history
refs #3624 - fix self referenced Optional property
  • Loading branch information
frantuma committed Jul 11, 2020
2 parents e9c4d61 + 309bf22 commit 57c8f64
Show file tree
Hide file tree
Showing 11 changed files with 326 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.swagger.v3.core.converter;

import io.swagger.v3.core.util.OptionalUtils;
import io.swagger.v3.oas.models.media.Schema;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
Expand Down Expand Up @@ -75,6 +76,11 @@ public Map<String, Schema> getDefinedModels() {
@Override
public Schema resolve(AnnotatedType type) {

AnnotatedType aType = OptionalUtils.unwrapOptional(type);
if (aType != null) {
return resolve(aType);
}

if (processedTypes.contains(type)) {
return modelByType.get(type);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import io.swagger.v3.core.util.Constants;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.ObjectMapperFactory;
import io.swagger.v3.core.util.OptionalUtils;
import io.swagger.v3.core.util.PrimitiveType;
import io.swagger.v3.core.util.ReflectionUtils;
import io.swagger.v3.oas.annotations.media.DiscriminatorMapping;
Expand Down Expand Up @@ -486,17 +487,8 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
.type("object")
.name(name);
} else {
if (_isOptionalType(type)) {
AnnotatedType aType = new AnnotatedType()
.type(type.containedType(0))
.ctxAnnotations(annotatedType.getCtxAnnotations())
.parent(annotatedType.getParent())
.schemaProperty(annotatedType.isSchemaProperty())
.name(annotatedType.getName())
.resolveAsRef(annotatedType.isResolveAsRef())
.jsonViewAnnotation(annotatedType.getJsonViewAnnotation())
.propertyName(annotatedType.getPropertyName())
.skipOverride(true);
AnnotatedType aType = OptionalUtils.unwrapOptional(annotatedType);
if (aType != null) {
model = context.resolve(aType);
return model;
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.swagger.v3.core.util;

import com.fasterxml.jackson.databind.JavaType;
import io.swagger.v3.core.converter.AnnotatedType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

public abstract class OptionalUtils {

private static Logger LOGGER = LoggerFactory.getLogger(OptionalUtils.class);

public static boolean _isOptionalType(JavaType jtype) {

return Arrays.asList("com.google.common.base.Optional", "java.util.Optional")
.contains(jtype.getRawClass().getCanonicalName());
}

/**
* check if type is an Optional type, returns the unwrapped type in case, otherwise null
*
* @param type
*
*/
public static AnnotatedType unwrapOptional(AnnotatedType type) {

if (type == null) {
return type;
}
try {
final JavaType jtype;
if (type.getType() instanceof JavaType) {
jtype = (JavaType) type.getType();
} else {
jtype = Json.mapper().constructType(type.getType());
}

if (_isOptionalType(jtype)) {
AnnotatedType aType = new AnnotatedType()
.type(jtype.containedType(0))
.name(type.getName())
.parent(type.getParent())
.jsonUnwrappedHandler(type.getJsonUnwrappedHandler())
.skipOverride(true)
.schemaProperty(type.isSchemaProperty())
.ctxAnnotations(type.getCtxAnnotations())
.resolveAsRef(type.isResolveAsRef())
.jsonViewAnnotation(type.getJsonViewAnnotation())
.skipSchemaName(type.isSkipSchemaName())
.skipJsonIdentity(type.isSkipJsonIdentity())
.propertyName(type.getPropertyName());
return aType;
} else {
return null;
}
} catch (Exception e) {
LOGGER.error("Error unwrapping optional", e);
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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.util.Yaml;
import io.swagger.v3.oas.models.media.Schema;
import org.testng.annotations.Test;

import java.util.Optional;

public class Ticket3624Test extends SwaggerTestBase {

@Test
public void testSelfReferencingOptional() throws Exception {

final ModelResolver modelResolver = new ModelResolver(mapper());

ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

Schema model = context
.resolve(new AnnotatedType(ModelContainer.class));

Yaml.prettyPrint(context.getDefinedModels());


SerializationMatchers.assertEqualsToYaml(context.getDefinedModels(), "Model:\n" +
" type: object\n" +
" properties:\n" +
" model:\n" +
" $ref: '#/components/schemas/Model'\n" +
"ModelContainer:\n" +
" type: object\n" +
" properties:\n" +
" model:\n" +
" $ref: '#/components/schemas/Model'\n");

}

static class ModelContainer {
public Optional<Model> model;
}

static class Model {
public Optional<Model> model;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import io.swagger.v3.jaxrs2.resources.extensions.ParameterExtensionsResource;
import io.swagger.v3.jaxrs2.resources.extensions.RequestBodyExtensionsResource;
import io.swagger.v3.jaxrs2.resources.rs.ProcessTokenRestService;
import io.swagger.v3.jaxrs2.resources.ticket3624.Service;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.ExternalDocumentation;
Expand Down Expand Up @@ -2238,4 +2239,110 @@ public void testTicket3587() {
+ " '*/*': {}";
SerializationMatchers.assertEqualsToYamlExact(openAPI, yaml);
}

@Test(description = "Optional hanlding")
public void testTicket3624() {
Reader reader = new Reader(new OpenAPI());

OpenAPI openAPI = reader.read(Service.class);
String yaml = "openapi: 3.0.1\n" +
"paths:\n" +
" /example/model:\n" +
" get:\n" +
" tags:\n" +
" - ExampleService\n" +
" summary: ' Retrieve models for display to the user'\n" +
" operationId: getModels\n" +
" responses:\n" +
" default:\n" +
" description: default response\n" +
" content:\n" +
" application/json:\n" +
" schema:\n" +
" $ref: '#/components/schemas/Response'\n" +
" /example/model/by/ids:\n" +
" get:\n" +
" tags:\n" +
" - ExampleService\n" +
" summary: ' Retrieve models by their ids'\n" +
" operationId: getModelsById\n" +
" responses:\n" +
" default:\n" +
" description: default response\n" +
" content:\n" +
" application/json:\n" +
" schema:\n" +
" $ref: '#/components/schemas/ByIdResponse'\n" +
" /example/containerized/model:\n" +
" get:\n" +
" tags:\n" +
" - ExampleService\n" +
" summary: ' Retrieve review insights for a specific product'\n" +
" operationId: getContainerizedModels\n" +
" responses:\n" +
" default:\n" +
" description: default response\n" +
" content:\n" +
" application/json:\n" +
" schema:\n" +
" $ref: '#/components/schemas/ContainerizedResponse'\n" +
"components:\n" +
" schemas:\n" +
" Model:\n" +
" type: object\n" +
" properties:\n" +
" text:\n" +
" type: string\n" +
" title:\n" +
" type: string\n" +
" active:\n" +
" type: boolean\n" +
" schemaParent:\n" +
" $ref: '#/components/schemas/Model'\n" +
" optionalString:\n" +
" type: string\n" +
" parent:\n" +
" $ref: '#/components/schemas/Model'\n" +
" id:\n" +
" type: integer\n" +
" format: int32\n" +
" Response:\n" +
" type: object\n" +
" properties:\n" +
" count:\n" +
" type: integer\n" +
" format: int32\n" +
" models:\n" +
" type: array\n" +
" items:\n" +
" $ref: '#/components/schemas/Model'\n" +
" ByIdResponse:\n" +
" type: object\n" +
" properties:\n" +
" modelsById:\n" +
" type: object\n" +
" additionalProperties:\n" +
" $ref: '#/components/schemas/Model'\n" +
" ContainerizedResponse:\n" +
" type: object\n" +
" properties:\n" +
" totalCount:\n" +
" type: integer\n" +
" format: int32\n" +
" containerizedModels:\n" +
" type: array\n" +
" items:\n" +
" $ref: '#/components/schemas/ModelContainer'\n" +
" ModelContainer:\n" +
" type: object\n" +
" properties:\n" +
" text:\n" +
" type: string\n" +
" model:\n" +
" $ref: '#/components/schemas/Model'\n" +
" id:\n" +
" type: integer\n" +
" format: int32";
SerializationMatchers.assertEqualsToYaml(openAPI, yaml);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.swagger.v3.jaxrs2.resources.ticket3624;

import io.swagger.v3.jaxrs2.resources.ticket3624.model.ByIdResponse;
import io.swagger.v3.jaxrs2.resources.ticket3624.model.ContainerizedResponse;
import io.swagger.v3.jaxrs2.resources.ticket3624.model.Response;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Tag(name = "ExampleService")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path(Service.ROOT_PATH)
public interface Service {
String ROOT_PATH = "/example";

@GET
@Path("/model")
@Operation(summary = " Retrieve models for display to the user")
Response getModels();


@GET
@Path("/model/by/ids")
@Operation(summary = " Retrieve models by their ids")
ByIdResponse getModelsById();

@GET
@Path("/containerized/model")
@Operation(summary = " Retrieve review insights for a specific product")
ContainerizedResponse getContainerizedModels();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.swagger.v3.jaxrs2.resources.ticket3624.model;

import java.util.Map;

public abstract class ByIdResponse {
public abstract Map<Integer, Model> getModelsById();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.swagger.v3.jaxrs2.resources.ticket3624.model;

import java.util.List;

public abstract class ContainerizedResponse {
public abstract List<ModelContainer> getContainerizedModels();
public abstract int getTotalCount();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.swagger.v3.jaxrs2.resources.ticket3624.model;

import io.swagger.v3.oas.annotations.media.Schema;

import java.util.Optional;


public abstract class Model {
// this is the ID of the review
public abstract int getId();

public abstract String getText();

public abstract String getTitle();

public abstract boolean isActive();

public abstract Optional<String> getOptionalString();

public abstract Optional<Model> getParent();

@Schema
public abstract Optional<Model> getSchemaParent();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.swagger.v3.jaxrs2.resources.ticket3624.model;


import java.util.Optional;


public abstract class ModelContainer {
public abstract Optional<Model> getModel();
public abstract int getId();
public abstract String getText();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.swagger.v3.jaxrs2.resources.ticket3624.model;

import java.util.List;

public abstract class Response {
public abstract List<Model> getModels();

public abstract int getCount();
}

0 comments on commit 57c8f64

Please sign in to comment.