Skip to content

Commit

Permalink
Polymorphic fields on polymorphic parents don't get correct oneOf doc…
Browse files Browse the repository at this point in the history
…s generated. Fixes #2597
  • Loading branch information
bnasslahsen committed Jun 30, 2024
1 parent 8a1e0ad commit 935b984
Show file tree
Hide file tree
Showing 10 changed files with 666 additions and 202 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.JavaType;
import io.swagger.v3.core.converter.AnnotatedType;
Expand All @@ -43,6 +43,7 @@

/**
* The type Polymorphic model converter.
*
* @author bnasslahsen
*/
public class PolymorphicModelConverter implements ModelConverter {
Expand All @@ -52,6 +53,17 @@ public class PolymorphicModelConverter implements ModelConverter {
*/
private final ObjectMapperProvider springDocObjectMapper;

/**
* The constant PARENT_TYPES_TO_IGNORE.
*/
private static final List<String> PARENT_TYPES_TO_IGNORE = Collections.synchronizedList(new ArrayList<>());

static {
PARENT_TYPES_TO_IGNORE.add("JsonSchema");
PARENT_TYPES_TO_IGNORE.add("Pageable");
PARENT_TYPES_TO_IGNORE.add("EntityModel");
}

/**
* Instantiates a new Polymorphic model converter.
*
Expand All @@ -61,12 +73,21 @@ public PolymorphicModelConverter(ObjectMapperProvider springDocObjectMapper) {
this.springDocObjectMapper = springDocObjectMapper;
}

private static Schema<?> getResolvedSchema(JavaType javaType, Schema<?> resolvedSchema) {
/**
* Add parent type.
*
* @param parentTypes the parent types
*/
public static void addParentType(String... parentTypes) {
PARENT_TYPES_TO_IGNORE.addAll(List.of(parentTypes));
}

private Schema<?> getResolvedSchema(JavaType javaType, Schema<?> resolvedSchema) {
if (resolvedSchema instanceof ObjectSchema && resolvedSchema.getProperties() != null) {
if (resolvedSchema.getProperties().containsKey(javaType.getRawClass().getName())){
if (resolvedSchema.getProperties().containsKey(javaType.getRawClass().getName())) {
resolvedSchema = resolvedSchema.getProperties().get(javaType.getRawClass().getName());
}
else if (resolvedSchema.getProperties().containsKey(javaType.getRawClass().getSimpleName())){
else if (resolvedSchema.getProperties().containsKey(javaType.getRawClass().getSimpleName())) {
resolvedSchema = resolvedSchema.getProperties().get(javaType.getRawClass().getSimpleName());
}
}
Expand All @@ -78,6 +99,9 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato
JavaType javaType = springDocObjectMapper.jsonMapper().constructType(type.getType());
if (javaType != null) {
if (chain.hasNext()) {
if (!type.isResolveAsRef() && type.getParent() != null
&& PARENT_TYPES_TO_IGNORE.stream().noneMatch(ignore -> type.getParent().getName().startsWith(ignore)))
type.resolveAsRef(true);
Schema<?> resolvedSchema = chain.next().resolve(type, context, chain);
resolvedSchema = getResolvedSchema(javaType, resolvedSchema);
if (resolvedSchema == null || resolvedSchema.get$ref() == null)
Expand All @@ -91,8 +115,8 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato
/**
* Compose polymorphic schema.
*
* @param type the type
* @param schema the schema
* @param type the type
* @param schema the schema
* @param schemas the schemas
* @return the schema
*/
Expand All @@ -111,7 +135,7 @@ private Schema composePolymorphicSchema(AnnotatedType type, Schema schema, Colle
/**
* Find composed schemas recursively.
*
* @param ref the reference of the schema
* @param ref the reference of the schema
* @param schemas the collection of schemas to search in
* @return the list of composed schemas
*/
Expand All @@ -133,6 +157,7 @@ private List<Schema> findComposedSchemas(String ref, Collection<Schema> schemas)

return resultSchemas;
}

/**
* Is concrete class boolean.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.springdoc.api.AbstractOpenApiResource;
import org.springdoc.core.converters.AdditionalModelsConverter;
import org.springdoc.core.converters.ConverterUtils;
import org.springdoc.core.converters.PolymorphicModelConverter;
import org.springdoc.core.converters.SchemaPropertyDeprecatingConverter;
import org.springdoc.core.extractor.MethodParameterPojoExtractor;
import org.springdoc.core.service.AbstractRequestService;
Expand Down Expand Up @@ -388,5 +389,16 @@ public static boolean isValidPath(String path) {
return true;
return false;
}

/**
* Add parent type spring doc utils.
*
* @param parentTypes the parent types
* @return the spring doc utils
*/
public SpringDocUtils addParentType(String ...parentTypes) {
PolymorphicModelConverter.addParentType(parentTypes);
return this;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package test.org.springdoc.api.v30.app223;


import test.org.springdoc.api.v30.app223.apiobjects.AbstractChild;
import test.org.springdoc.api.v30.app223.apiobjects.AbstractParent;
import test.org.springdoc.api.v30.app223.apiobjects.Response;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ARestController {
@PostMapping("/parent")
public Response parentEndpoint(@RequestBody AbstractParent parent) {
return null;
}

@PostMapping("/child")
public Response childEndpoint(@RequestBody AbstractChild child) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
*
* *
* * *
* * * *
* * * * * Copyright 2019-2022 the original author or authors.
* * * * *
* * * * * Licensed under the Apache License, Version 2.0 (the "License");
* * * * * you may not use this file except in compliance with the License.
* * * * * You may obtain a copy of the License at
* * * * *
* * * * * https://www.apache.org/licenses/LICENSE-2.0
* * * * *
* * * * * Unless required by applicable law or agreed to in writing, software
* * * * * distributed under the License is distributed on an "AS IS" BASIS,
* * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * * * * See the License for the specific language governing permissions and
* * * * * limitations under the License.
* * * *
* * *
* *
*
*/

package test.org.springdoc.api.v30.app223;

import test.org.springdoc.api.v30.AbstractSpringDocV30Test;

import org.springframework.boot.autoconfigure.SpringBootApplication;

public class SpringDocApp223Test extends AbstractSpringDocV30Test {

@SpringBootApplication
static class SpringDocTestApp {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package test.org.springdoc.api.v30.app223.apiobjects;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

@JsonTypeInfo(use = Id.NAME, property = "type")
@JsonSubTypes({
@Type(ChildType1.class),
@Type(ChildType2.class)
})
public abstract class AbstractChild {
private int id;

public AbstractChild(int id) {
this.id = id;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}
}

class ChildType1 extends AbstractChild {
private String childType1Param;

public ChildType1(int id, String childType1Param) {
super(id);
this.childType1Param = childType1Param;
}

public String getChildType1Param() {
return childType1Param;
}

public void setChildType1Param(String childType1Param) {
this.childType1Param = childType1Param;
}
}

class ChildType2 extends AbstractChild {
private String childType2Param;

public ChildType2(int id, String childType2Param) {
super(id);
this.childType2Param = childType2Param;
}

public String getChildType2Param() {
return childType2Param;
}

public void setChildType2Param(String childType2Param) {
this.childType2Param = childType2Param;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package test.org.springdoc.api.v30.app223.apiobjects;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;


@JsonTypeInfo(use = Id.NAME, property = "type")
@JsonSubTypes({
@Type(ParentType1.class),
@Type(ParentType2.class)
})
public abstract class AbstractParent {
private int id;

public AbstractParent(int id) {
this.id = id;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}
}

class ParentType1 extends AbstractParent {
private String parentType1Param;
private AbstractChild abstractChild;

public ParentType1(int id, String parentType1Param, AbstractChild abstractChild) {
super(id);
this.parentType1Param = parentType1Param;
this.abstractChild = abstractChild;
}

public String getParentType1Param() {
return parentType1Param;
}

public void setParentType1Param(String parentType1Param) {
this.parentType1Param = parentType1Param;
}

public AbstractChild getAbstractChild() {
return abstractChild;
}

public void setAbstractChild(AbstractChild abstractChild) {
this.abstractChild = abstractChild;
}
}

class ParentType2 extends AbstractParent {
private String parentType2Param;

public ParentType2(int id, String parentType2Param) {
super(id);
this.parentType2Param = parentType2Param;
}

public String getParentType2Param() {
return parentType2Param;
}

public void setParentType2Param(String parentType2Param) {
this.parentType2Param = parentType2Param;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package test.org.springdoc.api.v30.app223.apiobjects;

public record Response(AbstractParent abstractParent, AbstractChild abstractChild) {
}
Loading

0 comments on commit 935b984

Please sign in to comment.