diff --git a/api/pom.xml b/api/pom.xml index 466875d4f..30e82d087 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -20,7 +20,7 @@ org.eclipse.microprofile.openapi microprofile-openapi-parent - 3.2-SNAPSHOT + 4.0-SNAPSHOT microprofile-openapi-api diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/media/Schema.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/media/Schema.java index 7af94377d..6a7e45bb8 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/models/media/Schema.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/media/Schema.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; +import org.eclipse.microprofile.openapi.OASFactory; import org.eclipse.microprofile.openapi.models.Constructible; import org.eclipse.microprofile.openapi.models.Extensible; import org.eclipse.microprofile.openapi.models.ExternalDocumentation; @@ -47,7 +48,8 @@ public interface Schema extends Extensible, Constructible, Reference required) { void removeRequired(String required); /** - * Returns the type property from this Schema. + * Returns the type property of this Schema instance. Defines the types which are valid. * - * @return the type used in this Schema. Default value must be null - **/ - SchemaType getType(); + * @return a copy List (potentially immutable) of the allowed types + */ + List getType(); /** - * Sets the type used by this Schema to the string given. + * Sets the type property of this Schema instance. Defines the types which are valid. * - * @param type - * the type used by this Schema or null for reference schemas + * @param types + * a list of the allowed types */ - void setType(SchemaType type); + void setType(List types); /** - * Sets the type used by this Schema to the string given. + * Sets the type property of this Schema instance. Defines the types which are valid. * - * @param type - * the type used by this Schema or null for reference schemas - * @return the current Schema instance + * @param types + * a list of the allowed types + * @return current Schema instance + * @since "4.0" */ - default Schema type(SchemaType type) { - setType(type); + default Schema type(List types) { + setType(types); return this; } + /** + * Adds a type to the type list. + * + * @param type + * the type to add to the type list + * @return current Schema instance + * @since "4.0" + */ + Schema addType(SchemaType type); + + /** + * Removes a type from the type list. + * + * @param type + * the type to remove from the type list + * @since "4.0" + */ + void removeType(SchemaType type); + /** * Returns a Schema which describes properties not allowed in objects defined by the current schema. * @@ -727,7 +751,9 @@ default Schema properties(Map properties) { * * * @return this Schema's additionalProperties property (as {@link Boolean}) + * @deprecated use {@link #getAdditionalPropertiesSchema()} which may return a boolean-valued schema */ + @Deprecated(since = "4.0") Boolean getAdditionalPropertiesBoolean(); /** @@ -749,7 +775,9 @@ default Schema properties(Map properties) { * * @param additionalProperties * a Schema which defines additional properties + * @deprecated use {@link #setAdditionalPropertiesSchema(Schema)} with a boolean-valued schema */ + @Deprecated(since = "4.0") void setAdditionalPropertiesBoolean(Boolean additionalProperties); /** @@ -775,7 +803,9 @@ default Schema additionalPropertiesSchema(Schema additionalProperties) { * @param additionalProperties * a Schema which defines additional properties * @return the current Schema instance + * @deprecated use {@link #additionalPropertiesSchema(Schema)} with a boolean-valued schema */ + @Deprecated(since = "4.0") default Schema additionalPropertiesBoolean(Boolean additionalProperties) { setAdditionalPropertiesBoolean(additionalProperties); return this; @@ -838,33 +868,6 @@ default Schema format(String format) { return this; } - /** - * Returns the nullable property from this Schema instance which indicates whether null is a valid value. - * - * @return the nullable property - **/ - Boolean getNullable(); - - /** - * Sets the nullable property of this Schema instance. Specify true if this Schema will allow null values. - * - * @param nullable - * a boolean value indicating this Schema allows a null value. - */ - void setNullable(Boolean nullable); - - /** - * Sets the nullable property of this Schema instance. Specify true if this Schema will allow null values. - * - * @param nullable - * a boolean value indicating this Schema allows a null value. - * @return the current Schema instance - */ - default Schema nullable(Boolean nullable) { - setNullable(nullable); - return this; - } - /** * Returns the readOnly property from this Schema instance. * @@ -873,18 +876,18 @@ default Schema nullable(Boolean nullable) { Boolean getReadOnly(); /** - * Sets the readOnly property of this Schema. Only valid when the Schema is the property in an object. + * Sets the readOnly property of this Schema. * * @param readOnly - * true indicates the Schema should not be sent as part of a request message + * {@code true} indicates the Schema should not be sent as part of a request message */ void setReadOnly(Boolean readOnly); /** - * Sets the readOnly property of this Schema. Only valid when the Schema is the property in an object. + * Sets the readOnly property of this Schema. * * @param readOnly - * true indicates the Schema should not be sent as part of a request message + * {@code true} indicates the Schema should not be sent as part of a request message * @return the current Schema instance */ default Schema readOnly(Boolean readOnly) { @@ -900,18 +903,18 @@ default Schema readOnly(Boolean readOnly) { Boolean getWriteOnly(); /** - * Sets the writeOnly property of this Schema. Only valid when the Schema is the property in an object. + * Sets the writeOnly property of this Schema. * * @param writeOnly - * true indicates the Schema should not be sent as part of a response message + * {@code true} indicates the Schema should not be sent as part of a response message */ void setWriteOnly(Boolean writeOnly); /** - * Sets the writeOnly property of this Schema. Only valid when the Schema is the property in an object. + * Sets the writeOnly property of this Schema. * * @param writeOnly - * true indicates the Schema should not be sent as part of a response message + * {@code true} indicates the Schema should not be sent as part of a response message * @return the current Schema instance */ default Schema writeOnly(Boolean writeOnly) { @@ -923,7 +926,9 @@ default Schema writeOnly(Boolean writeOnly) { * Returns the example property from this Schema instance. * * @return an object which is an example of an instance of this Schema - **/ + * @deprecated use {@link #getExamples()} + */ + @Deprecated(since = "4.0") Object getExample(); /** @@ -932,7 +937,9 @@ default Schema writeOnly(Boolean writeOnly) { * * @param example * an object which is an instance of this Schema + * @deprecated use {@link #setExamples(List)} */ + @Deprecated(since = "4.0") void setExample(Object example); /** @@ -942,7 +949,9 @@ default Schema writeOnly(Boolean writeOnly) { * @param example * an object which is an instance of this Schema * @return the current Schema instance + * @deprecated use {@link #examples(List)} */ + @Deprecated(since = "4.0") default Schema example(Object example) { setExample(example); return this; @@ -1192,4 +1201,930 @@ default Schema oneOf(List oneOf) { */ void removeOneOf(Schema oneOf); + /** + * Returns the schema dialect in use. This is the value of the {@code $schema} property. + * + * @return the schema dialect name, or {@code null} for the default + * @since 4.0 + */ + String getSchemaDialect(); + + /** + * Sets the schema dialect in use. This is the value of the {@code $schema} property. + * + * @param schemaDialect + * the schema dialect name, or {@code null} for the default + * @since 4.0 + */ + void setSchemaDialect(String schemaDialect); + + /** + * Sets the schema dialect in use. This is the value of the {@code $schema} property. + * + * @param schemaDialect + * the schema dialect name, or {@code null} for the default + * @return the current Schema instance + * @since 4.0 + */ + default Schema schemaDialect(String schemaDialect) { + setSchemaDialect(schemaDialect); + return this; + } + + /** + * Returns the comment to be included in the {@code $comment} property of the schema. + * + * @return the comment, or {@code null} if no comment is set + * @since 4.0 + */ + String getComment(); + + /** + * Sets the comment to be included in the {@code $comment} property of the schema. + * + * @param comment + * the comment, or {@code null} to remove any comment + * @since 4.0 + */ + void setComment(String comment); + + /** + * Sets the comment to be included in the {@code $comment} property of the schema. + * + * @param comment + * the comment, or {@code null} to remove any comment + * @return the current Schema instance + * @since 4.0 + */ + default Schema comment(String comment) { + setComment(comment); + return this; + } + + /** + * Returns the "if" schema. If an object is valid against the "if" schema, then it must also be valid against the + * "then" schema, otherwise it must be valid against the "else" schema. + * + * @return the if schema + * @since 4.0 + */ + Schema getIfSchema(); + + /** + * Sets the "if" schema. If an object is valid against the "if" schema, then it must also be valid against the + * "then" schema, otherwise it must be valid against the "else" schema. + * + * @param ifSchema + * the if schema + * @since 4.0 + */ + void setIfSchema(Schema ifSchema); + + /** + * Sets the "if" schema. If an object is valid against the "if" schema, then it must also be valid against the + * "then" schema, otherwise it must be valid against the "else" schema. + * + * @param ifSchema + * the if schema + * @return the current Schema instance + * @since 4.0 + */ + default Schema ifSchema(Schema ifSchema) { + setIfSchema(ifSchema); + return this; + } + + /** + * Returns the "then" schema. If an object is valid against the "if" schema, then it must also be valid against the + * "then" schema. + * + * @return the then schema + * @since 4.0 + */ + Schema getThenSchema(); + + /** + * Sets the "then" schema. If an object is valid against the "if" schema, then it must also be valid against the + * "then" schema. + * + * @param thenSchema + * the then schema + * @since 4.0 + */ + void setThenSchema(Schema thenSchema); + + /** + * Sets the "then" schema. If an object is valid against the "if" schema, then it must also be valid against the + * "then" schema. + * + * @param thenSchema + * the then schema + * @return the current Schema instance + * @since 4.0 + */ + default Schema thenSchema(Schema thenSchema) { + setThenSchema(thenSchema); + return this; + } + + /** + * Returns the "else" schema. If an object is not valid against the "if" schema, then it must be valid against the + * "else" schema. + * + * @return the else schema + * @since 4.0 + */ + Schema getElseSchema(); + + /** + * Sets the "else" schema. If an object is not valid against the "if" schema, then it must be valid against the + * "else" schema. + * + * @param elseSchema + * the else schema + * @since 4.0 + */ + void setElseSchema(Schema elseSchema); + + /** + * Sets the "else" schema. If an object is not valid against the "if" schema, then it must be valid against the + * "else" schema. + * + * @param elseSchema + * the else schema + * @return the current Schema instance + * @since 4.0 + */ + default Schema elseSchema(Schema elseSchema) { + setElseSchema(elseSchema); + return this; + } + + /** + * Returns the dependentSchemas property of this Schema instance. + *

+ * For each name and property pair in the map, if the object contains a property with the given name, it must match + * the corresponding schema. + * + * @return a copy Map (potentially immutable) of properties and their dependent schemas + * @since 4.0 + */ + Map getDependentSchemas(); + + /** + * Sets the dependentSchemas property of this Schema instance. + *

+ * For each name and property pair in the map, if the object contains a property with the given name, it must match + * the corresponding schema. + * + * @param dependentSchemas + * a map of properties and their dependent schemas + * @since 4.0 + */ + void setDependentSchemas(Map dependentSchemas); + + /** + * Sets the dependentSchemas property of this Schema instance. + *

+ * For each name and property pair in the map, if the object contains a property with the given name, it must match + * the corresponding schema. + * + * @param dependentSchemas + * a map of properties and their dependent schemas + * @return the current Schema instance + * @since 4.0 + */ + default Schema dependentSchemas(Map dependentSchemas) { + setDependentSchemas(dependentSchemas); + return this; + } + + /** + * Sets the dependent schema for a property name. + *

+ * If the object contains a property with the given name, it must match the dependent schema. + * + * @param propertyName + * the property name + * @param schema + * the dependent schema + * @return the current Schema instance + * @since 4.0 + */ + Schema addDependentSchema(String propertyName, Schema schema); + + /** + * Removes the dependent schema for a property name. + * + * @param propertyName + * the property name + * @since 4.0 + */ + void removeDependentSchema(String propertyName); + + /** + * Returns the prefixItems property of this Schema instance. + *

+ * If the object is an array, the nth item in the array must match the nth schema in this list. + * + * @return a copy List (potentially immutable) of prefix item schemas + * @since 4.0 + */ + List getPrefixItems(); + + /** + * Sets the prefixItems property of this Schema instance. + *

+ * If the object is an array, the nth item in the array must match the nth schema in this list. + * + * @param prefixItems + * a list of prefix item schemas + * @since 4.0 + */ + void setPrefixItems(List prefixItems); + + /** + * Sets the prefixItems property of this Schema instance. + *

+ * If the object is an array, the nth item in the array must match the nth schema in this list. + * + * @param prefixItems + * a list of prefix item schemas + * @return current Schema instance + * @since 4.0 + */ + default Schema prefixItems(List prefixItems) { + setPrefixItems(prefixItems); + return this; + } + + /** + * Adds a schema to the end of the prefixItems list. + * + * @param prefixItem + * the schema to add to the prefixItems list + * @return current Schema instance + * @since 4.0 + */ + Schema addPrefixItem(Schema prefixItem); + + /** + * Removes a schema from the prefixItems list. + * + * @param prefixItem + * the schema to remove from the prefixItems list + * @since 4.0 + */ + void removePrefixItem(Schema prefixItem); + + /** + * Returns the contains property of this Schema instance. + *

+ * If the object is an array, at least one item in the array must match the returned schema. + * + * @return a schema that one item in the array should match + * @since 4.0 + */ + Schema getContains(); + + /** + * Sets the contains property of this Schema instance. + *

+ * If the object is an array, at least one item in the array must match the returned schema. + * + * @param contains + * a schema that one item in the array should match + * @since 4.0 + */ + void setContains(Schema contains); + + /** + * Sets the contains property of this Schema instance. + *

+ * If the object is an array, at least one item in the array must match the returned schema. + * + * @param contains + * a schema that one item in the array should match + * @return the current Schema instance + * @since 4.0 + */ + default Schema contains(Schema contains) { + setContains(contains); + return this; + } + + /** + * Returns the patternProperties property from this Schema instance. + *

+ * The value of patternProperties is a map from a regular expression to a schema. For each string and schema pair in + * the map, if a property name is matched by the regular expression then the value of that property must validate + * against the schema. + * + * @return a copy Map (potentially immutable) of regular expression and schema pairs + * @since 4.0 + */ + Map getPatternProperties(); + + /** + * Sets the patternProperties property from this Schema instance. + *

+ * The value of patternProperties is a map from a regular expression to a schema. For each string and schema pair in + * the map, if a property name is matched by the regular expression then the value of that property must validate + * against the schema. + * + * @param patternProperties + * a map of regular expression and schema pairs + * @since 4.0 + */ + void setPatternProperties(Map patternProperties); + + /** + * Sets the patternProperties property from this Schema instance. + *

+ * The value of patternProperties is a map from a regular expression to a schema. For each string and schema pair in + * the map, if a property name is matched by the regular expression then the value of that property must validate + * against the schema. + * + * @param patternProperties + * a map of regular expression and schema pairs + * @return the current Schema instance + * @since 4.0 + */ + default Schema patternProperties(Map patternProperties) { + setPatternProperties(patternProperties); + return this; + } + + /** + * Adds a regular expression and schema pair to the list of pattern properties. + *

+ * The value of patternProperties is a map from a regular expression to a schema. For each string and schema pair in + * the map, if a property name is matched by the regular expression then the value of that property must validate + * against the schema. + * + * @param regularExpression + * the regular expression to add + * @param schema + * the schema that a property value must validate against if its name matches {@code regularExpression} + * @return the current Schema instance + * @since 4.0 + */ + Schema addPatternProperty(String regularExpression, Schema schema); + + /** + * Removes a regular expression and its corresponding schema pair from the list of pattern properties. + * + * @param regularExpression + * the regular expression to remove + * @since 4.0 + */ + void removePatternProperty(String regularExpression); + + /** + * Returns the propertyNames property from this Schema instance. Each property name in the object must + * validate against this schema. + * + * @return the schema which each property name must validate against + * @since 4.0 + */ + Schema getPropertyNames(); + + /** + * Sets the propertyNames property from this Schema instance. Each property name in the object must + * validate against this schema. + * + * @param propertyNameSchema + * the schema which each property name must validate against + * @since 4.0 + */ + void setPropertyNames(Schema propertyNameSchema); + + /** + * Sets the propertyNames property from this Schema instance. Each property name in the object must + * validate against this schema. + * + * @param propertyNameSchema + * the schema which each property name must validate against + * @return the current Schema instance + * @since 4.0 + */ + default Schema propertyNames(Schema propertyNameSchema) { + setPropertyNames(propertyNameSchema); + return this; + } + + /** + * Returns the unevaluatedItems property of this Schema instance. + *

+ * Items which have not successfully validated against {@code prefixItems}, {@code items}, or {@code contains} must + * validate against this schema. + * + * @return a schema that unevaluated array items must validate against + * @since 4.0 + */ + Schema getUnevaluatedItems(); + + /** + * Sets the unevaluatedItems property of this Schema instance. + *

+ * Items which have not successfully validated against {@code prefixItems}, {@code items}, or {@code contains} must + * validate against this schema. + * + * @param unevaluatedItems + * a schema that unevaluated array items must validate against + * @since 4.0 + */ + void setUnevaluatedItems(Schema unevaluatedItems); + + /** + * Sets the unevaluatedItems property of this Schema instance. + *

+ * Items which have not successfully validated against {@code prefixItems}, {@code items}, or {@code contains} must + * validate against this schema. + * + * @param unevaluatedItems + * a schema that unevaluated array items must validate against + * @return the current Schema instance + * @since 4.0 + */ + default Schema unevaluatedItems(Schema unevaluatedItems) { + setUnevaluatedItems(unevaluatedItems); + return this; + } + + /** + * Returns the unevaluatedProperties property of this Schema instance. + *

+ * Property values which have not successfully validated against {@code properties}, {@code patternProperties}, or + * {@code additionalProperties} must validate against this schema. + * + * @return a schema that unevaluated object properties must validate against + * @since 4.0 + */ + Schema getUnevaluatedProperties(); + + /** + * Sets the unevaluatedProperties property of this Schema instance. + *

+ * Property values which have not successfully validated against {@code properties}, {@code patternProperties}, or + * {@code additionalProperties} must validate against this schema. + * + * @param unevaluatedProperties + * a schema that unevaluated object properties must validate against + * @since 4.0 + */ + void setUnevaluatedProperties(Schema unevaluatedProperties); + + /** + * Sets the unevaluatedProperties property of this Schema instance. + *

+ * Property values which have not successfully validated against {@code properties}, {@code patternProperties}, or + * {@code additionalProperties} must validate against this schema. + * + * @param unevaluatedProperties + * a schema that unevaluated object properties must validate against + * @return the current Schema instance + * @since 4.0 + */ + default Schema unevaluatedProperties(Schema unevaluatedProperties) { + setUnevaluatedProperties(unevaluatedProperties); + return this; + } + + /** + * Returns the const property from this Schema instance. Indicates that the object must have a specific value. + * + * @return the value that the object must have + * @since 4.0 + */ + Object getConstValue(); + + /** + * Sets the const property from this Schema instance. Indicates that the object must have a specific value. + * + * @param constValue + * the value that the object must have + * @since 4.0 + */ + void setConstValue(Object constValue); + + /** + * Sets the const property from this Schema instance. Indicates that the object must have a specific value. + * + * @param constValue + * the value that the object must have + * @return the current Schema instance + * @since 4.0 + */ + default Schema constValue(Object constValue) { + setConstValue(constValue); + return this; + } + + /** + * Returns the maxContains property from this Schema instance. Specifies that {@code contains} must match no more + * than this many items in the array. + * + * @return the max number of items which may be matched by {@code contains} + * @since 4.0 + */ + Integer getMaxContains(); + + /** + * Sets the maxContains property from this Schema instance. Specifies that {@code contains} must match no more than + * this many items in the array. + * + * @param maxContains + * the max number of items which may be matched by {@code contains} + * @since 4.0 + */ + void setMaxContains(Integer maxContains); + + /** + * Sets the maxContains property from this Schema instance. Specifies that {@code contains} must match no more than + * this many items in the array. + * + * @param maxContains + * the maximum number of items which may be matched by {@code contains} + * @return the current Schema instance + * @since 4.0 + */ + default Schema maxContains(Integer maxContains) { + setMaxContains(maxContains); + return this; + } + + /** + * Returns the minContains property from this Schema instance. Specifies that {@code contains} must match at least + * this many items in the array. + * + * @return the minimum number of items which may be matched by {@code contains} + * @since 4.0 + */ + Integer getMinContains(); + + /** + * Sets the minContains property from this Schema instance. Specifies that {@code contains} must match at least this + * many items in the array. + * + * @param minContains + * the minimum number of items which may be matched by {@code contains} + * @since 4.0 + */ + void setMinContains(Integer minContains); + + /** + * Sets the minContains property from this Schema instance. Specifies that {@code contains} must match at least this + * many items in the array. + * + * @param minContains + * the minimum number of items which may be matched by {@code contains} + * @return the current Schema instance + * @since 4.0 + */ + default Schema minContains(Integer minContains) { + setMinContains(minContains); + return this; + } + + /** + * Returns the dependentRequired property of this Schema instance. + *

+ * For each entry in the map, if the key exists as a property name in the object, then the list of names in the + * value must also exist as property names in the object. + * + * @return a copy Map (potentially immutable) of property names to lists of additional required property names + * @since 4.0 + */ + Map> getDependentRequired(); + + /** + * Sets the dependentRequired property of this Schema instance. + *

+ * For each entry in the map, if the key exists as a property name in the object, then the list of names in the + * value must also exist as property names in the object. + * + * @param dependentRequired + * a map of property names to lists of additional required property names + * @since 4.0 + */ + void setDependentRequired(Map> dependentRequired); + + /** + * Sets the dependentRequired property of this Schema instance. + *

+ * For each entry in the map, if the key exists as a property name in the object, then the list of names in the + * value must also exist as property names in the object. + * + * @param dependentRequired + * a map of property names to lists of additional required property names + * @return the current Schema instance + * @since 4.0 + */ + default Schema dependentRequired(Map> dependentRequired) { + setDependentRequired(dependentRequired); + return this; + } + + /** + * Sets the list of additional property names that are required if a property named {@code propertyName} exists. + * + * @param propertyName + * the property name + * @param additionalRequiredPropertyNames + * the names of additional properties which are required if {@code propertyName} exists to add + * @return the current Schema instance + * @since 4.0 + */ + Schema addDependentRequired(String propertyName, List additionalRequiredPropertyNames); + + /** + * Removes the list of additional property names that are required if a property named {@code propertyName} exists. + * + * @param propertyName + * the property name + * @since 4.0 + */ + void removeDependentRequired(String propertyName); + + /** + * Returns the contentEncoding property from this Schema instance. + *

+ * Specifies the encoding used to represent binary data as a string (e.g. base64). + * + * @return the encoding type + * @since 4.0 + */ + String getContentEncoding(); + + /** + * Sets the contentEncoding property from this Schema instance. + *

+ * Specifies the encoding used to represent binary data as a string (e.g. base64). + * + * @param contentEncoding + * the encoding type + * @since 4.0 + */ + void setContentEncoding(String contentEncoding); + + /** + * Sets the contentEncoding property from this Schema instance. + *

+ * Specifies the encoding used to represent binary data as a string (e.g. base64). + * + * @param contentEncoding + * the encoding type + * @return the current Schema instance + * @since 4.0 + */ + default Schema contentEncoding(String contentEncoding) { + setContentEncoding(contentEncoding); + return this; + } + + /** + * Returns the contentMediaType property from this Schema instance. + *

+ * Specifies the media type of the content of a string. + * + * @return the media type + * @since 4.0 + */ + String getContentMediaType(); + + /** + * Sets the contentMediaType property from this Schema instance. + *

+ * Specifies the media type of the content of a string. + * + * @param contentMediaType + * the media type + * @since 4.0 + */ + void setContentMediaType(String contentMediaType); + + /** + * Sets the contentMediaType property from this Schema instance. + *

+ * Specifies the media type of the content of a string. + * + * @param contentMediaType + * the media type + * @return the current Schema instance + * @since 4.0 + */ + default Schema contentMediaType(String contentMediaType) { + setContentMediaType(contentMediaType); + return this; + } + + /** + * Returns the contentSchema property from this Schema instance. + *

+ * If {@code contentMediaType} is a media type that maps into JSON Schema's data model, this property specifies a + * schema that the data in the string must conform to. + * + * @return the schema for the data within the string + * @since 4.0 + */ + Schema getContentSchema(); + + /** + * Sets the contentSchema property from this Schema instance. + *

+ * If {@code contentMediaType} is a media type that maps into JSON Schema's data model, this property specifies a + * schema that the data in the string must conform to. + * + * @param contentSchema + * the schema for the data within the string + * @since 4.0 + */ + void setContentSchema(Schema contentSchema); + + /** + * Sets the contentSchema property from this Schema instance. + *

+ * If {@code contentMediaType} is a media type that maps into JSON Schema's data model, this property specifies a + * schema that the data in the string must conform to. + * + * @param contentSchema + * the schema for the data within the string + * @return the current Schema instance + * @since 4.0 + */ + default Schema contentSchema(Schema contentSchema) { + setContentSchema(contentSchema); + return this; + } + + /** + * Returns whether this Schema is a boolean schema. + *

+ * If this property is not {@code null}, then all other properties are ignored and the schema will be represented by + * a boolean {@code true} or {@code false} value. + * + * @return the boolean value of this schema, or {@code null} if it is not a boolean schema + * @since 4.0 + */ + Boolean getBooleanSchema(); + + /** + * Sets this schema to a boolean value. + *

+ * If this property is not {@code null}, then all other properties are ignored and the schema will be represented by + * a boolean {@code true} or {@code false} value. + * + * @param booleanSchema + * the boolean value of this schema, or {@code null} if it is not a boolean schema + * @since 4.0 + */ + void setBooleanSchema(Boolean booleanSchema); + + /** + * Sets this schema to a boolean value. + *

+ * If this property is not {@code null}, then all other properties are ignored and the schema will be represented by + * a boolean {@code true} or {@code false} value. + * + * @param booleanSchema + * the boolean value of this schema, or {@code null} if it is not a boolean schema + * @return the current Schema instance + * @since 4.0 + */ + default Schema booleanSchema(Boolean booleanSchema) { + setBooleanSchema(booleanSchema); + return this; + } + + /** + * Returns the examples property of this Schema instance. + * + * @return a copy List (potentially immutable) of example objects which this schema could describe + * @since 4.0 + */ + List getExamples(); + + /** + * Sets the examples property of this Schema instance. + * + * @param examples + * a list of example objects which this schema could describe + * @since 4.0 + */ + void setExamples(List examples); + + /** + * Sets the examples property of this Schema instance. + * + * @param examples + * a list of example objects which this schema could describe + * @return current Schema instance + * @since 4.0 + */ + default Schema examples(List examples) { + setExamples(examples); + return this; + } + + /** + * Adds an example to the examples list. + * + * @param example + * the example to add to the examples list + * @return current Schema instance + * @since 4.0 + */ + Schema addExample(Object example); + + /** + * Removes an example from the examples list. + * + * @param example + * the example to remove from the examples list + * @since 4.0 + */ + void removeExample(Object example); + + /** + * Gets a schema property by name. + *

+ * Allows access to arbitrary properties in a schema object, allowing use of alternative schema dialects which use + * different property names (or the same property names with different data types). + *

+ * When using the standard schema dialect, this method can be used to retrieve values set by other methods. E.g. + * + *

+     * {@code
+     * schema.setMinimum(new BigDecimal(3));
+     * BigDecimal minimum = (BigDecimal) schema.get("minimum"); // returns 3
+     * }
+     * 
+ * + * @param propertyName + * the property name + * @return the value of the named property, or {@code null} if a property with the given name is not set + */ + Object get(String propertyName); + + /** + * Sets a schema property. + *

+ * Allows the modifications of arbitrary schema properties in a schema properties, allowing use of alternative + * schema dialects which use different property names (or the same property names with different data types). + *

+ * Passing {@code null} as the {@code value} removes the property from the schema object. + *

+ * {@code value} must be one of the following types, otherwise non-portable behavior results: + *

    + *
  • Any primitive type + *
  • Any primitive wrapper class + *
  • {@code null} + *
  • {@code String} + *
  • {@code BigDecimal} + *
  • {@code BigInteger} + *
  • Any type which {@link OASFactory} can create + *
  • Any Enumeration + *
  • {@code List} where every value is a permitted type + *
  • {@code Map} where every key is a {@code String} and every value is a permitted type + *
+ * + *

+ * When using the standard schema dialect, values set by this method can be retrieved by other methods. E.g. + * + *

+     * {@code
+     * schema.set("minimum", new BigDecimal(3));
+     * BigDecimal minimum = schema.getMinimum(); // returns 3
+     * }
+     * 
+ * + * @param propertyName + * the property name + * @param value + * the value to set, or {@code null} to remove the property + * @return the current Schema instance + */ + Schema set(String propertyName, Object value); + + /** + * Gets all properties of a schema. + *

+ * Equivalent to calling {@link #get(String)} for each property set to a non-{@code null} value and putting them all + * into a {@code Map}. + * + * @return a {@code Map} of property names to their corresponding values + */ + Map getAll(); + + /** + * Sets all properties of a schema. + *

+ * Equivalent to clearing all properties and then setting each property with {@link #set(String, Object)}. + * + * @param allProperties + * the properties to set. Each value in the map must be valid according to the rules in + * {@link #set(String, Object)} + */ + void setAll(Map allProperties); } diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/media/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/media/package-info.java index a974424cc..3e143df68 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/models/media/package-info.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/media/package-info.java @@ -30,6 +30,6 @@ * */ -@org.osgi.annotation.versioning.Version("2.1") +@org.osgi.annotation.versioning.Version("3.0") @org.osgi.annotation.versioning.ProviderType package org.eclipse.microprofile.openapi.models.media; \ No newline at end of file diff --git a/pom.xml b/pom.xml index 84e67b004..7f40622c0 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ org.eclipse.microprofile.openapi microprofile-openapi-parent - 3.2-SNAPSHOT + 4.0-SNAPSHOT pom MicroProfile OpenAPI Eclipse MicroProfile OpenAPI diff --git a/spec/pom.xml b/spec/pom.xml index 05d679445..336955778 100644 --- a/spec/pom.xml +++ b/spec/pom.xml @@ -20,7 +20,7 @@ org.eclipse.microprofile.openapi microprofile-openapi-parent - 3.2-SNAPSHOT + 4.0-SNAPSHOT microprofile-openapi-spec diff --git a/spi/pom.xml b/spi/pom.xml index 3d7f7a158..bc08375ec 100644 --- a/spi/pom.xml +++ b/spi/pom.xml @@ -20,7 +20,7 @@ org.eclipse.microprofile.openapi microprofile-openapi-parent - 3.2-SNAPSHOT + 4.0-SNAPSHOT microprofile-openapi-spi diff --git a/tck/pom.xml b/tck/pom.xml index 85370aba4..f2e5dfbe0 100644 --- a/tck/pom.xml +++ b/tck/pom.xml @@ -20,7 +20,7 @@ org.eclipse.microprofile.openapi microprofile-openapi-parent - 3.2-SNAPSHOT + 4.0-SNAPSHOT microprofile-openapi-tck diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java b/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java index 4769b2335..45ce98a39 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java @@ -15,9 +15,14 @@ */ package org.eclipse.microprofile.openapi.reader; +import static java.util.Collections.emptyMap; + import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.DayOfWeek; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import org.eclipse.microprofile.openapi.OASFactory; @@ -38,11 +43,13 @@ import org.eclipse.microprofile.openapi.models.media.MediaType; import org.eclipse.microprofile.openapi.models.media.Schema; import org.eclipse.microprofile.openapi.models.parameters.Parameter; +import org.eclipse.microprofile.openapi.models.parameters.Parameter.In; import org.eclipse.microprofile.openapi.models.parameters.RequestBody; import org.eclipse.microprofile.openapi.models.responses.APIResponse; import org.eclipse.microprofile.openapi.models.responses.APIResponses; import org.eclipse.microprofile.openapi.models.security.SecurityRequirement; import org.eclipse.microprofile.openapi.models.security.SecurityScheme; +import org.eclipse.microprofile.openapi.models.security.SecurityScheme.Type; import org.eclipse.microprofile.openapi.models.servers.Server; import org.eclipse.microprofile.openapi.models.servers.ServerVariable; import org.eclipse.microprofile.openapi.models.tags.Tag; @@ -90,30 +97,84 @@ public OpenAPI buildModel() { .components(OASFactory.createObject(Components.class) .schemas(new HashMap()) .addSchema("Bookings", OASFactory.createObject(Schema.class) - .type(Schema.SchemaType.INTEGER) + .addType(Schema.SchemaType.INTEGER) .title("Bookings") .ref("#/components.schemas.Booking")) .addSchema("Airlines", OASFactory.createObject(Schema.class) - .type(Schema.SchemaType.INTEGER) + .addType(Schema.SchemaType.INTEGER) .title("Airlines")) .addSchema("AirlinesRef", OASFactory.createObject(Schema.class) .ref("#/components/schemas/Airlines")) .addSchema("id", OASFactory.createObject(Schema.class) - .type(Schema.SchemaType.INTEGER) + .addType(Schema.SchemaType.INTEGER) .format("int32")) + .addSchema("custom", OASFactory.createSchema() + .schemaDialect("http://example.com/myCustomSchema") + .set("shortKey", (short) 1) + .set("intKey", 2) + .set("longKey", 3L) + .set("booleanKey", true) + .set("charKey", 'a') + .set("stringKey", "string") + .set("floatKey", 3.5f) + .set("doubleKey", 3.5d) + .set("bigDecimalKey", new BigDecimal("3.5")) + .set("bigIntegerKey", new BigInteger("7")) + .set("extDocKey", OASFactory.createExternalDocumentation().description("test")) + .set("operationKey", OASFactory.createOperation().description("test")) + .set("pathItemKey", OASFactory.createPathItem().description("test")) + .set("pathsKey", OASFactory.createPaths() + .addPathItem("test", OASFactory.createPathItem().description("test"))) + .set("callbackKey", OASFactory.createCallback() + .addPathItem("test", OASFactory.createPathItem().description("test"))) + .set("exampleKey", OASFactory.createExample().value("test")) + .set("headerKey", OASFactory.createHeader().description("test")) + .set("contactKey", OASFactory.createContact().name("test")) + .set("infoKey", OASFactory.createInfo().title("test").version("1.0")) + .set("licenseKey", OASFactory.createLicense().name("test")) + .set("linkKey", OASFactory.createLink().operationId("getTestFlights")) + .set("contentKey", OASFactory.createContent() + .addMediaType("test", OASFactory.createMediaType().example("test"))) + .set("discriminatorKey", OASFactory.createDiscriminator().propertyName("test")) + .set("schemaKey", OASFactory.createSchema().title("test")) + .set("xmlKey", OASFactory.createXML().name("test")) + .set("parameterKey", OASFactory.createParameter().name("test").in(In.PATH)) + .set("requestBodyKey", OASFactory.createRequestBody() + .content(OASFactory.createContent() + .addMediaType("test", OASFactory.createMediaType().example("test")))) + .set("apiResponseKey", OASFactory.createAPIResponse().description("test")) + .set("apiResponsesKey", OASFactory.createAPIResponses() + .addAPIResponse("200", OASFactory.createAPIResponse().description("test"))) + .set("oAuthFlowKey", + OASFactory.createOAuthFlow().authorizationUrl("http://example.com")) + .set("oAuthFlowsKey", OASFactory.createOAuthFlows().implicit( + OASFactory.createOAuthFlow().authorizationUrl("http://example.com") + .scopes(emptyMap()))) + .set("securityReqKey", OASFactory.createSecurityRequirement().addScheme("test")) + .set("securitySchemeKey", OASFactory.createSecurityScheme() + .type(Type.HTTP) + .scheme("Basic")) + .set("serverKey", OASFactory.createServer().url("http://example.com")) + .set("serverVarKey", OASFactory.createServerVariable().defaultValue("test")) + .set("tagKey", OASFactory.createTag().name("test")) + .set("enumKey", DayOfWeek.MONDAY) + .set("listKey", Arrays.asList( + "test", + OASFactory.createXML().name("test"))) + .set("mapKey", Collections.singletonMap("test", DayOfWeek.THURSDAY))) .responses(new HashMap()) .addResponse("FoundAirlines", OASFactory.createObject(APIResponse.class) .description("successfully found airlines") .content(OASFactory.createObject(Content.class) .addMediaType("application/json", OASFactory.createObject(MediaType.class) .schema(OASFactory.createObject(Schema.class) - .type(Schema.SchemaType.ARRAY))))) + .addType(Schema.SchemaType.ARRAY))))) .addResponse("FoundBookings", OASFactory.createObject(APIResponse.class) .description("Bookings retrieved") .content(OASFactory.createObject(Content.class) .addMediaType("application/json", OASFactory.createObject(MediaType.class) .schema(OASFactory.createObject(Schema.class) - .type(Schema.SchemaType.ARRAY) + .addType(Schema.SchemaType.ARRAY) .ref("#/components.schemas.Booking"))))) .parameters(new HashMap()) .addParameter("departureDate", OASFactory.createObject(Parameter.class) @@ -144,14 +205,14 @@ public OpenAPI buildModel() { .addHeader("Max-Rate", OASFactory.createObject(Header.class) .description("Maximum rate") .schema(OASFactory.createObject(Schema.class) - .type(Schema.SchemaType.INTEGER)) + .addType(Schema.SchemaType.INTEGER)) .required(true) .allowEmptyValue(true) .deprecated(true)) .addHeader("Request-Limit", OASFactory.createObject(Header.class) .description("The number of allowed requests in the current period") .schema(OASFactory.createObject(Schema.class) - .type(Schema.SchemaType.INTEGER))) + .addType(Schema.SchemaType.INTEGER))) .securitySchemes(new HashMap()) .addSecurityScheme("httpTestScheme", OASFactory.createObject(SecurityScheme.class) .description("user security scheme") @@ -218,7 +279,7 @@ public OpenAPI buildModel() { .addMediaType("application/json", OASFactory .createObject(MediaType.class) .schema(OASFactory.createObject(Schema.class) - .type(Schema.SchemaType.ARRAY) + .addType(Schema.SchemaType.ARRAY) .ref("#/components.schemas.Flight"))))) .addAPIResponse("404", OASFactory.createObject(APIResponse.class) .description("No available flights found") @@ -235,7 +296,7 @@ public OpenAPI buildModel() { .allowEmptyValue(true) .description("Airport the customer departs from") .schema(OASFactory.createObject(Schema.class) - .type(Schema.SchemaType.STRING))) + .addType(Schema.SchemaType.STRING))) .addParameter(OASFactory.createObject(Parameter.class) .name("returningDate") .required(true) @@ -243,14 +304,14 @@ public OpenAPI buildModel() { .allowReserved(true) .description("Customer return date") .schema(OASFactory.createObject(Schema.class) - .type(Schema.SchemaType.STRING))) + .addType(Schema.SchemaType.STRING))) .addParameter(OASFactory.createObject(Parameter.class) .name("airportTo") .required(true) .in(Parameter.In.QUERY) .description("Airport the customer returns to") .schema(OASFactory.createObject(Schema.class) - .type(Schema.SchemaType.STRING))) + .addType(Schema.SchemaType.STRING))) .addParameter(OASFactory.createObject(Parameter.class) .name("numberOfAdults") .required(true) @@ -258,7 +319,7 @@ public OpenAPI buildModel() { .description("Number of adults on the flight") .schema(OASFactory.createObject(Schema.class) .minimum(new BigDecimal(0)) - .type(Schema.SchemaType.STRING))) + .addType(Schema.SchemaType.STRING))) .addParameter(OASFactory.createObject(Parameter.class) .name("numberOfChildren") .required(true) @@ -267,7 +328,7 @@ public OpenAPI buildModel() { .description("Number of children on the flight") .schema(OASFactory.createObject(Schema.class) .minimum(new BigDecimal(0)) - .type(Schema.SchemaType.STRING))))) + .addType(Schema.SchemaType.STRING))))) .addPathItem("/modelReader/bookings", OASFactory.createObject(PathItem.class) .GET(OASFactory.createObject(Operation.class) .tags(new ArrayList()) @@ -281,7 +342,7 @@ public OpenAPI buildModel() { .addMediaType("applictaion/json", OASFactory .createObject(MediaType.class) .schema(OASFactory.createObject(Schema.class) - .type(Schema.SchemaType.ARRAY) + .addType(Schema.SchemaType.ARRAY) .ref("#/components.schemas.Booking"))))) .addAPIResponse("404", OASFactory.createObject(APIResponse.class) .description("No bookings found for the user")))) @@ -304,6 +365,7 @@ public OpenAPI buildModel() { .schema(OASFactory.createObject(Schema.class) .title("id") .description("id of the new booking") - .type(Schema.SchemaType.STRING))))))))); + .addType( + Schema.SchemaType.STRING))))))))); } } diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java index c938d71ce..96b54ecbd 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java @@ -16,6 +16,7 @@ package org.eclipse.microprofile.openapi.tck; +import static org.eclipse.microprofile.openapi.tck.utils.TCKMatchers.itemOrSingleton; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.anEmptyMap; import static org.hamcrest.Matchers.both; @@ -360,7 +361,7 @@ private void testUserLoginMethods(ValidatableResponse vr) { vr.body(query + ".in", both(hasSize(1)).and(contains("query"))); vr.body(query + ".description", both(hasSize(1)).and(contains(list.get(i)[1]))); vr.body(query + ".required", both(hasSize(1)).and(contains(true))); - vr.body(query + ".schema.type", both(hasSize(1)).and(contains("string"))); + vr.body(query + ".schema.type", both(hasSize(1)).and(contains(itemOrSingleton("string")))); } } @@ -373,7 +374,7 @@ private void testReviewIdMethods(ValidatableResponse vr) { both(hasSize(1)).and(contains("ID of the booking"))); vr.body(reviewParameters + ".findAll { it.name == 'id' }.required", both(hasSize(1)).and(contains(true))); vr.body(reviewParameters + ".findAll { it.name == 'id' }.content.'*/*'.schema.type", - both(hasSize(1)).and(contains("integer"))); + both(hasSize(1)).and(contains(itemOrSingleton("integer")))); } private void testBookingIdMethods(ValidatableResponse vr) { @@ -386,7 +387,7 @@ private void testBookingIdMethods(ValidatableResponse vr) { vr.body(bookingParameters + ".findAll { it }.name", contains("id")); vr.body(bookingParameters + ".findAll { it.name == 'id' }.required", both(hasSize(1)).and(contains(true))); vr.body(bookingParameters + ".findAll { it.name == 'id' }.schema.type", - both(hasSize(1)).and(contains("integer"))); + both(hasSize(1)).and(contains(itemOrSingleton("integer")))); } bookingParameters = "paths.'/bookings/{id}'.get.parameters"; @@ -415,7 +416,7 @@ private void testAvailabilityGetParamater(ValidatableResponse vr) { vr.body(query + ".in", both(hasSize(1)).and(contains("query"))); vr.body(query + ".description", both(hasSize(1)).and(contains(list.get(i)[1]))); vr.body(query + ".required", both(hasSize(1)).and(contains(true))); - vr.body(query + ".schema.type", both(hasSize(1)).and(contains("string"))); + vr.body(query + ".schema.type", both(hasSize(1)).and(contains(itemOrSingleton("string")))); } vr.body(availabilityParameters + ".findAll { it.name == 'numberOfAdults' }.schema.minimum", @@ -467,13 +468,13 @@ public void testCallbackOperationAnnotations(String type) { vr.body(endpoint, hasKey("get")); vr.body(endpoint + ".get.summary", equalTo("Retrieve all bookings for current user")); vr.body(endpoint + ".get.responses.'200'.description", equalTo("Bookings retrieved")); - vr.body(endpoint + ".get.responses.'200'.content.'application/json'.schema.type", equalTo("array")); + vr.body(endpoint + ".get.responses.'200'.content.'application/json'.schema.type", itemOrSingleton("array")); endpoint = "paths.'/reviews'.post.callbacks.testCallback.'http://localhost:9080/oas3-airlines/reviews'"; vr.body(endpoint, hasKey("get")); vr.body(endpoint + ".get.summary", equalTo("Get all reviews")); vr.body(endpoint + ".get.responses.'200'.description", equalTo("successful operation")); - vr.body(endpoint + ".get.responses.'200'.content.'application/json'.schema.type", equalTo("array")); + vr.body(endpoint + ".get.responses.'200'.content.'application/json'.schema.type", itemOrSingleton("array")); vr.body(endpoint + ".get.responses.'200'.content.'application/json'.schema.items", notNullValue()); vr.body(endpoint + ".get.x-callback-operation", equalTo("test-callback-operation")); } @@ -700,11 +701,12 @@ public void testSchema(String type) { vr.body("components.schemas.AirlinesRef.$ref", equalTo("#/components/schemas/Airlines")); vr.body("components.schemas.Airlines.title", equalTo("Airlines")); vr.body("components.schemas.Airlines.x-schema", equalTo("test-schema")); - vr.body("paths.'/bookings'.post.responses.'201'.content.'application/json'.schema.type", equalTo("string")); + vr.body("paths.'/bookings'.post.responses.'201'.content.'application/json'.schema.type", + itemOrSingleton("string")); vr.body("components.schemas.id.format", equalTo("int32")); vr.body("paths.'/bookings'.post.responses.'201'.content.'application/json'.schema.description", equalTo("id of the new booking")); - vr.body("components.schemas.User.properties.password.example", equalTo("bobSm37")); + vr.body("components.schemas.User.properties.password.examples", contains("bobSm37")); // Object properties vr.body("paths.'/user'.post.requestBody.content.'application/json'.schema.maxProperties", equalTo(1024)); @@ -715,7 +717,7 @@ public void testSchema(String type) { // Array properties String createSchema = "paths.'/user/createWithArray'.post.requestBody.content.'application/json'.schema"; - vr.body(createSchema + ".nullable", equalTo(true)); + vr.body(createSchema + ".type", containsInAnyOrder("array", "null")); vr.body(createSchema + ".writeOnly", equalTo(true)); vr.body(createSchema + ".maxItems", equalTo(20)); vr.body(createSchema + ".minItems", equalTo(2)); @@ -726,7 +728,7 @@ public void testSchema(String type) { public void testSchemaProperty(String type) { ValidatableResponse vr = callEndpoint(type); vr.body("components.schemas.User.properties", IsMapWithSize.aMapWithSize(10)); - vr.body("components.schemas.User.properties.phone.example", equalTo("123-456-7891")); + vr.body("components.schemas.User.properties.phone.examples", contains("123-456-7891")); vr.body("components.schemas.User.properties.phone.description", equalTo("Telephone number to contact the user")); vr.body("components.schemas.User.properties.phone.x-schema-property", equalTo("test-schema-property")); @@ -736,8 +738,8 @@ public void testSchemaProperty(String type) { public void testSchemaPropertyValuesOverrideClassPropertyValues(String type) { ValidatableResponse vr = callEndpoint(type); vr.body("components.schemas.User.properties", IsMapWithSize.aMapWithSize(10)); - vr.body("components.schemas.User.properties.phone.example", not("123-456-7890")); - vr.body("components.schemas.User.properties.phone.example", equalTo("123-456-7891")); + vr.body("components.schemas.User.properties.phone.examples", not(contains("123-456-7890"))); + vr.body("components.schemas.User.properties.phone.examples", contains("123-456-7891")); } @Test(dataProvider = "formatProvider") @@ -852,7 +854,7 @@ public void testHeaderInAPIResponse(String type) { vr.body(responseHeader1 + ".deprecated", equalTo(true)); vr.body(responseHeader1 + ".allowEmptyValue", equalTo(true)); vr.body(responseHeader1 + ".style", equalTo("simple")); - vr.body(responseHeader1 + ".schema.type", equalTo("integer")); + vr.body(responseHeader1 + ".schema.type", itemOrSingleton("integer")); String responseHeader2 = "paths.'/reviews/{id}'.get.responses.'200'.headers.responseHeader2"; vr.body(responseHeader2, notNullValue()); @@ -861,7 +863,7 @@ public void testHeaderInAPIResponse(String type) { vr.body(responseHeader2 + ".deprecated", equalTo(true)); vr.body(responseHeader2 + ".allowEmptyValue", equalTo(true)); vr.body(responseHeader2 + ".style", equalTo("simple")); - vr.body(responseHeader2 + ".schema.type", equalTo("string")); + vr.body(responseHeader2 + ".schema.type", itemOrSingleton("string")); } @Test(dataProvider = "formatProvider") @@ -877,7 +879,7 @@ public void testHeaderInEncoding(String type) { vr.body(testHeader + ".deprecated", equalTo(true)); vr.body(testHeader + ".allowEmptyValue", equalTo(true)); vr.body(testHeader + ".style", equalTo("simple")); - vr.body(testHeader + ".schema.type", equalTo("integer")); + vr.body(testHeader + ".schema.type", itemOrSingleton("integer")); } @Test(dataProvider = "formatProvider") @@ -910,7 +912,7 @@ public void testHeaderInComponents(String type) { vr.body(maxRate + ".deprecated", equalTo(true)); vr.body(maxRate + ".allowEmptyValue", equalTo(true)); vr.body(maxRate + ".style", equalTo("simple")); - vr.body(maxRate + ".schema.type", equalTo("integer")); + vr.body(maxRate + ".schema.type", itemOrSingleton("integer")); vr.body(maxRate + ".x-header", equalTo("test-header")); } @@ -920,7 +922,7 @@ public void testContentInAPIResponse(String type) { String content1 = "paths.'/availability'.get.responses.'200'.content.'application/json'"; vr.body(content1, notNullValue()); - vr.body(content1 + ".schema.type", equalTo("array")); + vr.body(content1 + ".schema.type", itemOrSingleton("array")); vr.body(content1 + ".schema.items", notNullValue()); vr.body(content1 + ".x-content", equalTo("test-content")); @@ -953,7 +955,7 @@ public void testContentInParameter(String type) { String content = "paths.'/reviews/users/{user}'.get.parameters.find{ it.name == 'user' }.content"; vr.body(content, notNullValue()); vr.body(content + ".'*/*'", notNullValue()); - vr.body(content + ".'*/*'.schema.type", equalTo("string")); + vr.body(content + ".'*/*'.schema.type", itemOrSingleton("string")); } @Test(dataProvider = "formatProvider") @@ -990,10 +992,10 @@ public void testStaticFileDefinitions(String type) { vr.body(parametersPath + ".find{ it.name == 'callbackUrl' }.required", equalTo(true)); vr.body(parametersPath + ".find{ it.name == 'callbackUrl' }.description", containsString("the location where data will be sent.")); - vr.body(parametersPath + ".find{ it.name == 'callbackUrl' }.schema.type", equalTo("string")); + vr.body(parametersPath + ".find{ it.name == 'callbackUrl' }.schema.type", itemOrSingleton("string")); vr.body(parametersPath + ".find{ it.name == 'callbackUrl' }.schema.format", equalTo("uri")); - vr.body(parametersPath + ".find{ it.name == 'callbackUrl' }.schema.example", - equalTo("https://tonys-server.com")); + vr.body(parametersPath + ".find{ it.name == 'callbackUrl' }.schema.examples", + contains("https://tonys-server.com")); final String responsePath = "paths.'/streams'.post.responses"; vr.body(responsePath, aMapWithSize(1)); @@ -1007,18 +1009,18 @@ public void testStaticFileDefinitions(String type) { vr.body(response201Path + ".content.'application/json'.schema.properties.subscriptionId.description", equalTo("this unique identifier allows management of the subscription")); vr.body(response201Path + ".content.'application/json'.schema.properties.subscriptionId.type", - equalTo("string")); - vr.body(response201Path + ".content.'application/json'.schema.properties.subscriptionId.example", - equalTo("2531329f-fb09-4ef7-887e-84e648214436")); + itemOrSingleton("string")); + vr.body(response201Path + ".content.'application/json'.schema.properties.subscriptionId.examples", + contains("2531329f-fb09-4ef7-887e-84e648214436")); final String callbacksPath = "paths.'/streams'.post.callbacks.onData.'{$request.query.callbackUrl}/data'.post"; vr.body(callbacksPath + ".requestBody.description", equalTo("subscription payload")); vr.body(callbacksPath + ".requestBody.content.'application/json'.schema.properties.timestamp.type", - equalTo("string")); + itemOrSingleton("string")); vr.body(callbacksPath + ".requestBody.content.'application/json'.schema.properties.timestamp.format", equalTo("date-time")); vr.body(callbacksPath + ".requestBody.content.'application/json'.schema.properties.userData.type", - equalTo("string")); + itemOrSingleton("string")); vr.body(callbacksPath + ".responses", aMapWithSize(2)); vr.body(callbacksPath + ".responses.'202'.description", @@ -1055,7 +1057,7 @@ public void testExceptionMappers(String type) { String rejectedReviewSchema = dereference(vr, "paths.'/reviews'.post.responses.'400'.content.'application/json'.schema"); - vr.body(rejectedReviewSchema + ".type", equalTo("object")); + vr.body(rejectedReviewSchema + ".type", itemOrSingleton("object")); vr.body(rejectedReviewSchema + ".properties", hasKey("reason")); } @@ -1104,7 +1106,7 @@ public void testAdditionalPropertiesTypeString(String type) { vr.body(responseSchema, notNullValue()); String flightSchema = dereference(vr, responseSchema, "properties.returningFlight"); - vr.body(flightSchema + ".additionalProperties.type", equalTo("string")); + vr.body(flightSchema + ".additionalProperties.type", itemOrSingleton("string")); } @Test(dataProvider = "formatProvider") diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/FilterTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/FilterTest.java index dbc4abf0f..70d11bd7e 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/FilterTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/FilterTest.java @@ -16,6 +16,7 @@ package org.eclipse.microprofile.openapi.tck; +import static org.eclipse.microprofile.openapi.tck.utils.TCKMatchers.itemOrSingleton; import static org.hamcrest.Matchers.both; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -112,7 +113,7 @@ public void testFilterParameter(String type) { vr.body(username + ".in", both(hasSize(1)).and(contains("query"))); vr.body(username + ".description", both(hasSize(1)).and(contains("filterParameter - The user name for login"))); vr.body(username + ".required", both(hasSize(1)).and(contains(true))); - vr.body(username + ".schema.type", both(hasSize(1)).and(contains("string"))); + vr.body(username + ".schema.type", both(hasSize(1)).and(contains(itemOrSingleton("string")))); // Parameter named 'password' should have been removed by filter vr.body(reviewParameters, hasSize(1)); @@ -161,7 +162,7 @@ public void testFilterHeader(String type) { vr.body(maxRate + ".deprecated", equalTo(true)); vr.body(maxRate + ".allowEmptyValue", equalTo(true)); vr.body(maxRate + ".style", equalTo("simple")); - vr.body(maxRate + ".schema.type", equalTo("integer")); + vr.body(maxRate + ".schema.type", itemOrSingleton("integer")); } @Test(dataProvider = "formatProvider") diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelConstructionTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelConstructionTest.java index aadb17d43..dd7360605 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelConstructionTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelConstructionTest.java @@ -20,6 +20,9 @@ // import static org.testng.Assert.assertNotSame; // import static org.testng.Assert.assertSame; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anEmptyMap; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.sameInstance; import static org.testng.Assert.assertEquals; @@ -40,6 +43,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; @@ -166,7 +170,7 @@ public Object invokeGetter(Object target) { try { return getter.invoke(target); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - fail("Invocation of getter method \"" + getter.getName() + "\" failed: " + e.getMessage()); + fail("Invocation of getter method \"" + getter.getName() + "\" failed: " + e.getMessage(), e); throw new RuntimeException(e); } } @@ -174,7 +178,7 @@ public void invokeSetter(Object target, Object value) { try { setter.invoke(target, value); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - fail("Invocation of setter method \"" + setter.getName() + "\" failed: " + e.getMessage()); + fail("Invocation of setter method \"" + setter.getName() + "\" failed: " + e.getMessage(), e); throw new RuntimeException(e); } } @@ -182,7 +186,7 @@ public Object invokeBuilder(Object target, Object value) { try { return builder.invoke(target, value); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - fail("Invocation of builder method \"" + builder.getName() + "\" failed: " + e.getMessage()); + fail("Invocation of builder method \"" + builder.getName() + "\" failed: " + e.getMessage(), e); throw new RuntimeException(e); } } @@ -990,6 +994,7 @@ public void mediaTypeTest() { checkNullValueInAdd(mt::getExamples, mt::addExample, "otherExample", exampleValue); } + @SuppressWarnings("deprecation") // Testing deprecated Schema methods @Test public void schemaTest() { final Schema s = processConstructible(Schema.class); @@ -999,22 +1004,40 @@ public void schemaTest() { checkSameObject(ap, s.getAdditionalPropertiesSchema()); assertEquals(s.getAdditionalPropertiesBoolean(), null, "AdditionalProperties (Boolean type) is expected to be null"); + checkSameObject(s, s.additionalPropertiesBoolean(Boolean.TRUE)); assertEquals(s.getAdditionalPropertiesBoolean(), Boolean.TRUE, "AdditionalProperties (Boolean type) is expected to be true"); - assertEquals(s.getAdditionalPropertiesSchema(), null, - "AdditionalProperties (Schema type) is expected to be null"); + Schema s2 = s.getAdditionalPropertiesSchema(); + assertNotNull(s2, "AdditionalProperties (Schema type) is expected to be non-null"); + assertEquals(s2.getBooleanSchema(), Boolean.TRUE, + "AdditionalProperties (Schema type) is expected to return a boolean-true schema"); + s.setAdditionalPropertiesBoolean(Boolean.FALSE); assertEquals(s.getAdditionalPropertiesBoolean(), Boolean.FALSE, "AdditionalProperties (Boolean type) is expected to be false"); - assertEquals(s.getAdditionalPropertiesSchema(), null, - "AdditionalProperties (Schema type) is expected to be null"); + s2 = s.getAdditionalPropertiesSchema(); + assertNotNull(s2, "AdditionalProperties (Schema type) is expected to be non-null"); + assertEquals(s2.getBooleanSchema(), Boolean.FALSE, + "AdditionalProperties (Schema type) is expected to return a boolean-false schema"); + s.setAdditionalPropertiesSchema(null); assertEquals(s.getAdditionalPropertiesBoolean(), null, "AdditionalProperties (Boolean type) is expected to be null"); assertEquals(s.getAdditionalPropertiesSchema(), null, "AdditionalProperties (Schema type) is expected to be null"); + s.setExamples(null); + s.setExample("example1"); + assertEquals(s.getExample(), "example1", "Example is expected to be set"); + assertNull(s.getExamples(), "Examples should be null"); + s.setExamples(Arrays.asList("example2", "example3")); + assertEquals(s.getExample(), "example1", "Example should not be affected by settings examples"); + assertThat("Examples should be set", s.getExamples(), contains("example2", "example3")); + s.setExample("example4"); + assertEquals(s.getExample(), "example4", "Example should be set"); + assertThat("Examples should not be affected by example", s.getExamples(), contains("example2", "example3")); + final Schema allOf = createConstructibleInstance(Schema.class); checkSameObject(s, s.addAllOf(allOf)); checkListEntry(s.getAllOf(), allOf); @@ -1124,7 +1147,285 @@ public void schemaTest() { checkListEntry(s.getRequired(), required); final String otherRequiredValue = new String("otherRequired"); - checkListImmutable(s, Schema::getEnumeration, otherRequiredValue); + checkListImmutable(s, Schema::getRequired, otherRequiredValue); + + final String dependentSchemaKey = "myDependentSchemaKey"; + final Schema dependentSchemaValue = createConstructibleInstance(Schema.class); + checkSameObject(s, s.addDependentSchema(dependentSchemaKey, dependentSchemaValue)); + checkMapEntry(s.getDependentSchemas(), dependentSchemaKey, dependentSchemaValue); + assertEquals(s.getDependentSchemas().size(), 1, "The map is expected to contain one entry."); + s.removeDependentSchema(dependentSchemaKey); + assertEquals(s.getDependentSchemas().size(), 0, "The map is expected to be empty."); + + final String dependentSchemaKey2 = "myDependentSchemaKey2"; + final Schema dependentSchemaValue2 = createConstructibleInstance(Schema.class); + s.setDependentSchemas(Collections.singletonMap(dependentSchemaKey2, dependentSchemaValue2)); + checkMapEntry(s.getDependentSchemas(), dependentSchemaKey2, dependentSchemaValue2); + assertEquals(s.getDependentSchemas().size(), 1, "The map is expected to contain one entry."); + checkSameObject(s, s.addDependentSchema(dependentSchemaKey, dependentSchemaValue)); + checkMapEntry(s.getDependentSchemas(), dependentSchemaKey, dependentSchemaValue); + assertEquals(s.getDependentSchemas().size(), 2, "The map is expected to contain two entries."); + + final Schema otherDependentSchemaValue = createConstructibleInstance(Schema.class); + checkMapImmutable(s, Schema::getDependentSchemas, "otherDependentSchemaKey", otherDependentSchemaValue); + checkNullValueInAdd(s::getDependentSchemas, s::addDependentSchema, "otherDependentSchemaKey", + dependentSchemaValue); + + final Schema prefixItem = createConstructibleInstance(Schema.class); + checkSameObject(s, s.addPrefixItem(prefixItem)); + checkListEntry(s.getPrefixItems(), prefixItem); + assertEquals(s.getPrefixItems().size(), 1, "The list is expected to contain one entry."); + s.removePrefixItem(prefixItem); + assertEquals(s.getPrefixItems().size(), 0, "The list is expected to be empty."); + + final Schema prefixItem2 = createConstructibleInstance(Schema.class); + s.setPrefixItems(Collections.singletonList(prefixItem2)); + assertEquals(s.getPrefixItems().size(), 1, "The list is expected to contain one entry."); + checkListEntry(s.getPrefixItems(), prefixItem2); + checkSameObject(s, s.addPrefixItem(prefixItem)); + assertEquals(s.getPrefixItems().size(), 2, "The list is expected to contain two entries."); + checkListEntry(s.getPrefixItems(), prefixItem); + + final Schema otherPrefixItemValue = createConstructibleInstance(Schema.class); + checkListImmutable(s, Schema::getPrefixItems, otherPrefixItemValue); + + final String patternPropertyKey = "myPatternPropertyKey"; + final Schema patternPropertyValue = createConstructibleInstance(Schema.class); + checkSameObject(s, s.addPatternProperty(patternPropertyKey, patternPropertyValue)); + checkMapEntry(s.getPatternProperties(), patternPropertyKey, patternPropertyValue); + assertEquals(s.getPatternProperties().size(), 1, "The map is expected to contain one entry."); + s.removePatternProperty(patternPropertyKey); + assertEquals(s.getPatternProperties().size(), 0, "The map is expected to be empty."); + + final String patternPropertyKey2 = "myPatternPropertyKey2"; + final Schema patternPropertyValue2 = createConstructibleInstance(Schema.class); + s.setPatternProperties(Collections.singletonMap(patternPropertyKey2, patternPropertyValue2)); + checkMapEntry(s.getPatternProperties(), patternPropertyKey2, patternPropertyValue2); + assertEquals(s.getPatternProperties().size(), 1, "The map is expected to contain one entry."); + checkSameObject(s, s.addPatternProperty(patternPropertyKey, patternPropertyValue)); + checkMapEntry(s.getPatternProperties(), patternPropertyKey, patternPropertyValue); + assertEquals(s.getPatternProperties().size(), 2, "The map is expected to contain two entries."); + + final Schema otherPatternPropertyValue = createConstructibleInstance(Schema.class); + checkMapImmutable(s, Schema::getPatternProperties, "otherPatternPropertyKey", otherPatternPropertyValue); + checkNullValueInAdd(s::getPatternProperties, s::addPatternProperty, "otherPatternPropertyKey", + patternPropertyValue); + + final String dependentRequiredKey = "myDependentRequiredKey"; + final List dependentRequiredValue = Collections.singletonList("myDependentRequired"); + checkSameObject(s, s.addDependentRequired(dependentRequiredKey, dependentRequiredValue)); + checkMapEntry(s.getDependentRequired(), dependentRequiredKey, dependentRequiredValue); + assertEquals(s.getDependentRequired().size(), 1, "The map is expected to contain one entry."); + s.removeDependentRequired(dependentRequiredKey); + assertEquals(s.getDependentRequired().size(), 0, "The map is expected to be empty."); + + final String dependentRequiredKey2 = "myDependentRequiredKey2"; + final List dependentRequiredValue2 = Collections.singletonList("myDependentRequired2"); + s.setDependentRequired(Collections.singletonMap(dependentRequiredKey2, dependentRequiredValue2)); + checkMapEntry(s.getDependentRequired(), dependentRequiredKey2, dependentRequiredValue2); + assertEquals(s.getDependentRequired().size(), 1, "The map is expected to contain one entry."); + checkSameObject(s, s.addDependentRequired(dependentRequiredKey, dependentRequiredValue)); + checkMapEntry(s.getDependentRequired(), dependentRequiredKey, dependentRequiredValue); + assertEquals(s.getDependentRequired().size(), 2, "The map is expected to contain two entries."); + + final List otherDependentRequiredValue = Collections.singletonList("myOtherDependentRequired"); + checkMapImmutable(s, Schema::getDependentRequired, "otherDependentRequiredKey", otherDependentRequiredValue); + checkNullValueInAdd(s::getDependentRequired, s::addDependentRequired, "otherDependentRequiredKey", + dependentRequiredValue); + } + + @SuppressWarnings("deprecation") // Testing deprecated Schema methods + @Test + public void testSchemaArbitraryProperties() { + Schema s = createConstructibleInstance(Schema.class); + + testSchemaProperty(s, "discriminator", Schema::getDiscriminator, Schema::setDiscriminator, + createConstructibleInstance(Discriminator.class)); + testSchemaProperty(s, "title", Schema::getTitle, Schema::setTitle, "test title"); + testSchemaProperty(s, "default", Schema::getDefaultValue, Schema::setDefaultValue, "test"); + testSchemaListProperty(s, "enum", Schema::getEnumeration, Schema::setEnumeration, "a"); + testSchemaProperty(s, "multipleOf", Schema::getMultipleOf, Schema::setMultipleOf, new BigDecimal("3")); + testSchemaProperty(s, "maximum", Schema::getMaximum, Schema::setMaximum, new BigDecimal("3")); + testSchemaProperty(s, "exclusiveMaximum", Schema::getExclusiveMaximum, Schema::setExclusiveMaximum, + new BigDecimal("3")); + testSchemaProperty(s, "minimum", Schema::getMinimum, Schema::setMinimum, new BigDecimal("3")); + testSchemaProperty(s, "exclusiveMinimum", Schema::getExclusiveMinimum, Schema::setExclusiveMinimum, + new BigDecimal("3")); + testSchemaProperty(s, "maxLength", Schema::getMaxLength, Schema::setMaxLength, 17); + testSchemaProperty(s, "minLength", Schema::getMinLength, Schema::setMinLength, 5); + testSchemaProperty(s, "pattern", Schema::getPattern, Schema::setPattern, "[a-z]+"); + testSchemaProperty(s, "maxItems", Schema::getMaxItems, Schema::setMaxItems, 5); + testSchemaProperty(s, "minItems", Schema::getMinItems, Schema::setMinItems, 3); + testSchemaProperty(s, "uniqueItems", Schema::getUniqueItems, Schema::setUniqueItems, true); + testSchemaProperty(s, "maxProperties", Schema::getMaxProperties, Schema::setMaxProperties, 10); + testSchemaProperty(s, "minProperties", Schema::getMinProperties, Schema::setMinProperties, 8); + testSchemaListProperty(s, "required", Schema::getRequired, Schema::setRequired, "propName"); + testSchemaListProperty(s, "type", Schema::getType, Schema::setType, Schema.SchemaType.OBJECT); + testSchemaProperty(s, "not", Schema::getNot, Schema::setNot, createConstructibleInstance(Schema.class)); + testSchemaMapProperty(s, "properties", Schema::getProperties, Schema::setProperties, + createConstructibleInstance(Schema.class)); + testSchemaProperty(s, "additionalProperties", Schema::getAdditionalPropertiesSchema, + Schema::setAdditionalPropertiesSchema, + createConstructibleInstance(Schema.class)); + testSchemaProperty(s, "description", Schema::getDescription, Schema::setDescription, "test schema"); + testSchemaProperty(s, "format", Schema::getFormat, Schema::setFormat, "date-time"); + testSchemaProperty(s, "readOnly", Schema::getReadOnly, Schema::setReadOnly, true); + testSchemaProperty(s, "writeOnly", Schema::getWriteOnly, Schema::setWriteOnly, true); + testSchemaProperty(s, "example", Schema::getExample, Schema::setExample, "test"); + testSchemaProperty(s, "externalDocs", Schema::getExternalDocs, Schema::setExternalDocs, + createConstructibleInstance(ExternalDocumentation.class)); + testSchemaProperty(s, "deprecated", Schema::getDeprecated, Schema::setDeprecated, true); + testSchemaProperty(s, "xml", Schema::getXml, Schema::setXml, createConstructibleInstance(XML.class)); + testSchemaProperty(s, "items", Schema::getItems, Schema::setItems, createConstructibleInstance(Schema.class)); + testSchemaListProperty(s, "allOf", Schema::getAllOf, Schema::setAllOf, + createConstructibleInstance(Schema.class)); + testSchemaListProperty(s, "anyOf", Schema::getAnyOf, Schema::setAnyOf, + createConstructibleInstance(Schema.class)); + testSchemaListProperty(s, "oneOf", Schema::getOneOf, Schema::setOneOf, + createConstructibleInstance(Schema.class)); + testSchemaProperty(s, "$schema", Schema::getSchemaDialect, Schema::setSchemaDialect, "http://test.dialect"); + testSchemaProperty(s, "$comment", Schema::getComment, Schema::setComment, "about this schema"); + testSchemaProperty(s, "if", Schema::getIfSchema, Schema::setIfSchema, + createConstructibleInstance(Schema.class)); + testSchemaProperty(s, "then", Schema::getThenSchema, Schema::setThenSchema, + createConstructibleInstance(Schema.class)); + testSchemaProperty(s, "else", Schema::getElseSchema, Schema::setElseSchema, + createConstructibleInstance(Schema.class)); + testSchemaMapProperty(s, "dependentSchemas", Schema::getDependentSchemas, Schema::setDependentSchemas, + createConstructibleInstance(Schema.class)); + testSchemaListProperty(s, "prefixItems", Schema::getPrefixItems, Schema::setPrefixItems, + createConstructibleInstance(Schema.class)); + testSchemaProperty(s, "contains", Schema::getContains, Schema::setContains, + createConstructibleInstance(Schema.class)); + testSchemaMapProperty(s, "patternProperties", Schema::getPatternProperties, Schema::setPatternProperties, + createConstructibleInstance(Schema.class)); + testSchemaProperty(s, "propertyNames", Schema::getPropertyNames, Schema::setPropertyNames, + createConstructibleInstance(Schema.class)); + testSchemaProperty(s, "unevaluatedItems", Schema::getUnevaluatedItems, Schema::setUnevaluatedItems, + createConstructibleInstance(Schema.class)); + testSchemaProperty(s, "unevaluatedProperties", Schema::getUnevaluatedProperties, + Schema::setUnevaluatedProperties, createConstructibleInstance(Schema.class)); + testSchemaProperty(s, "const", Schema::getConstValue, Schema::setConstValue, "value"); + testSchemaProperty(s, "maxContains", Schema::getMaxContains, Schema::setMaxContains, 5); + testSchemaProperty(s, "minContains", Schema::getMinContains, Schema::setMinContains, 3); + testSchemaMapProperty(s, "dependentRequired", Schema::getDependentRequired, Schema::setDependentRequired, + Arrays.asList("a", "b")); + testSchemaProperty(s, "contentEncoding", Schema::getContentEncoding, Schema::setContentEncoding, "base64"); + testSchemaProperty(s, "contentMediaType", Schema::getContentMediaType, Schema::setContentMediaType, + "test/plain"); + testSchemaProperty(s, "contentSchema", Schema::getContentSchema, Schema::setContentSchema, + createConstructibleInstance(Schema.class)); + testSchemaListProperty(s, "examples", Schema::getExamples, Schema::setExamples, "foo"); + } + + public void testSchemaProperty(Schema testSchema, String name, Function getter, + BiConsumer setter, V testValue) { + // Set with the setter + setter.accept(testSchema, testValue); + assertSame(getter.apply(testSchema), testValue, + "Getter should return the same instance as was set for property " + name); + assertSame(testSchema.get(name), testValue, + "Generic getter should return same instance as was set for property " + name); + + // Clear with the setter + setter.accept(testSchema, null); + assertNull(getter.apply(testSchema), "Getter should return null for property " + name); + assertNull(testSchema.get(name), "Generic access should return null for property " + name); + + // Set with generic access + testSchema.set(name, testValue); + assertSame(getter.apply(testSchema), testValue, + "Getter should return the same instance as was set for property " + name); + assertSame(testSchema.get(name), testValue, + "Generic getter should return same instance as was set for property " + name); + + // Clear with generic access + testSchema.set(name, null); + assertNull(getter.apply(testSchema), "Getter should return null for property " + name); + assertNull(testSchema.get(name), "Generic access should return null for property " + name); + } + + public void testSchemaMapProperty(Schema testSchema, String name, + Function> getter, BiConsumer> setter, + V testValue) { + // Set with the setter + Map testMap = new HashMap<>(); + testMap.put("test1", testValue); + + setter.accept(testSchema, testMap); + testMapFromGetter(() -> getter.apply(testSchema), testMap, name); + testMapFromGetter(() -> testSchema.get(name), testMap, name); + + // Clear with the setter + setter.accept(testSchema, null); + assertNull(getter.apply(testSchema), "Getter should return null for property " + name); + assertNull(testSchema.get(name), "Generic access should return null for property " + name); + + // Set with generic access + testSchema.set(name, testMap); + testMapFromGetter(() -> getter.apply(testSchema), testMap, name); + testMapFromGetter(() -> testSchema.get(name), testMap, name); + + // Clear with generic access + testSchema.set(name, null); + assertNull(getter.apply(testSchema), "Getter should return null for property " + name); + assertNull(testSchema.get(name), "Generic access should return null for property " + name); + } + + private void testMapFromGetter(Supplier getter, Map expected, + String name) { + assertThat("Test error: expected may not be empty", expected, not(anEmptyMap())); + + Object actualObj = getter.get(); + assertEquals(actualObj, expected, "Getter should return value that was set for property " + name); + + // Check that all values are the same instance + Map actualMap = (Map) actualObj; + expected.forEach((k, v) -> { + assertSame(v, actualMap.get(k), + "Getter should return value with same instance for key " + v + " property " + name); + }); + } + + public void testSchemaListProperty(Schema testSchema, String name, + Function> getter, BiConsumer> setter, + V testValue) { + // Set with the setter + List testList = new ArrayList<>(); + testList.add(testValue); + + setter.accept(testSchema, testList); + testListFromGetter(() -> getter.apply(testSchema), testList, name); + testListFromGetter(() -> testSchema.get(name), testList, name); + + // Clear with the setter + setter.accept(testSchema, null); + assertNull(getter.apply(testSchema), "Getter should return null for property " + name); + assertNull(testSchema.get(name), "Generic access should return null for property " + name); + + // Set with generic access + testSchema.set(name, testList); + testListFromGetter(() -> getter.apply(testSchema), testList, name); + testListFromGetter(() -> testSchema.get(name), testList, name); + + // Clear with generic access + testSchema.set(name, null); + assertNull(getter.apply(testSchema), "Getter should return null for property " + name); + assertNull(testSchema.get(name), "Generic access should return null for property " + name); + } + + private void testListFromGetter(Supplier getter, List expected, String name) { + assertThat("Test error: expected may not be empty", expected, not(empty())); + + Object actualObj = getter.get(); + assertEquals(actualObj, expected, "Getter should return value that was set for property " + name); + + // Check that all values are the same instance + List actualList = (List) actualObj; + expected.forEach((expectedV) -> { + assertTrue(actualList.stream().anyMatch((actualV) -> expectedV == actualV), + "Getter should return value containing " + expectedV + " for property " + name); + }); } @Test @@ -1524,6 +1825,7 @@ private void processConstructibleProperty(Constructible o, Property p, Class "\" is expected to be equal to the value that was set."); } } + p.invokeSetter(o, null); } // Returns instances for testing getter, setter and builder methods. @@ -1546,21 +1848,21 @@ private Object getInstanceOf(Class clazz, boolean alternateEnumValue) { } else if (clazz == String.class) { return new String("value"); } else if (clazz == Boolean.class || clazz == Boolean.TYPE) { - return new Boolean(true); + return Boolean.valueOf(true); } else if (clazz == Byte.class || clazz == Byte.TYPE) { - return new Byte((byte) 1); + return Byte.valueOf((byte) 1); } else if (clazz == Short.class || clazz == Short.TYPE) { - return new Short((short) 1); + return Short.valueOf((short) 1); } else if (clazz == Integer.class || clazz == Integer.TYPE) { - return new Integer(1); + return Integer.valueOf(1); } else if (clazz == Long.class || clazz == Long.TYPE) { - return new Long(1L); + return Long.valueOf(1L); } else if (clazz == Float.class || clazz == Float.TYPE) { - return new Float(1); + return Float.valueOf(1); } else if (clazz == Double.class || clazz == Double.TYPE) { - return new Double(1); + return Double.valueOf(1); } else if (clazz == Character.class || clazz == Character.TYPE) { - return new Character('a'); + return Character.valueOf('a'); } else if (clazz == BigInteger.class) { return new BigInteger("1"); } else if (clazz == BigDecimal.class) { diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java index cfdf6438c..7c638c722 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java @@ -16,11 +16,15 @@ package org.eclipse.microprofile.openapi.tck; +import static org.eclipse.microprofile.openapi.tck.utils.TCKMatchers.itemOrSingleton; +import static org.eclipse.microprofile.openapi.tck.utils.TCKMatchers.number; import static org.hamcrest.Matchers.both; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.equalToIgnoringCase; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasSize; @@ -169,7 +173,7 @@ public void testAvailabilityGetParameter(String type) { vr.body(query + ".in", both(hasSize(1)).and(contains("query"))); vr.body(query + ".description", both(hasSize(1)).and(contains(list.get(i)[1]))); vr.body(query + ".required", both(hasSize(1)).and(contains(true))); - vr.body(query + ".schema.type", both(hasSize(1)).and(contains("string"))); + vr.body(query + ".schema.type", both(hasSize(1)).and(contains(itemOrSingleton("string")))); } vr.body(availabilityParameters + ".findAll { it.name == 'numberOfAdults' }.schema.minimum", @@ -211,12 +215,58 @@ public void testSchema(String type) { vr.body("components.schemas.AirlinesRef.$ref", equalTo("#/components/schemas/Airlines")); vr.body("components.schemas.Airlines.title", equalTo("Airlines")); vr.body("paths.'/modelReader/bookings'.post.responses.'201'.content.'text/plain'.schema.type", - equalTo("string")); + itemOrSingleton("string")); vr.body("components.schemas.id.format", equalTo("int32")); vr.body("paths.'/modelReader/bookings'.post.responses.'201'.content.'text/plain'.schema.description", equalTo("id of the new booking")); } + @Test(dataProvider = "formatProvider") + public void testSchemaCustomProperties(String type) { + ValidatableResponse vr = callEndpoint(type); + vr.body("components.schemas.custom.$schema", equalTo("http://example.com/myCustomSchema")); + vr.body("components.schemas.custom.shortKey", number(1)); + vr.body("components.schemas.custom.intKey", number(2)); + vr.body("components.schemas.custom.longKey", number(3)); + vr.body("components.schemas.custom.booleanKey", equalTo(true)); + vr.body("components.schemas.custom.charKey", equalTo("a")); + vr.body("components.schemas.custom.stringKey", equalTo("string")); + vr.body("components.schemas.custom.floatKey", number(3.5)); + vr.body("components.schemas.custom.doubleKey", number(3.5)); + vr.body("components.schemas.custom.bigDecimalKey", number(3.5)); + vr.body("components.schemas.custom.bigIntegerKey", number(7)); + vr.body("components.schemas.custom.extDocKey.description", equalTo("test")); + vr.body("components.schemas.custom.operationKey.description", equalTo("test")); + vr.body("components.schemas.custom.pathItemKey.description", equalTo("test")); + vr.body("components.schemas.custom.pathsKey.test.description", equalTo("test")); + vr.body("components.schemas.custom.callbacKey.description", equalTo("test")); + vr.body("components.schemas.custom.exampleKey.value", equalTo("test")); + vr.body("components.schemas.custom.headerKey.description", equalTo("test")); + vr.body("components.schemas.custom.contactKey.name", equalTo("test")); + vr.body("components.schemas.custom.infoKey.title", equalTo("test")); + vr.body("components.schemas.custom.licenseKey.name", equalTo("test")); + vr.body("components.schemas.custom.linkKey.operationId", equalTo("test")); + vr.body("components.schemas.custom.contentKey.test.example", equalTo("test")); + vr.body("components.schemas.custom.discriminatorKey.propertyName", equalTo("test")); + vr.body("components.schemas.custom.schemaKey.title", equalTo("test")); + vr.body("components.schemas.custom.xmlKey.name", equalTo("test")); + vr.body("components.schemas.custom.parameterKey.name", equalTo("test")); + vr.body("components.schemas.custom.requestBodyKey.content.test.example", equalTo("test")); + vr.body("components.schemas.custom.apiResponseKey.description", equalTo("test")); + vr.body("components.schemas.custom.apiResponsesKey.200.description", equalTo("test")); + vr.body("components.schemas.custom.oAuthFlowKey.authorizationUrl", equalTo("http://example.com")); + vr.body("components.schemas.custom.oAuthFlowsKey.implicit.authorizationUrl", equalTo("http://example.com")); + vr.body("components.schemas.custom.securityReqKey.scheme", contains("test")); + vr.body("components.schemas.custom.securitySchemeKey.type", equalTo("http")); + vr.body("components.schemas.custom.serverKey.url", equalTo("http://example.com")); + vr.body("components.schemas.custom.serverVarKey.default", equalTo("test")); + vr.body("components.schemas.custom.tagKey.name", equalTo("test")); + vr.body("components.schemas.custom.enumKey", equalToIgnoringCase("MONDAY")); + vr.body("components.schemas.custom.listKey", hasItem("test")); + vr.body("components.schemas.custom.listKey.name", hasItem("test")); + vr.body("components.schemas.custom.mapKey.test", equalToIgnoringCase("MONDAY")); + } + @Test(dataProvider = "formatProvider") public void testExampleObject(String type) { ValidatableResponse vr = callEndpoint(type); @@ -291,7 +341,7 @@ public void testContentInAPIResponse(String type) { String content1 = "paths.'/availability'.get.responses.'200'.content.'application/json'"; vr.body(content1, notNullValue()); - vr.body(content1 + ".schema.type", equalTo("array")); + vr.body(content1 + ".schema.type", itemOrSingleton("array")); vr.body(content1 + ".schema.items", notNullValue()); } } diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/OASConfigSchemaTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/OASConfigSchemaTest.java index e42285ba6..e5b51949a 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/OASConfigSchemaTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/OASConfigSchemaTest.java @@ -16,14 +16,17 @@ package org.eclipse.microprofile.openapi.tck; +import static org.eclipse.microprofile.openapi.tck.utils.TCKMatchers.itemOrSingleton; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; import static org.hamcrest.collection.IsMapWithSize.aMapWithSize; import java.util.Map; import org.hamcrest.Matcher; +import org.hamcrest.Matchers; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.WebArchive; @@ -50,12 +53,14 @@ public void testSchemaConfigApplied(String type) { anyOf(epochSecondsSchema(), epochSecondsRef())); } - private Matcher> epochSecondsSchema() { - return allOf(aMapWithSize(4), + private Matcher> epochSecondsSchema() { + return Matchers.>allOf( + aMapWithSize(4), hasEntry("title", "Epoch Seconds"), - hasEntry("type", "number"), + hasEntry(is("type"), itemOrSingleton("number")), hasEntry("format", "int64"), - hasEntry("description", "Number of seconds from the epoch of 1970-01-01T00:00:00Z")); + hasEntry("description", + "Number of seconds from the epoch of 1970-01-01T00:00:00Z")); } private Matcher> epochSecondsRef() { diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/PetStoreAppTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/PetStoreAppTest.java index 23b3bd946..024856f19 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/PetStoreAppTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/PetStoreAppTest.java @@ -18,6 +18,7 @@ import static io.restassured.RestAssured.given; import static org.eclipse.microprofile.openapi.tck.utils.TCKMatchers.comparesEqualToNumber; +import static org.eclipse.microprofile.openapi.tck.utils.TCKMatchers.itemOrSingleton; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasEntry; @@ -60,14 +61,10 @@ public void testSchema(String type) { vr.body("paths.'/store/order/{orderId}'.get.responses.'599'.schema", nullValue()); // Numerical properties - vr.body("paths.'/pet/{petId}'.get.parameters.find{ it.name == 'petId' }.schema.maximum", - comparesEqualToNumber(101.0)); vr.body("paths.'/pet/{petId}'.get.parameters.find{ it.name == 'petId' }.schema.exclusiveMaximum", - equalTo(true)); - vr.body("paths.'/pet/{petId}'.get.parameters.find{ it.name == 'petId' }.schema.minimum", - comparesEqualToNumber(9)); + comparesEqualToNumber(101.0)); vr.body("paths.'/pet/{petId}'.get.parameters.find{ it.name == 'petId' }.schema.exclusiveMinimum", - equalTo(true)); + comparesEqualToNumber(9)); vr.body("paths.'/pet/{petId}'.get.parameters.find{ it.name == 'petId' }.schema.multipleOf", comparesEqualToNumber(10)); @@ -218,7 +215,7 @@ public void testRequestBodySchema(String type) { vr.body(schemaObject, allOf(aMapWithSize(3), hasEntry(equalTo("required"), notNullValue()), - hasEntry(equalTo("type"), equalTo("object")), + hasEntry(equalTo("type"), itemOrSingleton("object")), hasEntry(equalTo("properties"), notNullValue()))); } @@ -241,7 +238,7 @@ public void testAPIResponseSchema(String type) { vr.body(schemaObject, allOf(aMapWithSize(3), hasEntry(equalTo("required"), notNullValue()), - hasEntry(equalTo("type"), equalTo("object")), + hasEntry(equalTo("type"), itemOrSingleton("object")), hasEntry(equalTo("properties"), notNullValue()))); } @@ -265,7 +262,7 @@ public void testAPIResponseSchemaDefaultResponseCode(String type) { vr.body(arraySchemaObject, allOf(aMapWithSize(2), - hasEntry(equalTo("type"), equalTo("array")), + hasEntry(equalTo("type"), itemOrSingleton("array")), hasEntry(equalTo("items"), notNullValue()))); String schemaObject = dereference(vr, arraySchemaObject + ".items"); @@ -273,7 +270,7 @@ public void testAPIResponseSchemaDefaultResponseCode(String type) { vr.body(schemaObject, allOf(aMapWithSize(3), hasEntry(equalTo("required"), notNullValue()), - hasEntry(equalTo("type"), equalTo("object")), + hasEntry(equalTo("type"), itemOrSingleton("object")), hasEntry(equalTo("properties"), notNullValue()))); } diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/StaticDocumentCustomDialectTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/StaticDocumentCustomDialectTest.java new file mode 100644 index 000000000..e4ec2f469 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/StaticDocumentCustomDialectTest.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.microprofile.openapi.tck; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.startsWith; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.testng.annotations.Test; + +import io.restassured.response.ValidatableResponse; + +/** + * Test that a static document can contain a schema with a custom dialect + */ +public class StaticDocumentCustomDialectTest extends AppTestBase { + + @Deployment(name = "customdialect", testable = false) + public static WebArchive createDeployment() { + return ShrinkWrap.create(WebArchive.class, "customdialect.war") + .addAsManifestResource("customDialect.yaml", "openapi.yaml"); + } + + @Test(dataProvider = "formatProvider") + public void testStaticDocumentCustomDialect(String type) { + ValidatableResponse vr = callEndpoint(type); + + vr.body("openapi", startsWith("3.1.")); + + final String schemaPath = "paths.'/test'.get.responses.'200'.content.'application/json'.schema"; + vr.body(schemaPath + ".$schema", equalTo("http://example.com/custom")); + vr.body(schemaPath + ".foo", equalTo("bar")); + vr.body(schemaPath + ".baz", equalTo("qux")); + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/beanvalidation/BeanValidationTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/beanvalidation/BeanValidationTest.java index f94d45a69..396f39434 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/beanvalidation/BeanValidationTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/beanvalidation/BeanValidationTest.java @@ -16,8 +16,10 @@ package org.eclipse.microprofile.openapi.tck.beanvalidation; import static org.eclipse.microprofile.openapi.tck.Groups.BEAN_VALIDATION; +import static org.eclipse.microprofile.openapi.tck.utils.TCKMatchers.itemOrSingleton; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import org.eclipse.microprofile.openapi.apps.beanvalidation.BeanValidationApp; @@ -42,21 +44,21 @@ public static WebArchive buildApp() { public void notEmptyStringTest(String format) { ValidatableResponse vr = callEndpoint(format); assertProperty(vr, "notEmptyString", hasEntry("minLength", 1)); - assertProperty(vr, "notEmptyString", hasEntry("type", "string")); + assertProperty(vr, "notEmptyString", hasEntry(is("type"), itemOrSingleton("string"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) public void notEmptyListTest(String format) { ValidatableResponse vr = callEndpoint(format); assertProperty(vr, "notEmptyList", hasEntry("minItems", 1)); - assertProperty(vr, "notEmptyList", hasEntry("type", "array")); + assertProperty(vr, "notEmptyList", hasEntry(is("type"), itemOrSingleton("array"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) public void notEmptyMapTest(String format) { ValidatableResponse vr = callEndpoint(format); assertProperty(vr, "notEmptyMap", hasEntry("minProperties", 1)); - assertProperty(vr, "notEmptyMap", hasEntry("type", "object")); + assertProperty(vr, "notEmptyMap", hasEntry(is("type"), itemOrSingleton("object"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) @@ -70,7 +72,7 @@ public void sizedStringTest(String format) { ValidatableResponse vr = callEndpoint(format); assertProperty(vr, "sizedString", hasEntry("minLength", 2)); assertProperty(vr, "sizedString", hasEntry("maxLength", 7)); - assertProperty(vr, "sizedString", hasEntry("type", "string")); + assertProperty(vr, "sizedString", hasEntry(is("type"), itemOrSingleton("string"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) @@ -78,7 +80,7 @@ public void sizedListTest(String format) { ValidatableResponse vr = callEndpoint(format); assertProperty(vr, "sizedList", hasEntry("minItems", 1)); assertProperty(vr, "sizedList", hasEntry("maxItems", 10)); - assertProperty(vr, "sizedList", hasEntry("type", "array")); + assertProperty(vr, "sizedList", hasEntry(is("type"), itemOrSingleton("array"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) @@ -86,37 +88,39 @@ public void sizedMapTest(String format) { ValidatableResponse vr = callEndpoint(format); assertProperty(vr, "sizedMap", hasEntry("minProperties", 3)); assertProperty(vr, "sizedMap", hasEntry("maxProperties", 5)); - assertProperty(vr, "sizedMap", hasEntry("type", "object")); + assertProperty(vr, "sizedMap", hasEntry(is("type"), itemOrSingleton("object"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) public void maxDecimalInclusiveTest(String format) { ValidatableResponse vr = callEndpoint(format); assertProperty(vr, "maxDecimalInclusive", hasEntry("maximum", 1.5f)); - assertProperty(vr, "maxDecimalInclusive", hasEntry("type", "number")); + assertProperty(vr, "maxDecimalInclusive", not(hasKey("exclusiveMaximum"))); + assertProperty(vr, "maxDecimalInclusive", hasEntry(is("type"), itemOrSingleton("number"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) public void maxDecimalExclusiveTest(String format) { ValidatableResponse vr = callEndpoint(format); - assertProperty(vr, "maxDecimalExclusive", hasEntry("maximum", 1.5f)); - assertProperty(vr, "maxDecimalExclusive", hasEntry("exclusiveMaximum", true)); - assertProperty(vr, "maxDecimalExclusive", hasEntry("type", "number")); + assertProperty(vr, "maxDecimalExclusive", hasEntry("exclusiveMaximum", 1.5f)); + assertProperty(vr, "maxDecimalExclusive", not(hasKey("maximum"))); + assertProperty(vr, "maxDecimalExclusive", hasEntry(is("type"), itemOrSingleton("number"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) public void minDecimalInclusiveTest(String format) { ValidatableResponse vr = callEndpoint(format); assertProperty(vr, "minDecimalInclusive", hasEntry("minimum", 3.25f)); - assertProperty(vr, "minDecimalInclusive", hasEntry("type", "number")); + assertProperty(vr, "minDecimalInclusive", not(hasKey("exclusiveMinimum"))); + assertProperty(vr, "minDecimalInclusive", hasEntry(is("type"), itemOrSingleton("number"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) public void minDecimalExclusiveTest(String format) { ValidatableResponse vr = callEndpoint(format); - assertProperty(vr, "minDecimalExclusive", hasEntry("minimum", 3.25f)); - assertProperty(vr, "minDecimalExclusive", hasEntry("exclusiveMinimum", true)); - assertProperty(vr, "minDecimalExclusive", hasEntry("type", "number")); + assertProperty(vr, "minDecimalExclusive", hasEntry("exclusiveMinimum", 3.25f)); + assertProperty(vr, "minDecimalExclusive", not(hasKey("minimum"))); + assertProperty(vr, "minDecimalExclusive", hasEntry(is("type"), itemOrSingleton("number"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) @@ -134,27 +138,29 @@ public void minIntTest(String format) { @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) public void negativeIntTest(String format) { ValidatableResponse vr = callEndpoint(format); - assertProperty(vr, "negativeInt", hasEntry("maximum", 0)); - assertProperty(vr, "negativeInt", hasEntry("exclusiveMaximum", true)); + assertProperty(vr, "negativeInt", hasEntry("exclusiveMaximum", 0)); + assertProperty(vr, "negativeInt", not(hasKey("maximum"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) public void negativeOrZeroIntTest(String format) { ValidatableResponse vr = callEndpoint(format); assertProperty(vr, "negativeOrZeroInt", hasEntry("maximum", 0)); + assertProperty(vr, "negativeOrZeroInt", not(hasKey("exclusiveMaximum"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) public void positiveIntTest(String format) { ValidatableResponse vr = callEndpoint(format); - assertProperty(vr, "positiveInt", hasEntry("minimum", 0)); - assertProperty(vr, "positiveInt", hasEntry("exclusiveMinimum", true)); + assertProperty(vr, "positiveInt", hasEntry("exclusiveMinimum", 0)); + assertProperty(vr, "positiveInt", not(hasKey("minimum"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) public void positiveOrZeroIntTest(String format) { ValidatableResponse vr = callEndpoint(format); assertProperty(vr, "positiveOrZeroInt", hasEntry("minimum", 0)); + assertProperty(vr, "positiveOrZeroInt", not(hasKey("exclusiveMinimum"))); } @Test(dataProvider = "formatProvider", groups = BEAN_VALIDATION) @@ -180,7 +186,7 @@ public void parameterTest(String format) { ValidatableResponse vr = callEndpoint(format); String schemaPath = dereference(vr, "paths.'/parameter/{test}'.post.parameters[0]", "schema"); vr.body(schemaPath, hasEntry("maxLength", 6)); - vr.body(schemaPath, hasEntry("type", "string")); + vr.body(schemaPath, hasEntry(is("type"), itemOrSingleton("string"))); } /** diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/utils/TCKMatchers.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/utils/TCKMatchers.java index b819359a4..61e120700 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/utils/TCKMatchers.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/utils/TCKMatchers.java @@ -15,12 +15,17 @@ */ package org.eclipse.microprofile.openapi.tck.utils; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.comparator.ComparatorMatcherBuilder.comparedBy; import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; import java.util.Comparator; +import org.hamcrest.Description; import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; public final class TCKMatchers { @@ -55,4 +60,113 @@ private TCKMatchers() { public static Matcher comparesEqualToNumber(Number expected) { return comparedBy(NUMERIC_COMPARATOR).comparesEqualTo(expected); } + + /** + * Creates a matcher which matches an item or a {@link Collection} containing just that item + * + * @param itemMatcher + * the matcher for the item + * @return the matcher + */ + public static Matcher itemOrSingleton(Matcher itemMatcher) { + return new ItemOrSingletonMatcher(itemMatcher); + } + + /** + * Creates a matcher which matches an item or a {@link Collection} containing just that item + * + * @param item + * the item + * @return the matcher + */ + public static Matcher itemOrSingleton(Object item) { + return itemOrSingleton(equalTo(item)); + } + + /** + * Creates a matcher which matches numbers based on their numeric value without considering their type. + *

+ * Both the expected and actual value are converted to {@link BigDecimal} and compared using + * {@link BigDecimal#compareTo(BigDecimal) compareTo}. + * + * @param number + * the expected number + * @return the matcher + */ + public static Matcher number(Number number) { + return new NumericEqualityMatcher(number); + } + + public static class ItemOrSingletonMatcher extends TypeSafeDiagnosingMatcher { + + private Matcher baseMatcher; + + public ItemOrSingletonMatcher(Matcher baseMatcher) { + super(Object.class); + this.baseMatcher = baseMatcher; + } + + @Override + public void describeTo(Description description) { + description.appendText("An item or singleton list containing ").appendDescriptionOf(baseMatcher); + } + + @Override + protected boolean matchesSafely(Object item, Description mismatchDescription) { + if (item instanceof Collection) { + Collection collection = (Collection) item; + if (collection.size() != 1) { + mismatchDescription.appendText("object is a collection of size ").appendValue(collection.size()); + return false; + } + item = collection.iterator().next(); + } + + boolean result = baseMatcher.matches(item); + if (!result) { + baseMatcher.describeMismatch(item, mismatchDescription); + } + return result; + } + } + + public static class NumericEqualityMatcher extends TypeSafeDiagnosingMatcher { + + private BigDecimal expected; + + public NumericEqualityMatcher(Number expected) { + this.expected = toBigDecimal(expected); + } + + @Override + public void describeTo(Description desc) { + desc.appendText("A number equal to ").appendValue(expected); + } + + @Override + protected boolean matchesSafely(Number item, Description mismatchDescription) { + BigDecimal actual = toBigDecimal(item); + mismatchDescription.appendText("was: ").appendValue(item); + return expected.compareTo(actual) == 0; + } + + private static BigDecimal toBigDecimal(Number number) { + if (number instanceof Integer) { + return new BigDecimal((Integer) number); + } else if (number instanceof Short) { + return new BigDecimal((Short) number); + } else if (number instanceof Long) { + return new BigDecimal((Long) number); + } else if (number instanceof Float) { + return new BigDecimal((Float) number); + } else if (number instanceof Double) { + return new BigDecimal((Double) number); + } else if (number instanceof BigInteger) { + return new BigDecimal((BigInteger) number); + } else { + return new BigDecimal(number.doubleValue()); + } + } + + } } diff --git a/tck/src/main/resources/customDialect.yaml b/tck/src/main/resources/customDialect.yaml new file mode 100644 index 000000000..e6d80a43e --- /dev/null +++ b/tck/src/main/resources/customDialect.yaml @@ -0,0 +1,31 @@ +# Copyright (c) 2024 Contributors to the Eclipse Foundation +#

+# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +#

+# http://www.apache.org/licenses/LICENSE-2.0 +#

+# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +openapi: 3.1.0 +info: + title: Custom dialect schema example + version: 1.0.0 +paths: + /test: + get: + description: Returns data in a custom schema dialect + responses: + '200': + content: + application/json: + schema: + $schema: http://example.com/custom + description: This is an example of a completely custom schema which should be passed through untouched + foo: bar + baz: qux \ No newline at end of file diff --git a/tck/src/main/resources/openapi.yaml b/tck/src/main/resources/openapi.yaml index 94bc335bc..94016fcf6 100644 --- a/tck/src/main/resources/openapi.yaml +++ b/tck/src/main/resources/openapi.yaml @@ -30,7 +30,8 @@ paths: schema: type: string format: uri - example: https://tonys-server.com + examples: + - https://tonys-server.com responses: '201': description: subscription successfully created @@ -44,7 +45,8 @@ paths: subscriptionId: description: this unique identifier allows management of the subscription type: string - example: 2531329f-fb09-4ef7-887e-84e648214436 + examples: + - 2531329f-fb09-4ef7-887e-84e648214436 callbacks: # the name `onData` is a convenience locator onData: diff --git a/tck/src/main/resources/simpleapi.yaml b/tck/src/main/resources/simpleapi.yaml index a081863ec..7e414995b 100644 --- a/tck/src/main/resources/simpleapi.yaml +++ b/tck/src/main/resources/simpleapi.yaml @@ -133,14 +133,17 @@ components: id: type: string format: uuid - example: d290f1ee-6c54-4b01-90e6-d701748f0851 + examples: + - d290f1ee-6c54-4b01-90e6-d701748f0851 name: type: string - example: Widget Adapter + examples: + - Widget Adapter releaseDate: type: string format: int32 - example: '2016-08-29T09:12:33.001Z' + examples: + - '2016-08-29T09:12:33.001Z' manufacturer: $ref: '#/components/schemas/Manufacturer' Manufacturer: @@ -149,12 +152,15 @@ components: properties: name: type: string - example: ACME Corporation + examples: + - ACME Corporation homePage: type: string format: url - example: 'https://www.acme-corp.com' + examples: + - 'https://www.acme-corp.com' phone: type: string - example: 408-867-5309 + examples: + - 408-867-5309 type: object \ No newline at end of file