Skip to content

Commit a5ecee0

Browse files
committed
remove empty object placeholder, add tests
1 parent a155c00 commit a5ecee0

2 files changed

Lines changed: 67 additions & 22 deletions

File tree

‎modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java‎

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2682,11 +2682,6 @@ protected void resolveDiscriminatorProperty(JavaType type, ModelConverterContext
26822682
}
26832683
}
26842684

2685-
/** An empty object schema for the current OpenAPI version. */
2686-
private Schema newObjectSchema() {
2687-
return openapi31 ? new JsonSchema().typesItem("object") : new ObjectSchema();
2688-
}
2689-
26902685
/*
26912686
TODO partial implementation supporting WRAPPER_OBJECT with JsonTypeInfo.Id.CLASS and JsonTypeInfo.Id.NAME
26922687
@@ -2711,14 +2706,13 @@ protected Schema resolveWrapping(JavaType type, ModelConverterContext context, S
27112706
if (JsonTypeInfo.Id.NAME.equals(id) && name == null) {
27122707
name = type.getRawClass().getSimpleName();
27132708
}
2714-
// If the model already carries a oneOf (e.g. from @Schema(oneOf) or @JsonSubTypes),
2715-
// adding the wrapper property alongside it preserves the composition instead of
2716-
// nesting the whole model (and dropping the oneOf) inside a wrapper schema.
2709+
// A model that already carries a oneOf (e.g. from @Schema(oneOf) or @JsonSubTypes) is
2710+
// returned unchanged: nesting it inside a wrapper schema would hide the composition.
2711+
// TODO the per-subtype wrapper (e.g. {"Dog": {...}}) is not modelled for oneOf yet.
27172712
if (model.getOneOf() != null && !model.getOneOf().isEmpty()) {
2718-
model.addProperties(name, newObjectSchema());
27192713
return model;
27202714
}
2721-
Schema wrapperSchema = newObjectSchema();
2715+
Schema wrapperSchema = openapi31 ? new JsonSchema().typesItem("object") : new ObjectSchema();
27222716
wrapperSchema.name(model.getName());
27232717
wrapperSchema.addProperties(name, model);
27242718
return wrapperSchema;

‎modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/WrapperObjectOneOfTest.java‎

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
import io.swagger.v3.core.converter.ModelConverterContextImpl;
66
import io.swagger.v3.core.jackson.ModelResolver;
77
import io.swagger.v3.core.util.Json;
8+
import io.swagger.v3.core.util.Json31;
89
import io.swagger.v3.oas.models.media.ComposedSchema;
910
import io.swagger.v3.oas.models.media.Schema;
1011
import org.testng.annotations.Test;
1112

13+
import java.util.Collections;
14+
1215
import static org.testng.Assert.assertEquals;
1316
import static org.testng.Assert.assertNotNull;
17+
import static org.testng.Assert.assertNull;
1418
import static org.testng.Assert.assertTrue;
1519

1620
/**
@@ -19,34 +23,81 @@
1923
* <p>
2024
* {@code ModelResolver.resolveWrapping(...)} used to nest the whole model inside a new wrapper
2125
* schema, which dropped the {@code oneOf} (the wrapper only copied name + properties). The
22-
* composition is now preserved and the wrapper property is added alongside it.
26+
* composition is now preserved: a model with a {@code oneOf} is returned unchanged.
27+
* Per-subtype wrapping is not modelled yet.
2328
*/
2429
public class WrapperObjectOneOfTest {
2530

2631
@Test
2732
public void wrapperObjectPreservesOneOf() {
33+
final Schema<?> model = resolve(new ModelResolver(Json.mapper()), WrappedOneOf.class);
34+
35+
assertTrue(model instanceof ComposedSchema,
36+
"the explicit @Schema(oneOf) must produce a ComposedSchema, got: " + model.getClass().getName());
37+
assertOneOfKept(model);
38+
}
39+
40+
@Test
41+
public void wrapperObjectDoesNotAddWrapperProperty() {
42+
final Schema<?> model = resolve(new ModelResolver(Json.mapper()), WrappedOneOf.class);
43+
44+
// only the declared member is present: no property named after the base type is added next to oneOf
45+
assertEquals(model.getProperties().keySet(), Collections.singleton("petType"));
46+
}
47+
48+
@Test
49+
public void wrapperObjectOneOfIsStableWhenResolvedTwice() {
2850
final ModelResolver modelResolver = new ModelResolver(Json.mapper());
2951
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
3052

31-
final Schema<?> model = context.resolve(new AnnotatedType(WrappedOneOf.class));
53+
final Schema<?> first = context.resolve(new AnnotatedType(WrappedOneOf.class));
54+
final Schema<?> second = context.resolve(new AnnotatedType(WrappedOneOf.class));
3255

33-
assertTrue(model instanceof ComposedSchema,
34-
"the explicit @Schema(oneOf) must produce a ComposedSchema, got: " + model.getClass().getName());
35-
final ComposedSchema composed = (ComposedSchema) model;
36-
assertNotNull(composed.getOneOf(), "oneOf must be preserved when WRAPPER_OBJECT wrapping is applied");
37-
assertEquals(composed.getOneOf().size(), 2, "oneOf should reference both subtypes");
38-
assertEquals(composed.getOneOf().get(0).get$ref(), "#/components/schemas/Dog");
39-
assertEquals(composed.getOneOf().get(1).get$ref(), "#/components/schemas/Cat");
40-
// the WRAPPER_OBJECT property is added next to oneOf, not nested around it
41-
assertNotNull(composed.getProperties(), "the wrapper property must be present alongside oneOf");
56+
assertOneOfKept(first);
57+
assertOneOfKept(second);
58+
assertEquals(second.getProperties().keySet(), first.getProperties().keySet());
4259
}
4360

44-
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT, property = "pet")
61+
@Test
62+
public void wrapperObjectPreservesOneOfOpenApi31() {
63+
final Schema<?> model = resolve(new ModelResolver(Json31.mapper()).openapi31(true), WrappedOneOf.class);
64+
65+
assertOneOfKept(model);
66+
assertEquals(model.getProperties().keySet(), Collections.singleton("petType"));
67+
}
68+
69+
@Test
70+
public void wrapperObjectWithoutOneOfIsStillWrapped() {
71+
final Schema<?> model = resolve(new ModelResolver(Json.mapper()), PlainWrapped.class);
72+
73+
assertNull(model.getOneOf(), "a model without oneOf must not gain one");
74+
assertEquals(model.getProperties().keySet(), Collections.singleton("PlainWrapped"));
75+
final Schema<?> inner = (Schema<?>) model.getProperties().get("PlainWrapped");
76+
assertEquals(inner.getProperties().keySet(), Collections.singleton("value"));
77+
}
78+
79+
private static Schema<?> resolve(ModelResolver modelResolver, Class<?> cls) {
80+
return new ModelConverterContextImpl(modelResolver).resolve(new AnnotatedType(cls));
81+
}
82+
83+
private static void assertOneOfKept(Schema<?> model) {
84+
assertNotNull(model.getOneOf(), "oneOf must be preserved when WRAPPER_OBJECT wrapping is applied");
85+
assertEquals(model.getOneOf().size(), 2, "oneOf should reference both subtypes");
86+
assertEquals(model.getOneOf().get(0).get$ref(), "#/components/schemas/Dog");
87+
assertEquals(model.getOneOf().get(1).get$ref(), "#/components/schemas/Cat");
88+
}
89+
90+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
4591
@io.swagger.v3.oas.annotations.media.Schema(oneOf = {Dog.class, Cat.class})
4692
static abstract class WrappedOneOf {
4793
public String petType;
4894
}
4995

96+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
97+
static class PlainWrapped {
98+
public String value;
99+
}
100+
50101
static class Dog extends WrappedOneOf {
51102
public String bark;
52103
}

0 commit comments

Comments
 (0)