Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- #3331 – Validation annotations declared inside `Optional` parameters are dropped
- #3322 – Validation annotations on a container's type argument leak between parameters
- #3315 – An OAS 3.1 `JsonSchema` cannot be cloned through JSON
- #3314 – `Json Processing Exception occurred` is logged for every constrained parameter whose schema is not a `JsonSchema`
- #3300 – TYPE_USE annotations on `@ParameterObject` fields are not passed along
- #3341 – Stabilize Spring Data `Sort` and `Pageable` schema property order
- #3338 – Kotlin nullability interpretation of the `Any?` type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,20 @@

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.swagger.v3.core.jackson.mixin.Schema31Mixin;
import org.springdoc.core.deserializers.TypeSetDeserializer;

/**
* The type Schema type mixin. Makes the OpenAPI 3.1 {@code type} readable back into the
* {@code types} set, whichever of the two serialized forms it takes.
* <p>
* It extends the swagger-core mixin so that it can be registered on {@code Schema} itself
* without losing the serialization it declares, which is what makes the deserializer apply
* to every schema subclass rather than to {@code JsonSchema} alone.
*
* @author Mattias-Sehlstedt
*/
public interface SchemaTypeMixin {
public abstract class SchemaTypeMixin extends Schema31Mixin {

/**
* Sets types.
Expand All @@ -47,6 +52,6 @@ public interface SchemaTypeMixin {
*/
@JsonProperty("type")
@JsonDeserialize(using = TypeSetDeserializer.class)
void setTypes(Set<String> types);
public abstract void setTypes(Set<String> types);

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.swagger.v3.core.jackson.mixin.Schema31Mixin;
import org.springdoc.core.deserializers.TypeSetDeserializer;

/**
* The interface Sorted schema mixin 31.
Expand Down Expand Up @@ -121,6 +123,15 @@ public interface SortedSchemaMixin31 {
@JsonSerialize(using = Schema31Mixin.TypeSerializer.class)
Set<String> getTypes();

/**
* Sets types.
*
* @param types the types
*/
@JsonProperty("type")
@JsonDeserialize(using = TypeSetDeserializer.class)
void setTypes(Set<String> types);

/**
* Add extension.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.core.util.Yaml31;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.JsonSchema;
import io.swagger.v3.oas.models.media.Schema;
import org.springdoc.core.mixins.SchemaTypeMixin;
import org.springdoc.core.mixins.SortedOpenAPIMixin;
Expand Down Expand Up @@ -76,7 +75,7 @@ public ObjectMapperProvider(SpringDocConfigProperties springDocConfigProperties)
if (openApiVersion == OpenApiVersion.OPENAPI_3_1) {
jsonMapper = Json31.mapper();
yamlMapper = Yaml31.mapper();
jsonMapper.addMixIn(JsonSchema.class, SchemaTypeMixin.class);
jsonMapper.addMixIn(Schema.class, SchemaTypeMixin.class);
if (springDocConfigProperties.isUseArbitrarySchemas()) {
System.setProperty(Schema.USE_ARBITRARY_SCHEMA_PROPERTY, "true");
}
Expand All @@ -99,8 +98,10 @@ public ObjectMapperProvider(SpringDocConfigProperties springDocConfigProperties)
public static ObjectMapper createJson(SpringDocConfigProperties springDocConfigProperties) {
OpenApiVersion openApiVersion = springDocConfigProperties.getApiDocs().getVersion();
ObjectMapper objectMapper;
if (openApiVersion == OpenApiVersion.OPENAPI_3_1)
if (openApiVersion == OpenApiVersion.OPENAPI_3_1) {
objectMapper = ObjectMapperFactory.createJson31();
objectMapper.addMixIn(Schema.class, SchemaTypeMixin.class);
}
else
objectMapper = ObjectMapperFactory.createJson();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@

import java.util.Set;

import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.JsonSchema;
import io.swagger.v3.oas.models.media.StringSchema;
import org.junit.jupiter.api.Test;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.properties.SpringDocConfigProperties.ApiDocs.OpenApiVersion;
Expand Down Expand Up @@ -90,10 +92,61 @@ void typeArrayIsRetainedForJsonSchemaJsonCloning() {
assertEquals(Set.of("integer", "null"), cloned.getTypes());
}

@Test
void singleTypeForSchemaSubclassJsonCloning() {
ObjectMapperProvider provider = openapi31Provider();

ArraySchema arraySchema = new ArraySchema();
arraySchema.setTypes(Set.of("array"));
arraySchema.setItems(new StringSchema());

ArraySchema cloned = SpringDocUtils.cloneViaJson(arraySchema, ArraySchema.class, provider.jsonMapper());

// The object is cloned properly, we do not get the type cast fallback
assertNotSame(arraySchema, cloned);
assertNotNull(cloned);
assertEquals(Set.of("array"), cloned.getTypes());
}

@Test
void singleTypeForSchemaSubclassJsonCloningWithAStandaloneMapper() {
ArraySchema arraySchema = new ArraySchema();
arraySchema.setTypes(Set.of("array"));
arraySchema.setItems(new StringSchema());

ArraySchema cloned = SpringDocUtils.cloneViaJson(arraySchema, ArraySchema.class, ObjectMapperProvider.createJson(openapi31Properties()));

// The object is cloned properly, we do not get the type cast fallback
assertNotSame(arraySchema, cloned);
assertNotNull(cloned);
assertEquals(Set.of("array"), cloned.getTypes());
}

@Test
void singleTypeForSchemaSubclassJsonCloningWithASortingMapper() {
SpringDocConfigProperties properties = openapi31Properties();
properties.setWriterWithOrderByKeys(true);

ArraySchema arraySchema = new ArraySchema();
arraySchema.setTypes(Set.of("array"));
arraySchema.setItems(new StringSchema());

ArraySchema cloned = SpringDocUtils.cloneViaJson(arraySchema, ArraySchema.class, ObjectMapperProvider.createJson(properties));

// The object is cloned properly, we do not get the type cast fallback
assertNotSame(arraySchema, cloned);
assertNotNull(cloned);
assertEquals(Set.of("array"), cloned.getTypes());
}

private ObjectMapperProvider openapi31Provider() {
return new ObjectMapperProvider(openapi31Properties());
}

private SpringDocConfigProperties openapi31Properties() {
SpringDocConfigProperties properties = new SpringDocConfigProperties();
properties.getApiDocs().setVersion(OpenApiVersion.OPENAPI_3_1);
return new ObjectMapperProvider(properties);
return properties;
}

}
Loading