From 908a398e4095d200ffc0a4213c807747471f645b Mon Sep 17 00:00:00 2001 From: Andrew Rouse Date: Thu, 11 Apr 2024 14:43:03 +0100 Subject: [PATCH 1/3] Schema annotations: update for OpenAPI 3.1 --- .../annotations/media/DependentRequired.java | 41 +++ .../annotations/media/DependentSchema.java | 41 +++ .../annotations/media/PatternProperty.java | 47 ++++ .../openapi/annotations/media/Schema.java | 261 +++++++++++++++--- .../annotations/media/SchemaProperty.java | 234 ++++++++++++++-- .../annotations/media/package-info.java | 2 +- 6 files changed, 564 insertions(+), 62 deletions(-) create mode 100644 api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/DependentRequired.java create mode 100644 api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/DependentSchema.java create mode 100644 api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/PatternProperty.java diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/DependentRequired.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/DependentRequired.java new file mode 100644 index 00000000..786dcdae --- /dev/null +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/DependentRequired.java @@ -0,0 +1,41 @@ +/* + * 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.annotations.media; + +/** + * A property name and an associated list of other property names. + *

+ * Used with {@link Schema#dependentRequired()}, if an object has a property named {@link #name()}, it must also have + * properties with the names in {@link #requires()}. + * + * @see Schema#dependentRequired() + */ +public @interface DependentRequired { + + /** + * The property name to look for + * + * @return a property name + */ + String name(); + + /** + * The property names that an object is required to have, if it has a property named {@link #name()} + * + * @return the required property names + */ + String[] requires(); +} diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/DependentSchema.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/DependentSchema.java new file mode 100644 index 00000000..4e7b0091 --- /dev/null +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/DependentSchema.java @@ -0,0 +1,41 @@ +/* + * 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.annotations.media; + +/** + * A property name and an associated schema. + *

+ * Used with {@link Schema#dependentSchemas()}, if an instance has a property named {@link #name()}, then it must + * validate against {@link #schema()}. + * + * @see Schema#dependentSchemas() + */ +public @interface DependentSchema { + + /** + * A property name + * + * @return property name + */ + String name(); + + /** + * The schema that an instance must validate against if it has a property named {@link #name()}. + * + * @return a class used to generate a schema which is used to validate objects with properties named {@link #name()} + */ + Class schema(); +} diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/PatternProperty.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/PatternProperty.java new file mode 100644 index 00000000..e9531a53 --- /dev/null +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/PatternProperty.java @@ -0,0 +1,47 @@ +/* + * 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.annotations.media; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A regular expression and an associated schema. + *

+ * Used with {@link Schema#patternProperties()}, properties with names that match {@link #regex()} must have values + * which validate against {@link #schema()}. + * + * @see Schema#patternProperties() + */ +@Target({}) +@Retention(RetentionPolicy.RUNTIME) +public @interface PatternProperty { + + /** + * A regular expression to match against property names. + * + * @return an ECMA-262 regular expression + */ + String regex(); + + /** + * A schema that a property value must validate against + * + * @return a class used to generate a schema used to validate properties with names that match {@link #regex()} + */ + Class schema(); +} diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/Schema.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/Schema.java index d5533585..911af50b 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/Schema.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/Schema.java @@ -29,7 +29,21 @@ /** * The Schema Object allows the definition of input and output data types. These types can be objects, but also - * primitives and arrays. This object is an extended subset of the JSON Schema Specification Wright Draft 00. + * primitives and arrays. This object is an extended subset of the JSON Schema draft specification 2020-12. + * + *

Defining subschemas

+ *

+ * Some of the parameters on this annotation correspond to parts of an OpenAPI document where a subschema is expected, + * for example {@link #allOf()}, {@link #contains()} and {@link #propertyNames()}. Due to limitations in the Java + * language, we can't use a second {@code @Schema} annotation, so instead we allow a class to be provided. A schema will + * be generated from that class by introspecting its annotations, methods and fields. + *

+ * In addition, several special values can be used: + *

* * @see OpenAPI * Specification Schema Object @@ -40,10 +54,7 @@ public @interface Schema { /** - * Marker class for use in {@link #additionalProperties() additionalProperties} to indicate that the corresponding - * schema's {@link org.eclipse.microprofile.openapi.models.media.Schema#setAdditionalPropertiesBoolean(Boolean) - * additionalPropertiesBoolean} value is to be set to boolean {@code true}. The value {@code true} declares that any - * properties in addition to those defined by the {@code properties} attribute of the same schema are valid. + * Marker class to indicate that a boolean {@code true} schema should be used. * * @since 3.1 */ @@ -53,10 +64,7 @@ private True() { } /** - * Marker class for use in {@link #additionalProperties() additionalProperties} to indicate that the corresponding - * schema's {@link org.eclipse.microprofile.openapi.models.media.Schema#setAdditionalPropertiesBoolean(Boolean) - * additionalPropertiesBoolean} value is to be set to boolean {@code false}. The value {@code false} declares that - * no property in addition to those defined by the {@code properties} attribute of the same schema is valid. + * Marker class to indicate that a boolean {@code false} schema should be used. * * @since 3.1 */ @@ -84,9 +92,6 @@ private False() { /** * Provides an array of java class implementations which can be used to describe multiple acceptable schemas. If * more than one match the derived schemas, a validation error will occur. - *

- * Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. - *

* * @return the list of possible classes for a single match **/ @@ -95,9 +100,6 @@ private False() { /** * Provides an array of java class implementations which can be used to describe multiple acceptable schemas. If any * match, the schema will be considered valid. - *

- * Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. - *

* * @return the list of possible class matches **/ @@ -106,9 +108,6 @@ private False() { /** * Provides an array of java class implementations which can be used to describe multiple acceptable schemas. If all * match, the schema will be considered valid. - *

- * Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. - *

* * @return the list of classes to match **/ @@ -134,8 +133,8 @@ private False() { String title() default ""; /** - * Constrains a value such that when divided by the multipleOf, the remainder must be an integer. Ignored if the - * value is 0. + * Constrains a value such that when divided by the multipleOf, the result must be an integer. Ignored if the value + * is {@code 0}. * * @return the multiplier constraint of the schema **/ @@ -186,9 +185,11 @@ private False() { int minLength() default 0; /** - * A pattern that the value must satisfy. Ignored if the value is an empty string. + * A regular expression that the value must satisfy. Ignored if the value is an empty string. + *

+ * If the instance is a string, the regular expression must match the instance. * - * @return the pattern of this schema + * @return the ECMA-262 regular expression to match against **/ String pattern() default ""; @@ -214,7 +215,7 @@ private False() { String[] requiredProperties() default {}; /** - * Mandates whether the annotated item is required or not. + * Specifies whether the annotated item is required or not. * * @return whether or not this schema is required **/ @@ -242,8 +243,10 @@ private False() { /** * Reference value to a Schema definition. *

- * This property provides a reference to an object defined elsewhere. This property and all other properties are - * mutually exclusive. If other properties are defined in addition to the ref property then the result is undefined. + * This property provides a reference to an object defined elsewhere. + *

+ * Unlike {@code ref} on most MP OpenAPI annotations, this property is not mutually exclusive with other + * properties. * * @return a reference to a schema definition **/ @@ -257,8 +260,8 @@ private False() { boolean nullable() default false; /** - * Relevant only for Schema "properties" definitions. Declares the property as "read only". This means that it MAY - * be sent as part of a response but SHOULD NOT be sent as part of the request. + * Declares the property as "read only". This means that it MAY be sent as part of a response but SHOULD NOT be sent + * as part of the request. *

* If the property is marked as readOnly being true and is in the required list, the required will take effect on * the response only. A property MUST NOT be marked as both readOnly and writeOnly being true. @@ -269,8 +272,8 @@ private False() { boolean readOnly() default false; /** - * Relevant only for Schema "properties" definitions. Declares the property as "write only". Therefore, it MAY be - * sent as part of a request but SHOULD NOT be sent as part of the response. + * Declares the property as "write only". This means that it MAY be sent as part of a request but SHOULD NOT be sent + * as part of the response. *

* If the property is marked as writeOnly being true and is in the required list, the required will take effect on * the request only. A property MUST NOT be marked as both readOnly and writeOnly being true. @@ -282,17 +285,26 @@ private False() { /** * A free-form property to include an example of an instance for this schema. - *

- * To represent examples that cannot be naturally represented in JSON or YAML, a string value is used to contain the - * example with escaping where necessary. - *

- * When associated with a specific media type, the example string shall be parsed by the consumer to be treated as - * an object or an array. * * @return an example of this schema + * @deprecated use {@link #examples()} **/ + @Deprecated(since = "4.0") String example() default ""; + /** + * A free-form property to include examples of an instance for this schema. + *

+ * Each example SHOULD validate against this schema. + *

+ * If the schema {@link #type()} is STRING, the value will be interpreted as a literal string, otherwise it will be + * parsed as JSON. + * + * @return an array of examples of this schema + * @since 4.0 + **/ + String[] examples() default {}; + /** * Additional external documentation for this schema. * @@ -463,4 +475,183 @@ private False() { * @since 3.1 */ Extension[] extensions() default {}; + + /** + * A comment to be included in the schema + *

+ * This value is set in the {@code $comment} property of the schema object + * + * @return the comment + * @since 4.0 + */ + String comment() default ""; + + /** + * Requires that the instance must be a specific value. No other values are permitted. + *

+ * The value is parsed as JSON if the schema type is anything other than STRING. + * + * @return the value which the instance must be equal to, expressed according to the type of the schema + * @since 4.0 + */ + String constValue() default ""; + + /** + * A class used to create a schema used to control conditional evaluation. If an instance validates against the + * {@code if} schema then it must also validate against the {@code then} schema. Otherwise it must validate against + * the {@code else} schema. + * + * @return a class used to create the {@code if} schema + * @see #thenSchema() + * @see #elseSchema() + * @since 4.0 + */ + Class ifSchema() default Void.class; + + /** + * A class used to create a schema that an instance must validate against if it validates against the {@code if} + * schema. + * + * @return a class used to create the {@code then} schema + * @see #ifSchema() + * @see #elseSchema() + * @since 4.0 + */ + Class thenSchema() default Void.class; + + /** + * A class used to create a schema that an instance must validate against if it does not validate against the + * {@code if} schema. + * + * @return a class used to create the {@code else} schema + * @see #ifSchema() + * @see #thenSchema() + * @since 4.0 + */ + Class elseSchema() default Void.class; + + /** + * Schemas which an instance must validate against if the instance has certain properties. + *

+ * For each {@link DependentSchema} listed, if the instance is an object which has a property named + * {@link DependentSchema#name() name()} then the instance must validate against the schema created from + * {@link DependentSchema#schema() schema()}. + * + * @return an array of DependentSchema entries + * @since 4.0 + */ + DependentSchema[] dependentSchemas() default {}; + + /** + * A schema which at least one element of an array instance must validate against. + *

+ * The class is used to create a schema. If the instance is an array, then at least one element of the array must + * validate against the schema. + * + * @return a class used to create a schema which at least one element of an array instance must validate against + * @since 4.0 + */ + Class contains() default Void.class; + + /** + * Specifies the maximum number of elements which may validate against the {@link #contains()} schema. + *

+ * If more than this number of elements of an array instance match the {@code contains} schema, the instance does + * not validate against this schema. + * + * @return the maximum number of elements which may validate against the {@link #contains()} schema + * @since 4.0 + */ + int maxContains() default Integer.MAX_VALUE; + + /** + * Specifies the minimum number of elements which must validate against the {@link #contains()} schema. + *

+ * If fewer than this number of elements of an array instance match the {@code contains} schema, the instance does + * not validate against this schema. + * + * @return the minimum number of elements which must validate against the {@link #contains()} schema + * @since 4.0 + */ + int minContains() default 0; + + /** + * Schemas which the leading elements of an array instance must validate against. + *

+ * The array of classes is used to create an array of schemas. If an instance is an array, the first element of the + * array must validate against the first schema, the second element must validate against the second schema and so + * on. + * + * @return an array of classes used to create an array of schemas used to validate the leading elements of an array + * instance + * @since 4.0 + */ + Class[] prefixItems() default {}; + + /** + * Applies subschemas against properties matched by regular expressions. + *

+ * For each {@link PatternProperty} listed, for each property whose name matches {@link PatternProperty#regex() | + * regex()}, its value must validate against the schema created from {@link PatternProperty#schema() schema()}. + * + * @return a mapping from regular expressions to schemas + * @since 4.0 + */ + PatternProperty[] patternProperties() default {}; + + /** + * Specifies that certain properties must be present if other properties are present. + *

+ * For each {@link DependentRequired} entry in the list, if the instance is an object and has a property named + * {@link DependentRequired#name()} then it must also have property named for each entry of + * {@link DependentRequired#requires()} to validate against this schema. + * + * @return the properties required if certain other properties are present + * @since 4.0 + */ + DependentRequired[] dependentRequired() default {}; + + /** + * A schema which the names of properties of an object instance must validate against. + *

+ * The class is used to create a schema. If the instance is an object, then the name of each property in the + * instance must validate against the schema. + * + * @return a schema that property names must validate against + * @since 4.0 + */ + Class propertyNames() default Void.class; + + /** + * The encoding used to allow binary data to be stored in a string. + *

+ * If the instance is a string, this property specifies that it contains binary data encoded as text using the + * specified encoding (e.g. base64). + * + * @return the encoding + * @since 4.0 + */ + String contentEncoding() default ""; + + /** + * The media type of the data in a string. + *

+ * If the instance is a string, this property specifies the media type of the data it contains. If + * {@link #contentEncoding()} is also set, it specifies the media type of the decoded string. + * + * @return the media type of the data in a string + * @since 4.0 + */ + String contentMediaType() default ""; + + /** + * The schema that data in a string must validate against. + *

+ * The class is used to create a schema. If the instance is a string and {@link #contentMediaType()} is set, the + * data must validate against the schema when interpreted as the given media type. + * + * @return a class used to create a schema used to validate the data in a string + * @since 4.0 + */ + Class contentSchema() default Void.class; } diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/SchemaProperty.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/SchemaProperty.java index e603b2bc..87429909 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/SchemaProperty.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/SchemaProperty.java @@ -29,7 +29,7 @@ * properties attribute of a {@link Schema} annotation. These types can be objects, but also primitives and * arrays. * - * This object is an extended subset of the JSON Schema Specification Wright Draft 00. + * This object is an extended subset of the JSON Schema draft specification 2020-12. * * @see OpenAPI * Specification Schema Object @@ -58,9 +58,6 @@ /** * Provides an array of java class implementations which can be used to describe multiple acceptable schemas. If * more than one match the derived schemas, a validation error will occur. - *

- * Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. - *

* * @return the list of possible classes for a single match **/ @@ -69,9 +66,6 @@ /** * Provides an array of java class implementations which can be used to describe multiple acceptable schemas. If any * match, the schema will be considered valid. - *

- * Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. - *

* * @return the list of possible class matches **/ @@ -80,9 +74,6 @@ /** * Provides an array of java class implementations which can be used to describe multiple acceptable schemas. If all * match, the schema will be considered valid. - *

- * Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. - *

* * @return the list of classes to match **/ @@ -106,8 +97,8 @@ String title() default ""; /** - * Constrains a value such that when divided by the multipleOf, the remainder must be an integer. Ignored if the - * value is 0. + * Constrains a value such that when divided by the multipleOf, the result must be an integer. Ignored if the value + * is {@code 0}. * * @return the multiplier constraint of the schema **/ @@ -158,9 +149,11 @@ int minLength() default 0; /** - * A pattern that the value must satisfy. Ignored if the value is an empty string. + * A regular expression that the value must satisfy. Ignored if the value is an empty string. + *

+ * If the instance is a string, the regular expression must match the instance. * - * @return the pattern of this schema + * @return the ECMA-262 regular expression to match against **/ String pattern() default ""; @@ -207,8 +200,10 @@ /** * Reference value to a Schema definition. *

- * This property provides a reference to an object defined elsewhere. This property and all other properties are - * mutually exclusive. If other properties are defined in addition to the ref property then the result is undefined. + * This property provides a reference to an object defined elsewhere. + *

+ * Unlike {@code ref} on most MP OpenAPI annotations, this property is not mutually exclusive with other + * properties. * * @return a reference to a schema definition **/ @@ -222,8 +217,8 @@ boolean nullable() default false; /** - * Relevant only for Schema "properties" definitions. Declares the property as "read only". This means that it MAY - * be sent as part of a response but SHOULD NOT be sent as part of the request. + * Declares the property as "read only". This means that it MAY be sent as part of a response but SHOULD NOT be sent + * as part of the request. *

* If the property is marked as readOnly being true and is in the required list, the required will take effect on * the response only. A property MUST NOT be marked as both readOnly and writeOnly being true. @@ -234,8 +229,8 @@ boolean readOnly() default false; /** - * Relevant only for Schema "properties" definitions. Declares the property as "write only". Therefore, it MAY be - * sent as part of a request but SHOULD NOT be sent as part of the response. + * Declares the property as "write only". Therefore, it MAY be sent as part of a request but SHOULD NOT be sent as + * part of the response. *

* If the property is marked as writeOnly being true and is in the required list, the required will take effect on * the request only. A property MUST NOT be marked as both readOnly and writeOnly being true. @@ -247,17 +242,26 @@ /** * A free-form property to include an example of an instance for this schema. - *

- * To represent examples that cannot be naturally represented in JSON or YAML, a string value is used to contain the - * example with escaping where necessary. - *

- * When associated with a specific media type, the example string shall be parsed by the consumer to be treated as - * an object or an array. * * @return an example of this schema + * @deprecated use {@link #examples()} **/ + @Deprecated(since = "4.0") String example() default ""; + /** + * A free-form property to include examples of an instance for this schema. + *

+ * Each example SHOULD validate against this schema. + *

+ * If the schema {@link #type()} is STRING, the value will be interpreted as a literal string, otherwise it will be + * parsed as JSON. + * + * @return an array of examples of this schema + * @since 4.0 + **/ + String[] examples() default {}; + /** * Additional external documentation for this schema. * @@ -374,4 +378,182 @@ */ Extension[] extensions() default {}; + /** + * A comment to be included in the schema + *

+ * This value is set in the {@code $comment} property of the schema object + * + * @return the comment + * @since 4.0 + */ + String comment() default ""; + + /** + * Requires that the instance must be a specific value. No other values are permitted. + *

+ * The value is parsed as JSON if the schema type is anything other than STRING. + * + * @return the value which the instance must be equal to, expressed according to the type of the schema + * @since 4.0 + */ + String constValue() default ""; + + /** + * A class used to create a schema used to control conditional evaluation. If an instance validates against the + * {@code if} schema then it must also validate against the {@code then} schema. Otherwise it must validate against + * the {@code else} schema. + * + * @return a class used to create the {@code if} schema + * @see #thenSchema() + * @see #elseSchema() + * @since 4.0 + */ + Class ifSchema() default Void.class; + + /** + * A class used to create a schema that an instance must validate against if it validates against the {@code if} + * schema. + * + * @return a class used to create the {@code then} schema + * @see #ifSchema() + * @see #elseSchema() + * @since 4.0 + */ + Class thenSchema() default Void.class; + + /** + * A class used to create a schema that an instance must validate against if it does not validate against the + * {@code if} schema. + * + * @return a class used to create the {@code else} schema + * @see #ifSchema() + * @see #thenSchema() + * @since 4.0 + */ + Class elseSchema() default Void.class; + + /** + * Schemas which an instance must validate against if the instance has certain properties. + *

+ * For each {@link DependentSchema} listed, if the instance is an object which has a property named + * {@link DependentSchema#name() name()} then the instance must validate against the schema created from + * {@link DependentSchema#schema() schema()}. + * + * @return an array of DependentSchema entries + * @since 4.0 + */ + DependentSchema[] dependentSchemas() default {}; + + /** + * A schema which at least one element of an array instance must validate against. + *

+ * The class is used to create a schema. If the instance is an array, then at least one element of the array must + * validate against the schema. + * + * @return a class used to create a schema which at least one element of an array instance must validate against + * @since 4.0 + */ + Class contains() default Void.class; + + /** + * Specifies the maximum number of elements which may validate against the {@link #contains()} schema. + *

+ * If more than this number of elements of an array instance match the {@code contains} schema, the instance does + * not validate against this schema. + * + * @return the maximum number of elements which may validate against the {@link #contains()} schema + * @since 4.0 + */ + int maxContains() default Integer.MAX_VALUE; + + /** + * Specifies the minimum number of elements which must validate against the {@link #contains()} schema. + *

+ * If fewer than this number of elements of an array instance match the {@code contains} schema, the instance does + * not validate against this schema. + * + * @return the minimum number of elements which must validate against the {@link #contains()} schema + * @since 4.0 + */ + int minContains() default 0; + + /** + * Schemas which the leading elements of an array instance must validate against. + *

+ * The array of classes is used to create an array of schemas. If an instance is an array, the first element of the + * array must validate against the first schema, the second element must validate against the second schema and so + * on. + * + * @return an array of classes used to create an array of schemas used to validate the leading elements of an array + * instance + * @since 4.0 + */ + Class[] prefixItems() default {}; + + /** + * Applies subschemas against properties matched by regular expressions. + *

+ * For each {@link PatternProperty} listed, for each property whose name matches {@link PatternProperty#regex() | + * regex()}, its value must validate against the schema created from {@link PatternProperty#schema() schema()}. + * + * @return a mapping from regular expressions to schemas + * @since 4.0 + */ + PatternProperty[] patternProperties() default {}; + + /** + * Specifies that certain properties must be present if other properties are present. + *

+ * For each {@link DependentRequired} entry in the list, if the instance is an object and has a property named + * {@link DependentRequired#name()} then it must also have property named for each entry of + * {@link DependentRequired#requires()} to validate against this schema. + * + * @return the properties required if certain other properties are present + * @since 4.0 + */ + DependentRequired[] dependentRequired() default {}; + + /** + * A schema which the names of properties of an object instance must validate against. + *

+ * The class is used to create a schema. If the instance is an object, then the name of each property in the + * instance must validate against the schema. + * + * @return a schema that property names must validate against + * @since 4.0 + */ + Class propertyNames() default Void.class; + + /** + * The encoding used to allow binary data to be stored in a string. + *

+ * If the instance is a string, this property specifies that it contains binary data encoded as text using the + * specified encoding (e.g. base64). + * + * @return the encoding + * @since 4.0 + */ + String contentEncoding() default ""; + + /** + * The media type of the data in a string. + *

+ * If the instance is a string, this property specifies the media type of the data it contains. If + * {@link #contentEncoding()} is also set, it specifies the media type of the decoded string. + * + * @return the media type of the data in a string + * @since 4.0 + */ + String contentMediaType() default ""; + + /** + * The schema that data in a string must validate against. + *

+ * The class is used to create a schema. If the instance is a string and {@link #contentMediaType()} is set, the + * data must validate against the schema when interpreted as the given media type. + * + * @return a class used to create a schema used to validate the data in a string + * @since 4.0 + */ + Class contentSchema() default Void.class; } diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/package-info.java index a0fb0c55..1c8176ad 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/package-info.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/package-info.java @@ -25,6 +25,6 @@ * */ -@org.osgi.annotation.versioning.Version("1.2") +@org.osgi.annotation.versioning.Version("1.3") @org.osgi.annotation.versioning.ProviderType package org.eclipse.microprofile.openapi.annotations.media; \ No newline at end of file From 5660eba0443105db323f6f2b8bbf5af23e13f215 Mon Sep 17 00:00:00 2001 From: Andrew Rouse Date: Fri, 12 Apr 2024 16:31:26 +0100 Subject: [PATCH 2/3] Schema annotation new field TCKs Test new parameters on the Schema annotation which don't involve subschemas. - comment - const - dependentRequired - contentEncoding - contentMediaType --- .../openapi/apps/airlines/JAXRSApp.java | 3 +- .../openapi/apps/airlines/model/User.java | 78 ++++++++++++++++++- .../openapi/tck/AirlinesAppTest.java | 16 +++- 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java index a9995962..37c1404f 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java @@ -115,7 +115,8 @@ type = SchemaType.ARRAY, implementation = Airline.class, extensions = @Extension(name = "x-schema", - value = "test-schema")), + value = "test-schema"), + comment = "This is an airline"), @Schema(name = "id", type = SchemaType.INTEGER, format = "int32"), @Schema(name = "AirlinesRef", ref = "#/components/schemas/Airlines"), diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/model/User.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/model/User.java index 5e428e2d..868e80fd 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/model/User.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/model/User.java @@ -13,9 +13,19 @@ package org.eclipse.microprofile.openapi.apps.airlines.model; -import org.eclipse.microprofile.openapi.annotations.media.Schema; +import java.time.LocalDate; -@Schema(maxProperties = 1024, minProperties = 1, requiredProperties = {"id", "username", "password"}) +import org.eclipse.microprofile.openapi.annotations.media.DependentRequired; +import org.eclipse.microprofile.openapi.annotations.media.DependentSchema; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.media.Schema.False; +import org.eclipse.microprofile.openapi.annotations.media.Schema.True; + +@Schema(maxProperties = 1024, minProperties = 1, + requiredProperties = {"id", "username", "password"}, + dependentRequired = @DependentRequired(name = "frequentFlyerNumber", + requires = {"frequentFlyerProgrammeName", "frequentFlyerStartDate"}), + dependentSchemas = @DependentSchema(name = "forbiddenField", schema = False.class)) public class User { @Schema(example = "3456") @@ -51,6 +61,22 @@ public class User { @Schema(hidden = true) private String undocumentedProperty; + private String frequentFlyerNumber; + + private String frequentFlyerProgrammeName; + + private LocalDate frequentFlyerStartDate; + + @Schema(constValue = "true") + private boolean human; + + /** Base64 encoded JPEG photo */ + @Schema(contentEncoding = "base64", contentMediaType = "image/jpeg") + private String photo; + + @Schema(implementation = True.class) + private Object freeformNotes; + /** * Creates a User instance with the parameters specified. * @@ -290,4 +316,52 @@ public void setUndocumentedProperty(String undocumentedProperty) { this.undocumentedProperty = undocumentedProperty; } + public String getFrequentFlyerNumber() { + return frequentFlyerNumber; + } + + public void setFrequentFlyerNumber(String frequentFlyerNumber) { + this.frequentFlyerNumber = frequentFlyerNumber; + } + + public String getFrequentFlyerProgrammeName() { + return frequentFlyerProgrammeName; + } + + public void setFrequentFlyerProgrammeName(String frequentFlyerProgrammeName) { + this.frequentFlyerProgrammeName = frequentFlyerProgrammeName; + } + + public LocalDate getFrequentFlyerStartDate() { + return frequentFlyerStartDate; + } + + public void setFrequentFlyerStartDate(LocalDate frequentFlyerStartDate) { + this.frequentFlyerStartDate = frequentFlyerStartDate; + } + + public String getPhoto() { + return photo; + } + + public void setPhoto(String photo) { + this.photo = photo; + } + + public boolean getHuman() { + return human; + } + + public void setHuman(boolean human) { + this.human = human; + } + + public Object getFreeformNotes() { + return freeformNotes; + } + + public void setFreeformNotes(Object freeformNotes) { + this.freeformNotes = freeformNotes; + } + } 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 96b54ecb..29ebb5fa 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 @@ -700,6 +700,7 @@ public void testSchema(String type) { // Basic properties vr.body("components.schemas.AirlinesRef.$ref", equalTo("#/components/schemas/Airlines")); vr.body("components.schemas.Airlines.title", equalTo("Airlines")); + vr.body("components.schemas.Airlines.$comment", equalTo("This is an airline")); vr.body("components.schemas.Airlines.x-schema", equalTo("test-schema")); vr.body("paths.'/bookings'.post.responses.'201'.content.'application/json'.schema.type", itemOrSingleton("string")); @@ -714,6 +715,17 @@ public void testSchema(String type) { vr.body("components.schemas.User.required", hasItems("id", "username", "password")); // requiredProperties vr.body("components.schemas.User", not(hasItem("undocumentedProperty"))); // hidden property vr.body("components.schemas.Gender.enum", hasItems("Male", "Female", "Other")); + vr.body("components.schemas.User.dependentRequired", aMapWithSize(1)); + vr.body("components.schemas.User.dependentRequired.frequentFlyerNumber", + contains("frequentFlyerProgrammeName", "frequentFlyerStartDate")); + String photoPath = dereference(vr, "components.schemas.User.properties.photo"); + vr.body(photoPath + ".contentEncoding", equalTo("base64")); + vr.body(photoPath + ".contentMediaType", equalTo("image/jpeg")); + String humanPath = dereference(vr, "components.schemas.User.properties.human"); + vr.body(humanPath + ".const", equalTo(true)); + vr.body("components.schemas.User.properties.freeformNotes", equalTo(true)); + vr.body("components.schemas.User.dependentSchemas", aMapWithSize(1)); + vr.body("components.schemas.User.dependentSchemas.forbiddenField", equalTo(false)); // Array properties String createSchema = "paths.'/user/createWithArray'.post.requestBody.content.'application/json'.schema"; @@ -727,7 +739,7 @@ public void testSchema(String type) { @Test(dataProvider = "formatProvider") public void testSchemaProperty(String type) { ValidatableResponse vr = callEndpoint(type); - vr.body("components.schemas.User.properties", IsMapWithSize.aMapWithSize(10)); + vr.body("components.schemas.User.properties", IsMapWithSize.aMapWithSize(16)); 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")); @@ -737,7 +749,7 @@ public void testSchemaProperty(String type) { @Test(dataProvider = "formatProvider") public void testSchemaPropertyValuesOverrideClassPropertyValues(String type) { ValidatableResponse vr = callEndpoint(type); - vr.body("components.schemas.User.properties", IsMapWithSize.aMapWithSize(10)); + vr.body("components.schemas.User.properties", IsMapWithSize.aMapWithSize(16)); 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")); } From cd35c078ba1b52c4e051bd763de0825822413218 Mon Sep 17 00:00:00 2001 From: Andrew Rouse Date: Tue, 16 Apr 2024 16:17:12 +0100 Subject: [PATCH 3/3] Add additionalProperties to SchemaProperty --- .../annotations/media/SchemaProperty.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/SchemaProperty.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/SchemaProperty.java index 87429909..9a915dd6 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/SchemaProperty.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/SchemaProperty.java @@ -23,6 +23,8 @@ import org.eclipse.microprofile.openapi.annotations.ExternalDocumentation; import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; import org.eclipse.microprofile.openapi.annotations.extensions.Extension; +import org.eclipse.microprofile.openapi.annotations.media.Schema.False; +import org.eclipse.microprofile.openapi.annotations.media.Schema.True; /** * The SchemaProperty Object allows the definition of input and output data types nested within the @@ -368,6 +370,32 @@ **/ boolean uniqueItems() default false; + /** + * Provides a Java class as implementation for additional properties that may be present in instances of this + * schema. + * + *

+ * If no additional properties are allowed, the value of this property should be set to {@link False False.class} + * which will be rendered as boolean false in the resulting OpenAPI document. + * + *

+ * The default value {@link Void Void.class} will result in no {@code additionalProperties} attribute being + * generated in the resulting OpenAPI document. The effective value in that case is {@code true} per the OpenAPI + * specification. + * + *

+ * Implementations MAY ignore this property if this schema's {@linkplain #type() type} is not + * {@linkplain SchemaType#OBJECT OBJECT}, either explicitly or as derived by the placement of the annotation. + * + * @return a class that describes the allowable schema for additional properties not explicitly defined + * + * @since 4.0 + * + * @see True + * @see False + */ + Class additionalProperties() default Void.class; + /** * List of extensions to be added to the {@link org.eclipse.microprofile.openapi.models.media.Schema Schema} model * corresponding to the containing annotation.