Skip to content

Commit

Permalink
schema resolution options - Phase 4: granular schema resolution via @…
Browse files Browse the repository at this point in the history
…Schema.schemaResolution
  • Loading branch information
frantuma committed Oct 2, 2024
1 parent 9f57589 commit dd028ca
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,14 @@ enum RequiredMode {
NOT_REQUIRED;
}

enum SchemaResolution {
AUTO,
DEFAULT,
INLINE,
ALL_OF,
ALL_OF_REF;
}

/**
* Allows to specify the dependentRequired value
**
Expand Down Expand Up @@ -616,4 +624,17 @@ enum RequiredMode {
*/
@OpenAPI31
String _const() default "";

/**
* Allows to specify the schema resolution mode for object schemas
*
* SchemaResolution.DEFAULT: bundled into components/schemas, $ref with no siblings
* SchemaResolution.INLINE: inline schema, no $ref
* SchemaResolution.ALL_OF: bundled into components/schemas, $ref and any context annotation resolution into allOf
* SchemaResolution.ALL_OF_REF: bundled into components/schemas, $ref into allOf, context annotation resolution into root
*
* @return the schema resolution mode for this schema
*
*/
SchemaResolution schemaResolution() default SchemaResolution.AUTO;
}
Original file line number Diff line number Diff line change
Expand Up @@ -681,10 +681,10 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
io.swagger.v3.oas.annotations.media.Schema.RequiredMode requiredMode = resolveRequiredMode(propResolvedSchemaAnnotation);

Annotation[] ctxAnnotation31 = null;

Schema.SchemaResolution resolvedSchemaResolution = AnnotationsUtils.resolveSchemaResolution(this.schemaResolution, ctxSchema);
if (
Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) ||
Schema.SchemaResolution.ALL_OF_REF.equals(this.schemaResolution) ||
Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) ||
Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) ||
openapi31) {
List<Annotation> ctxAnnotations31List = new ArrayList<>();
if (annotations != null) {
Expand Down Expand Up @@ -712,8 +712,8 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
.components(annotatedType.getComponents())
.propertyName(propName);
if (
Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) ||
Schema.SchemaResolution.ALL_OF_REF.equals(this.schemaResolution) ||
Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) ||
Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) ||
openapi31) {
aType.ctxAnnotations(ctxAnnotation31);
} else {
Expand Down Expand Up @@ -746,7 +746,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
property = reResolvedProperty.get();
}

} else if (Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) || Schema.SchemaResolution.ALL_OF_REF.equals(this.schemaResolution)) {
} else if (Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) || Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution)) {
Optional<Schema> reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, null, schemaResolution, context);
if (reResolvedProperty.isPresent()) {
ctxProperty = reResolvedProperty.get();
Expand Down Expand Up @@ -795,20 +795,20 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
}

if (context.getDefinedModels().containsKey(pName)) {
if (Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) {
if (Schema.SchemaResolution.INLINE.equals(resolvedSchemaResolution)) {
property = context.getDefinedModels().get(pName);
} else if (Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) && ctxProperty != null) {
} else if (Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) && ctxProperty != null) {
property = new Schema()
.addAllOfItem(ctxProperty)
.addAllOfItem(new Schema().$ref(constructRef(pName)));
} else if (Schema.SchemaResolution.ALL_OF_REF.equals(this.schemaResolution) && ctxProperty != null) {
} else if (Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) && ctxProperty != null) {
property = ctxProperty.addAllOfItem(new Schema().$ref(constructRef(pName)));
} else {
property = new Schema().$ref(constructRef(pName));
}
property = clone(property);
// TODO: why is this needed? is it not handled before?
if (openapi31 || Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) {
if (openapi31 || Schema.SchemaResolution.INLINE.equals(resolvedSchemaResolution)) {
Optional<Schema> reResolvedProperty = AnnotationsUtils.getSchemaFromAnnotation(ctxSchema, annotatedType.getComponents(), null, openapi31, property, this.schemaResolution, context);
if (reResolvedProperty.isPresent()) {
property = reResolvedProperty.get();
Expand All @@ -821,11 +821,11 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
}
} else if (property.get$ref() != null) {
if (!openapi31) {
if (Schema.SchemaResolution.ALL_OF.equals(this.schemaResolution) && ctxProperty != null) {
if (Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) && ctxProperty != null) {
property = new Schema()
.addAllOfItem(ctxProperty)
.addAllOfItem(new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName()));
} else if (Schema.SchemaResolution.ALL_OF_REF.equals(this.schemaResolution) && ctxProperty != null) {
} else if (Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) && ctxProperty != null) {
property = ctxProperty
.addAllOfItem(new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName()));
} else {
Expand Down Expand Up @@ -1040,12 +1040,14 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
context.defineModel(name, model, annotatedType, null);
}

Schema.SchemaResolution resolvedSchemaResolution = AnnotationsUtils.resolveSchemaResolution(this.schemaResolution, resolvedSchemaAnnotation);

if (model != null && annotatedType.isResolveAsRef() &&
(isComposedSchema || isObjectSchema(model)) &&
StringUtils.isNotBlank(model.getName()))
{
if (context.getDefinedModels().containsKey(model.getName())) {
if (!Schema.SchemaResolution.INLINE.equals(this.schemaResolution)) {
if (!Schema.SchemaResolution.INLINE.equals(resolvedSchemaResolution)) {
model = new Schema().$ref(constructRef(model.getName()));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2658,6 +2658,15 @@ public Class<?> additionalPropertiesSchema() {
return patch.additionalPropertiesSchema();
}

/* We always want the patch to take precedence in schema resolution behavior */
@Override
public SchemaResolution schemaResolution() {
if (!patch.schemaResolution().equals(SchemaResolution.DEFAULT) || master.schemaResolution().equals(SchemaResolution.DEFAULT)) {
return patch.schemaResolution();
}
return master.schemaResolution();
}

};

return (io.swagger.v3.oas.annotations.media.Schema)schema;
Expand Down Expand Up @@ -2874,4 +2883,10 @@ public io.swagger.v3.oas.annotations.media.Schema[] prefixItems() {
return (io.swagger.v3.oas.annotations.media.ArraySchema)newArraySchema;
}

public static Schema.SchemaResolution resolveSchemaResolution(Schema.SchemaResolution globalSchemaResolution, io.swagger.v3.oas.annotations.media.Schema schemaAnnotation) {
if (schemaAnnotation != null && !io.swagger.v3.oas.annotations.media.Schema.SchemaResolution.AUTO.equals(schemaAnnotation.schemaResolution())) {
return Schema.SchemaResolution.valueOf(schemaAnnotation.schemaResolution().toString());
}
return globalSchemaResolution;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package io.swagger.v3.jaxrs2;

import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.jaxrs2.matchers.SerializationMatchers;
import io.swagger.v3.jaxrs2.schemaResolution.SchemaResolutionAnnotatedResource;
import io.swagger.v3.oas.integration.SwaggerConfiguration;
import io.swagger.v3.oas.models.OpenAPI;
import org.testng.annotations.Test;

public class SchemaResolutionAnnotationTest {

@Test
public void testSchemaResolutionAnnotation() {
ModelConverters.reset();
Reader reader = new Reader(new SwaggerConfiguration().openAPI(new OpenAPI()));
OpenAPI openAPI = reader.read(SchemaResolutionAnnotatedResource.class);
String yaml = "openapi: 3.0.1\n" +
"paths:\n" +
" /test/inlineSchemaFirst:\n" +
" get:\n" +
" operationId: inlineSchemaFirst\n" +
" responses:\n" +
" default:\n" +
" description: default response\n" +
" content:\n" +
" '*/*':\n" +
" schema:\n" +
" $ref: '#/components/schemas/InlineSchemaFirst'\n" +
" /test/inlineSchemaSecond:\n" +
" get:\n" +
" operationId: inlineSchemaSecond\n" +
" requestBody:\n" +
" content:\n" +
" '*/*':\n" +
" schema:\n" +
" type: object\n" +
" properties:\n" +
" foo:\n" +
" type: string\n" +
" propertySecond1:\n" +
" $ref: '#/components/schemas/InlineSchemaPropertySecond'\n" +
" property2:\n" +
" $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" +
" description: InlineSchemaSecond API\n" +
" responses:\n" +
" default:\n" +
" description: default response\n" +
" content:\n" +
" '*/*':\n" +
" schema:\n" +
" $ref: '#/components/schemas/InlineSchemaSecond'\n" +
"components:\n" +
" schemas:\n" +
" InlineSchemaFirst:\n" +
" type: object\n" +
" properties:\n" +
" property1:\n" +
" description: InlineSchemaFirst property 1\n" +
" nullable: true\n" +
" allOf:\n" +
" - $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" +
" property2:\n" +
" type: object\n" +
" properties:\n" +
" bar:\n" +
" type: string\n" +
" description: property\n" +
" example: example\n" +
" InlineSchemaPropertyFirst:\n" +
" type: object\n" +
" properties:\n" +
" bar:\n" +
" type: string\n" +
" description: property\n" +
" example: example\n" +
" InlineSchemaPropertySecond:\n" +
" type: object\n" +
" properties:\n" +
" bar:\n" +
" $ref: '#/components/schemas/InlineSchemaSimple'\n" +
" description: propertysecond\n" +
" nullable: true\n" +
" example: examplesecond\n" +
" InlineSchemaPropertySimple:\n" +
" type: object\n" +
" properties:\n" +
" bar:\n" +
" type: string\n" +
" description: property\n" +
" InlineSchemaSecond:\n" +
" type: object\n" +
" properties:\n" +
" foo:\n" +
" type: string\n" +
" propertySecond1:\n" +
" $ref: '#/components/schemas/InlineSchemaPropertySecond'\n" +
" property2:\n" +
" $ref: '#/components/schemas/InlineSchemaPropertyFirst'\n" +
" description: InlineSchemaSecond API\n" +
" InlineSchemaSimple:\n" +
" type: object\n" +
" properties:\n" +
" property1:\n" +
" type: object\n" +
" properties:\n" +
" bar:\n" +
" type: string\n" +
" description: property\n" +
" property2:\n" +
" description: property 2\n" +
" example: example\n" +
" allOf:\n" +
" - $ref: '#/components/schemas/InlineSchemaPropertySimple'\n";
SerializationMatchers.assertEqualsToYaml(openAPI, yaml);
ModelConverters.reset();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package io.swagger.v3.jaxrs2.schemaResolution;

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

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("test")
public class SchemaResolutionAnnotatedResource {

@GET
@Path("/inlineSchemaSecond")
public InlineSchemaSecond inlineSchemaSecond(@Schema(description = "InlineSchemaSecond API", schemaResolution = Schema.SchemaResolution.INLINE) InlineSchemaSecond inlineSchemaSecond) {
return null;
}
@GET
@Path("/inlineSchemaFirst")
public InlineSchemaFirst inlineSchemaFirst() {
return null;
}


static class InlineSchemaFirst {

// public String foo;

@Schema(description = "InlineSchemaFirst property 1", nullable = true, schemaResolution = Schema.SchemaResolution.ALL_OF_REF)
public InlineSchemaPropertyFirst property1;


private InlineSchemaPropertyFirst property2;

@Schema(description = " InlineSchemaFirst property 2", example = "example 2", schemaResolution = Schema.SchemaResolution.INLINE)
public InlineSchemaPropertyFirst getProperty2() {
return null;
}
}

static class InlineSchemaSecond {

public String foo;

@Schema(description = "InlineSchemaSecond property 1", nullable = true)
public InlineSchemaPropertySecond propertySecond1;


private InlineSchemaPropertyFirst property2;

@Schema(description = "InlineSchemaSecond property 2", example = "InlineSchemaSecond example 2")
public InlineSchemaPropertyFirst getProperty2() {
return null;
}
}

@Schema(description = "property", example = "example")
static class InlineSchemaPropertyFirst {
public String bar;
}

@Schema(description = "propertysecond", example = "examplesecond")
static class InlineSchemaPropertySecond {
public InlineSchemaSimple bar;
}

static class InlineSchemaSimple {

@Schema(description = "property 1", schemaResolution = Schema.SchemaResolution.INLINE)
public InlineSchemaPropertySimple property1;


private InlineSchemaPropertySimple property2;

@Schema(description = "property 2", example = "example", schemaResolution = Schema.SchemaResolution.ALL_OF_REF)
public InlineSchemaPropertySimple getProperty2() {
return null;
}
}

@Schema(description = "property")
static class InlineSchemaPropertySimple {
public String bar;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.swagger.v3.jaxrs2.schemaResolution;

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

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("test")
public class SchemaResolutionAnnotatedSimpleResource {

@GET
@Path("/inlineSchemaFirst")
public InlineSchemaFirst inlineSchemaFirst() {
return null;
}


static class InlineSchemaFirst {

private InlineSchemaPropertyFirst property2;

@Schema(description = " InlineSchemaFirst property 2", example = "example 2", schemaResolution = Schema.SchemaResolution.INLINE)
public InlineSchemaPropertyFirst getProperty2() {
return null;
}
}

@Schema(description = "property", example = "example")
static class InlineSchemaPropertyFirst {
// public String bar;
}
}

0 comments on commit dd028ca

Please sign in to comment.