Skip to content

Commit

Permalink
Merge pull request #1801 from Azquelt/schema-annotations-3.1
Browse files Browse the repository at this point in the history
Schema annotations 3.1
  • Loading branch information
MikeEdgar authored May 9, 2024
2 parents 21a6193 + 617b831 commit 7cdd1cc
Show file tree
Hide file tree
Showing 14 changed files with 881 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,18 @@ public class SchemaConstant {
public static final String PROP_PREFIX_ITEMS = "prefixItems";
public static final String PROP_CONTAINS = "contains";
public static final String PROP_PATTERN_PROPERTIES = "patternProperties";
public static final String PROP_IF_SCHEMA = "ifSchema";
public static final String PROP_THEN_SCHEMA = "thenSchema";
public static final String PROP_ELSE_SCHEMA = "elseSchema";
public static final String PROP_CONST_VALUE = "constValue";
public static final String PROP_COMMENT_FIELD = "comment";

// Only in SchemaFactory ?
public static final String PROP_REQUIRED_PROPERTIES = "requiredProperties";
public static final String PROP_PROPERTIES = "properties";
public static final String PROP_NOT = "not";
public static final String PROP_REGEX = "regex";
public static final String PROP_REQUIRES = "requires";

public static final String DIALECT_OAS31 = "https://spec.openapis.org/oas/3.1/dialect/base";
public static final String DIALECT_JSON_2020_12 = "https://json-schema.org/draft/2020-12/schema";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.smallrye.openapi.runtime.io.schema;

import static java.util.Arrays.asList;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -154,6 +156,12 @@ public static Schema readSchema(final AnnotationScannerContext context,
types -> readClassSchemas(context, types, false), defaults));
schema.setAllOf(SchemaFactory.<Type[], List<Schema>> readAttr(context, annotation, SchemaConstant.PROP_ALL_OF,
types -> readClassSchemas(context, types, true), defaults));
schema.setIfSchema(SchemaFactory.<Type, Schema> readAttr(context, annotation, SchemaConstant.PROP_IF_SCHEMA,
types -> readClassSchema(context, types, true), defaults));
schema.setThenSchema(SchemaFactory.<Type, Schema> readAttr(context, annotation, SchemaConstant.PROP_THEN_SCHEMA,
types -> readClassSchema(context, types, true), defaults));
schema.setElseSchema(SchemaFactory.<Type, Schema> readAttr(context, annotation, SchemaConstant.PROP_ELSE_SCHEMA,
types -> readClassSchema(context, types, true), defaults));
schema.setTitle(readAttr(context, annotation, SchemaConstant.PROP_TITLE, defaults));
schema.setMultipleOf(SchemaFactory.<Double, BigDecimal> readAttr(context, annotation, SchemaConstant.PROP_MULTIPLE_OF,
BigDecimal::valueOf, defaults));
Expand Down Expand Up @@ -191,14 +199,32 @@ public static Schema readSchema(final AnnotationScannerContext context,

final SchemaType type = readSchemaType(context, annotation, schema, defaults);
SchemaImpl.setType(schema, type);
schema.setExamples(wrapInList(parseSchemaAttr(context, annotation, SchemaConstant.PROP_EXAMPLE, defaults, type)));

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);
}
schema.setExamples(examples);
} else {
schema.setExamples(wrapInList(example));
}

schema.setDefaultValue(
parseSchemaAttr(context, annotation, SchemaConstant.PROP_DEFAULT_VALUE, defaults, type));
schema.setDiscriminator(context.io().discriminatorIO().read(annotation));
schema.setMaxItems(readAttr(context, annotation, SchemaConstant.PROP_MAX_ITEMS, defaults));
schema.setMinItems(readAttr(context, annotation, SchemaConstant.PROP_MIN_ITEMS, defaults));
schema.setUniqueItems(readAttr(context, annotation, SchemaConstant.PROP_UNIQUE_ITEMS, defaults));
schema.setExtensions(context.io().extensionIO().readExtensible(annotation));
schema.setComment(readAttr(context, annotation, SchemaConstant.PROP_COMMENT_FIELD, defaults));
schema.setConstValue(parseSchemaAttr(context, annotation, SchemaConstant.PROP_CONST_VALUE, defaults, type));

schema.setProperties(SchemaFactory.<AnnotationInstance[], Map<String, Schema>> readAttr(context, annotation,
SchemaConstant.PROP_PROPERTIES, properties -> {
Expand All @@ -215,17 +241,35 @@ public static Schema readSchema(final AnnotationScannerContext context,
return propertySchemas;
}, defaults));

Type additionalProperties = readAttr(context, annotation, "additionalProperties", defaults);
schema.setAdditionalPropertiesSchema(
SchemaFactory.<Type, Schema> readAttr(context, annotation, SchemaConstant.PROP_ADDITIONAL_PROPERTIES,
types -> readClassSchema(context, types, true), defaults));

if (additionalProperties != null) {
if (additionalProperties.name().equals(SchemaConstant.DOTNAME_TRUE_SCHEMA)) {
schema.setAdditionalPropertiesSchema(new SchemaImpl().booleanSchema(Boolean.TRUE));
} else if (additionalProperties.name().equals(SchemaConstant.DOTNAME_FALSE_SCHEMA)) {
schema.setAdditionalPropertiesSchema(new SchemaImpl().booleanSchema(Boolean.FALSE));
} else {
schema.setAdditionalPropertiesSchema(readClassSchema(context, additionalProperties, true));
}
}
schema.setDependentRequired(SchemaFactory.<AnnotationInstance[], Map<String, List<String>>> readAttr(context,
annotation, SchemaConstant.PROP_DEPENDENT_REQUIRED,
annos -> readDependentRequired(context, annos), defaults));

schema.setDependentSchemas(SchemaFactory.<AnnotationInstance[], Map<String, Schema>> readAttr(context, annotation,
SchemaConstant.PROP_DEPENDENT_SCHEMAS,
annos -> readDependentSchemas(context, annos), defaults));

schema.setContentEncoding(readAttr(context, annotation, SchemaConstant.PROP_CONTENT_ENCODING, defaults));
schema.setContentMediaType(readAttr(context, annotation, SchemaConstant.PROP_CONTENT_MEDIA_TYPE, defaults));
schema.setContentSchema(SchemaFactory.<Type, Schema> readAttr(context, annotation, SchemaConstant.PROP_CONTENT_SCHEMA,
types -> readClassSchema(context, types, true), defaults));

schema.setContains(SchemaFactory.<Type, Schema> readAttr(context, annotation, SchemaConstant.PROP_CONTAINS,
types -> readClassSchema(context, types, true), defaults));
schema.setMaxContains(readAttr(context, annotation, SchemaConstant.PROP_MAX_CONTAINS, defaults));
schema.setMinContains(readAttr(context, annotation, SchemaConstant.PROP_MIN_CONTAINS, defaults));
schema.setPrefixItems(
SchemaFactory.<Type[], List<Schema>> readAttr(context, annotation, SchemaConstant.PROP_PREFIX_ITEMS,
types -> readClassSchemas(context, types, true), defaults));
schema.setPropertyNames(SchemaFactory.<Type, Schema> readAttr(context, annotation, SchemaConstant.PROP_PROPERTY_NAMES,
types -> readClassSchema(context, types, true), defaults));
schema.setPatternProperties(SchemaFactory.<AnnotationInstance[], Map<String, Schema>> readAttr(context, annotation,
SchemaConstant.PROP_PATTERN_PROPERTIES,
annos -> readPatternProperties(context, annos), defaults));

List<Object> enumeration = readAttr(context, annotation, SchemaConstant.PROP_ENUMERATION, (Object[] values) -> {
List<Object> parsed = new ArrayList<>(values.length);
Expand Down Expand Up @@ -467,7 +511,11 @@ static Schema readClassSchema(final AnnotationScannerContext context, Type type,
return null;
}
Schema schema;
if (type.kind() == Type.Kind.ARRAY) {
if (type.name().equals(SchemaConstant.DOTNAME_TRUE_SCHEMA)) {
schema = new SchemaImpl().booleanSchema(true);
} else if (type.name().equals(SchemaConstant.DOTNAME_FALSE_SCHEMA)) {
schema = new SchemaImpl().booleanSchema(false);
} else if (type.kind() == Type.Kind.ARRAY) {
schema = new SchemaImpl().addType(SchemaType.ARRAY);
ArrayType array = type.asArrayType();
int dimensions = array.dimensions();
Expand Down Expand Up @@ -760,4 +808,56 @@ private static BigDecimal tolerantParseBigDecimal(String value) {
return null;
}
}

private static Map<String, List<String>> readDependentRequired(AnnotationScannerContext context,
AnnotationInstance[] requireds) {
if (requireds == null || requireds.length == 0) {
return null;
}

Map<String, List<String>> result = new LinkedHashMap<>();
for (AnnotationInstance required : requireds) {
String name = context.annotations().value(required, SchemaConstant.PROP_NAME);
String[] requires = context.annotations().value(required, SchemaConstant.PROP_REQUIRES);
result.put(name, new ArrayList<>(asList(requires)));
}

return result;
}

private static Map<String, Schema> readDependentSchemas(AnnotationScannerContext context,
AnnotationInstance[] dependentSchemas) {
if (dependentSchemas == null || dependentSchemas.length == 0) {
return null;
}

Map<String, Schema> result = new LinkedHashMap<>();
for (AnnotationInstance dependentSchema : dependentSchemas) {
String name = context.annotations().value(dependentSchema, SchemaConstant.PROP_NAME);
Type schemaClass = context.annotations().value(dependentSchema, SchemaConstant.PROP_SCHEMA);
Schema schema = readClassSchema(context, schemaClass, true);
if (schema != null) {
result.put(name, schema);
}
}
return result;
}

private static Map<String, Schema> readPatternProperties(AnnotationScannerContext context,
AnnotationInstance[] patternProperties) {
if (patternProperties == null || patternProperties.length == 0) {
return null;
}

Map<String, Schema> result = new LinkedHashMap<>();
for (AnnotationInstance patternProperty : patternProperties) {
String regex = context.annotations().value(patternProperty, SchemaConstant.PROP_REGEX);
Type schemaClass = context.annotations().value(patternProperty, SchemaConstant.PROP_SCHEMA);
Schema schema = readClassSchema(context, schemaClass, true);
if (schema != null) {
result.put(regex, schema);
}
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,13 @@ public static void assertJsonEquals(String expectedResource, OpenAPI actual) thr
}

public static void assertJsonEquals(URL expectedResourceUrl, OpenAPI actual) throws JSONException, IOException {
JSONAssert.assertEquals(loadResource(expectedResourceUrl), toJSON(actual), true);
String json = toJSON(actual);
try {
JSONAssert.assertEquals(loadResource(expectedResourceUrl), json, true);
} catch (AssertionError e) {
// If the JSON did not match, we want to add the serialized version to the end
throw new AssertionError(e.getMessage() + "\nFull result:\n" + json, e);
}
}

public static OpenAPI scan(Class<?>... classes) {
Expand Down
Loading

0 comments on commit 7cdd1cc

Please sign in to comment.