Skip to content

Commit

Permalink
Add config to keep example/examples as distinct attributes (#2099)
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Edgar <[email protected]>
  • Loading branch information
MikeEdgar authored Dec 8, 2024
1 parent 955121c commit eebf353
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 25 deletions.
8 changes: 8 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,11 @@ Set to `FAIL` to abort in case of duplicate operationIds, set to `WARN` to log w
mp.openapi.extensions.smallrye.maximumStaticFileSize
----
Set this value in order to change the maximum threshold for processed static files, when generating model from them. If not set, it will default to 3 MB.

* Merge Schema Examples
+
[source%nowrap]
----
mp.openapi.extensions.smallrye.merge-schema-examples
----
Set this boolean value to disable the merging of the deprecated `@Schema` `example` property into the `examples` array introduced in OAS 3.1.0. If not set, it will default to `true` the deprecated `example` will be mapped to the `examples` array in the OpenAPI model.
4 changes: 4 additions & 0 deletions core/src/main/java/io/smallrye/openapi/api/OpenApiConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ default boolean removeUnusedSchemas() {
return getConfigValue(SmallRyeOASConfig.SMALLRYE_REMOVE_UNUSED_SCHEMAS, Boolean.class, () -> Boolean.FALSE);
}

default boolean mergeSchemaExamples() {
return getConfigValue(SmallRyeOASConfig.SMALLRYE_MERGE_SCHEMA_EXAMPLES, Boolean.class, () -> Boolean.TRUE);
}

default Integer getMaximumStaticFileSize() {
return getConfigValue(SmallRyeOASConfig.MAXIMUM_STATIC_FILE_SIZE, Integer.class,
() -> MAXIMUM_STATIC_FILE_SIZE_DEFAULT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ private SmallRyeOASConfig() {
private static final String SUFFIX_PROPERTY_NAMING_STRATEGY = "property-naming-strategy";
private static final String SUFFIX_SORTED_PROPERTIES_ENABLE = "sorted-properties.enable";
private static final String SUFFIX_REMOVE_UNUSED_SCHEMAS_ENABLE = "remove-unused-schemas.enable";
private static final String SUFFIX_MERGE_SCHEMA_EXAMPLES = "merge-schema-examples";
private static final String SMALLRYE_PREFIX = OASConfig.EXTENSIONS_PREFIX + VENDOR_NAME;

public static final String SMALLRYE_SCAN_DEPENDENCIES_DISABLE = SMALLRYE_PREFIX + SUFFIX_SCAN_DEPENDENCIES_DISABLE;
Expand All @@ -49,6 +50,8 @@ private SmallRyeOASConfig() {

public static final String SMALLRYE_REMOVE_UNUSED_SCHEMAS = SMALLRYE_PREFIX + SUFFIX_REMOVE_UNUSED_SCHEMAS_ENABLE;

public static final String SMALLRYE_MERGE_SCHEMA_EXAMPLES = SMALLRYE_PREFIX + SUFFIX_MERGE_SCHEMA_EXAMPLES;

public static final String SCAN_PROFILES = SMALLRYE_PREFIX + "scan.profiles";

public static final String SCAN_EXCLUDE_PROFILES = SMALLRYE_PREFIX + "scan.exclude.profiles";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,19 +208,30 @@ public static Schema readSchema(final AnnotationScannerContext context,
SchemaSupport.setType(schema, readSchemaType(context, annotation, schema, defaults));

Object example = parseSchemaAttr(context, annotation, SchemaConstant.PROP_EXAMPLE, defaults, type);

List<Object> examples = SchemaFactory.<String[], List<Object>> readAttr(context, annotation,
SchemaConstant.PROP_EXAMPLES,
egs -> Arrays.stream(egs)
.map(e -> parseValue(context, e, type))
.collect(Collectors.toCollection(ArrayList::new)),
defaults);
if (examples != null) {
if (example != null) {
examples.add(example);
/*
* Do not allow default examples to be added if the annotation has specified an example
* with the deprecated property.
*/
example != null ? Collections.emptyMap() : defaults);

if (context.getConfig().mergeSchemaExamples()) {
if (examples != null) {
if (example != null) {
examples.add(example);
}
schema.setExamples(examples);
} else {
schema.setExamples(wrapInList(example));
}
schema.setExamples(examples);
} else {
schema.setExamples(wrapInList(example));
schema.setExample(example);
schema.setExamples(examples);
}

schema.setDefaultValue(
Expand Down Expand Up @@ -579,7 +590,7 @@ public static Schema typeToSchema(final AnnotationScannerContext context, Type t
schema = typeToSchema(context, currentScanner.get().unwrapType(type), null);
} else if (TypeUtil.isTerminalType(type)) {
schema = OASFactory.createSchema();
TypeUtil.applyTypeAttributes(type, schema);
TypeUtil.applyTypeAttributes(type, schema, schemaAnnotation);
schema = schemaRegistration(context, type, schema);
} else if (type.kind() == Type.Kind.ARRAY) {
schema = OASFactory.createSchema().addType(SchemaType.ARRAY);
Expand Down Expand Up @@ -647,7 +658,7 @@ public static Schema enumToSchema(final AnnotationScannerContext context, Type e

enumSchema = readSchema(context, enumSchema, schemaAnnotation, enumKlazz, true, defaults);
} else {
TypeUtil.applyTypeAttributes(enumValueType, enumSchema);
TypeUtil.applyTypeAttributes(enumValueType, enumSchema, null);
enumSchema.setEnumeration(enumeration);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public static Schema process(final AnnotationScannerContext context, Type type)
*/
public static Schema process(PrimitiveType primitive) {
Schema primitiveSchema = OASFactory.createSchema();
TypeUtil.applyTypeAttributes(primitive, primitiveSchema);
TypeUtil.applyTypeAttributes(primitive, primitiveSchema, null);
return primitiveSchema;
}

Expand All @@ -197,7 +197,7 @@ Schema process() {
// If top level item is simple
if (TypeUtil.isTerminalType(rootClassType)) {
Schema simpleSchema = OASFactory.createSchema();
TypeUtil.applyTypeAttributes(rootClassType, simpleSchema);
TypeUtil.applyTypeAttributes(rootClassType, simpleSchema, null);
return simpleSchema;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ Schema processField() {

// Set any default values that apply to the type schema as a result of the TypeProcessor
if (!TypeUtil.isTypeOverridden(context, fieldType, schemaAnnotation)) {
TypeUtil.applyTypeAttributes(fieldType, initTypeSchema);
TypeUtil.applyTypeAttributes(fieldType, initTypeSchema, schemaAnnotation);
}

// The registeredTypeSchema will be a reference to typeSchema if registration occurs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ private Type readArrayType(ArrayType arrayType, Schema arraySchema) {
}

// Only use component (excludes the special name formatting for arrays).
TypeUtil.applyTypeAttributes(componentType, itemSchema);
TypeUtil.applyTypeAttributes(componentType, itemSchema, null);

if (!isTerminalType(componentType) && index.containsClass(componentType)) {
// If it's not a terminal type, then push for later inspection.
Expand Down Expand Up @@ -290,7 +290,7 @@ private Schema readGenericValueType(Type valueType) {
Schema valueSchema = OASFactory.createSchema();

if (isTerminalType(valueType)) {
TypeUtil.applyTypeAttributes(valueType, valueSchema);
TypeUtil.applyTypeAttributes(valueType, valueSchema, null);
} else if (valueType.kind() == Kind.PARAMETERIZED_TYPE) {
readParameterizedType(valueType.asParameterizedType(), valueSchema);
} else {
Expand Down Expand Up @@ -346,7 +346,7 @@ private Type resolveTypeVariable(Schema schema, Type fieldType, boolean pushToSt

if (isTerminalType(resolvedType) || !index.containsClass(resolvedType)) {
DataObjectLogging.logger.terminalType(resolvedType);
TypeUtil.applyTypeAttributes(resolvedType, schema);
TypeUtil.applyTypeAttributes(resolvedType, schema, null);
} else if (pushToStack) {
// Add resolved type to stack.
pushToStack(resolvedType, schema);
Expand Down
17 changes: 6 additions & 11 deletions core/src/main/java/io/smallrye/openapi/runtime/util/TypeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -413,22 +413,17 @@ public static boolean isTypeOverridden(AnnotationScannerContext context, Type cl
* @param classType the type
* @param schema a writable schema to be updated with the type's default schema attributes
*/
public static void applyTypeAttributes(Type classType, Schema schema) {
@SuppressWarnings("unchecked")
public static void applyTypeAttributes(Type classType, Schema schema, AnnotationInstance schemaAnnotation) {
Map<String, Object> properties = getTypeAttributes(classType);

SchemaSupport.setType(schema, (SchemaType) properties.get(SchemaConstant.PROP_TYPE));
schema.setFormat((String) properties.get(SchemaConstant.PROP_FORMAT));
schema.setPattern((String) properties.get(SchemaConstant.PROP_PATTERN));
schema.setExamples(wrapInList(properties.get(SchemaConstant.PROP_EXAMPLE)));
schema.setExternalDocs((ExternalDocumentation) properties.get(SchemaConstant.PROP_EXTERNAL_DOCS));
}

private static <E> List<E> wrapInList(E item) {
if (item == null) {
return null;
} else {
return Collections.singletonList(item);
if (schemaAnnotation == null || schemaAnnotation.value(SchemaConstant.PROP_EXAMPLE) == null) {
schema.setExamples((List<Object>) properties.get(SchemaConstant.PROP_EXAMPLES));
}
schema.setExternalDocs((ExternalDocumentation) properties.get(SchemaConstant.PROP_EXTERNAL_DOCS));
}

/**
Expand Down Expand Up @@ -837,7 +832,7 @@ Builder pattern(String pattern) {
}

Builder example(Object example) {
properties.put(SchemaConstant.PROP_EXAMPLE, example);
properties.put(SchemaConstant.PROP_EXAMPLES, Collections.singletonList(example));
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -766,6 +767,7 @@ public String getWo() {

@Test
void testExceptionalExampleParsing() throws IOException, JSONException {
// All example properties convert to `examples` in the model by default
@Schema(name = "Bean")
class Bean {
@Schema(example = "{ Looks like object, but invalid }")
Expand Down Expand Up @@ -820,4 +822,23 @@ public void getProperty2(java.net.URL getProperty2) {

assertJsonEquals("components.schemas.javabean-property-prefix.json", Bean.class, java.net.URL.class);
}

@Test
void testExampleNotMerged() throws IOException, JSONException {
@Schema(name = "Bean")
class DTO {

@Schema(example = "Hello World") // NOSONAR
String name;

@Schema(example = "14:45:30.987654321") // NOSONAR
LocalTime localTime;

@Schema(example = "14:45:30.999999999", nullable = true) // NOSONAR
LocalTime localTimeNullable;
}

assertJsonEquals("components.schemas.example-not-merged.json",
scan(config(SmallRyeOASConfig.SMALLRYE_MERGE_SCHEMA_EXAMPLES, "false"), null, new Class[] { DTO.class }));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"openapi" : "3.1.0",
"components" : {
"schemas" : {
"Bean" : {
"type" : "object",
"properties" : {
"name" : {
"type" : "string",
"example" : "Hello World"
},
"localTime" : {
"type" : "string",
"format" : "local-time",
"externalDocs" : {
"description" : "As defined by 'partial-time' in RFC3339",
"url" : "https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6"
},
"example" : "14:45:30.987654321"
},
"localTimeNullable" : {
"type" : [ "string", "null" ],
"format" : "local-time",
"example" : "14:45:30.999999999",
"externalDocs" : {
"description" : "As defined by 'partial-time' in RFC3339",
"url" : "https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6"
}
}
}
}
}
}
}

0 comments on commit eebf353

Please sign in to comment.