Skip to content

Commit

Permalink
Support Schema additionalProperties via annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeEdgar committed Mar 18, 2022
1 parent 685c407 commit 65d72f0
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 2 deletions.
Expand Up @@ -401,6 +401,32 @@
*/
SchemaProperty[] properties() default {};

/**
* Provides a Java class as implementation for additional properties that may be present in instances of this
* schema.
*
* <p>
* If no additional properties are allowed, the value of this property should be set to
* {@link org.eclipse.microprofile.openapi.models.media.Schema.FalseSchema FalseSchema.class} which will be rendered
* as boolean <code>false</code> in the resulting OpenAPI document.
*
* <p>
* The default value {@link org.eclipse.microprofile.openapi.models.media.Schema.TrueSchema TrueSchema.class}
* indicates that additional properties are allowed for object-type schemas.
*
* <p>
* Implementations MAY ignore this property if this schema's {@linkplain #type() type} is not
* {@linkplain SchemaType#OBJECT OBJECT}, either explicitly or as derived by the placement of the annotation.
*
* @return a class that describes the allowable schema for additional properties not explicitly defined
*
* @since 3.1
*
* @see org.eclipse.microprofile.openapi.models.media.Schema.TrueSchema
* @see org.eclipse.microprofile.openapi.models.media.Schema.FalseSchema
*/
Class<?> additionalProperties() default org.eclipse.microprofile.openapi.models.media.Schema.TrueSchema.class;

/**
* List of extensions to be added to the {@link org.eclipse.microprofile.openapi.models.media.Schema Schema} model
* corresponding to the containing annotation.
Expand Down
Expand Up @@ -61,6 +61,37 @@ public String toString() {
}
}

/**
* Special case used to indicate that a {@literal @}{@link Schema} should be rendered as a literal boolean
* <code>true</code>. A value of <code>true</code> declares that the schema will match any instance, equivalent to
* <code>{}</code>.
*
* @apiNote As of version 3.1, implementations are only required to support the use of this interface in
* {@literal @}{@link org.eclipse.microprofile.openapi.annotations.media.Schema#additionalProperties()
* Schema.additionalProperties} to render a value for <code>additionalProperties</code> in an object
* schema. Future releases of MicroProfile OpenAPI may require more general support of this special case
* schema.
*
* @since 3.1
*/
interface TrueSchema extends Schema {
}

/**
* Special case used to indicate that a {@literal @}{@link Schema} should be rendered as a literal boolean
* <code>false</code>. A value of <code>false</code> declares that the schema will match no instances.
*
* @apiNote As of version 3.1, implementations are only required to support the use of this interface in
* {@literal @}{@link org.eclipse.microprofile.openapi.annotations.media.Schema#additionalProperties()
* Schema.additionalProperties} to render a value for <code>additionalProperties</code> in an object
* schema. Future releases of MicroProfile OpenAPI may require more general support of this special case
* schema.
*
* @since 3.1
*/
interface FalseSchema extends Schema {
}

/**
* Returns the discriminator property from this Schema instance.
*
Expand Down Expand Up @@ -1192,4 +1223,4 @@ default Schema oneOf(List<Schema> oneOf) {
*/
void removeOneOf(Schema oneOf);

}
}
Expand Up @@ -30,6 +30,6 @@
* </pre>
*/

@org.osgi.annotation.versioning.Version("2.0")
@org.osgi.annotation.versioning.Version("2.1")
@org.osgi.annotation.versioning.ProviderType
package org.eclipse.microprofile.openapi.models.media;
Expand Up @@ -14,7 +14,9 @@
package org.eclipse.microprofile.openapi.apps.airlines.model;

import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.models.media.Schema.TrueSchema;

@Schema(additionalProperties = TrueSchema.class)
public class Airline {

@Schema(required = true, example = "Acme Air")
Expand Down
Expand Up @@ -14,7 +14,9 @@
package org.eclipse.microprofile.openapi.apps.airlines.model;

import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.models.media.Schema.FalseSchema;

@Schema(additionalProperties = FalseSchema.class)
public class CreditCard {

@Schema(required = true, example = "VISA")
Expand Down
Expand Up @@ -15,6 +15,7 @@

import org.eclipse.microprofile.openapi.annotations.media.Schema;

@Schema(additionalProperties = String.class)
public class Flight {

@Schema(required = true)
Expand Down
Expand Up @@ -1021,4 +1021,44 @@ public void testExceptionMappers(String type) {
vr.body("paths.'/user/{id}'.get.responses.'404'.content.'application/json'.schema", notNullValue());
}

@RunAsClient
@Test(dataProvider = "formatProvider")
public void testAdditionalPropertiesFalse(String type) {
ValidatableResponse vr = callEndpoint(type);

String responseSchema =
dereference(vr, "paths.'/bookings/{id}'.get.responses.'200'", "content.'application/json'.schema");
vr.body(responseSchema, notNullValue());
String ccSchema = dereference(vr, responseSchema, "properties.creditCard");

vr.body(ccSchema + ".additionalProperties", equalTo(false));

}

@RunAsClient
@Test(dataProvider = "formatProvider")
public void testAdditionalPropertiesTrue(String type) {
ValidatableResponse vr = callEndpoint(type);

String responseSchema =
dereference(vr, "paths.'/bookings/{id}'.get.responses.'200'", "content.'application/json'.schema");

vr.body(responseSchema, notNullValue());

String airlineSchema = dereference(vr, responseSchema, "properties.returningFlight", "properties.airline");
vr.body(airlineSchema + ".additionalProperties", equalTo(true));
}

@RunAsClient
@Test(dataProvider = "formatProvider")
public void testAdditionalPropertiesTypeString(String type) {
ValidatableResponse vr = callEndpoint(type);

String responseSchema =
dereference(vr, "paths.'/bookings/{id}'.get.responses.'200'", "content.'application/json'.schema");
vr.body(responseSchema, notNullValue());
String flightSchema = dereference(vr, responseSchema, "properties.returningFlight");

vr.body(flightSchema + ".additionalProperties.type", equalTo("string"));
}
}
Expand Up @@ -111,6 +111,42 @@ public static String dereference(ValidatableResponse vr, String path) {
}
}

public static String dereference(ValidatableResponse vr, String... paths) {
ExtractableResponse<Response> response = vr.extract();
String context = "";
StringBuilder lookup = new StringBuilder();
StringBuilder absolutePath = new StringBuilder();

for (String path : paths) {
lookup.setLength(0);

if (!context.isEmpty()) {
lookup.append(context);
lookup.append('.');
}

lookup.append(path);
lookup.append(".$ref");

String ref = response.path(lookup.toString());

if (ref != null) {
absolutePath.setLength(0);
absolutePath.append(ref.replaceFirst("^#/?", "").replace('/', '.'));
} else {
// No $ref, keep appending
if (absolutePath.length() > 0) {
absolutePath.append('.');
}
absolutePath.append(path);
}

context = absolutePath.toString();
}

return absolutePath.toString();
}

@DataProvider(name = "formatProvider")
public Object[][] provide() {
return new Object[][]{{"JSON"}, {"YAML"}};
Expand Down

0 comments on commit 65d72f0

Please sign in to comment.