Skip to content

Commit 2badf0b

Browse files
authored
Merge pull request #736 from MikeEdgar/issue-698
Define and verify behavior of schema extension properties
2 parents d4c8d3e + 5121ae7 commit 2badf0b

7 files changed

Lines changed: 234 additions & 12 deletions

File tree

api/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333
<artifactId>osgi.annotation</artifactId>
3434
<scope>provided</scope>
3535
</dependency>
36+
<dependency>
37+
<groupId>biz.aQute.bnd</groupId>
38+
<artifactId>biz.aQute.bnd.annotation</artifactId>
39+
<version>7.0.0</version>
40+
<scope>provided</scope>
41+
</dependency>
3642
</dependencies>
3743

3844
<build>

api/src/main/java/module-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,5 @@
5252

5353
// Required for compilation, not used at runtime
5454
requires static osgi.annotation;
55-
55+
requires static biz.aQute.bnd.annotation;
5656
}

api/src/main/java/org/eclipse/microprofile/openapi/models/Extensible.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,24 @@
2323
* The base interface for OpenAPI model objects that can contain extensions. Extensions contain data not required by the
2424
* specification and may or may not be supported by the tools you use.
2525
* <p>
26-
* The extensions property names are always prefixed by "x-".
26+
* The extension's property names are always prefixed by "x-" unless otherwise specified by sub-interfaces or Extensible
27+
* methods overridden therein. Sub-interfaces may also include additional detail regarding the handling of extension
28+
* properties and how they relate to other well-known properties of the interface.
29+
*
30+
* For example, {@link org.eclipse.microprofile.openapi.models.media.Schema Schema} defines such handling and relaxes
31+
* the requirement that extension properties are prefixed by "x-".
2732
*/
2833
public interface Extensible<T extends Extensible<T>> {
2934

3035
/**
31-
* Returns the extensions property from an Extensible instance.
36+
* Returns the map of all extension properties from an Extensible instance.
3237
*
3338
* @return a map containing keys which start with "x-" and values which provide additional information
3439
**/
3540
Map<String, Object> getExtensions();
3641

3742
/**
38-
* Sets this Extensible's extensions property to the given map of extensions.
43+
* Sets this Extensible's extension properties to the given map of extensions.
3944
*
4045
* @param extensions
4146
* map containing keys which start with "x-" and values which provide additional information
@@ -49,7 +54,7 @@ default T extensions(Map<String, Object> extensions) {
4954
}
5055

5156
/**
52-
* Adds the given object to this Extensible's map of extensions, with the given name as its key.
57+
* Adds the given extension property to this Extensible, with the given name as its key.
5358
*
5459
* @param name
5560
* the key used to access the extension object. Always prefixed by "x-".
@@ -61,23 +66,23 @@ default T extensions(Map<String, Object> extensions) {
6166
T addExtension(String name, Object value);
6267

6368
/**
64-
* Removes the given object to this Extensible's map of extensions, with the given name as its key.
69+
* Removes an extension with the given property name from this Extensible.
6570
*
6671
* @param name
6772
* the key used to access the extension object. Always prefixed by "x-".
6873
*/
6974
void removeExtension(String name);
7075

7176
/**
72-
* Sets this Extensible's extensions property to the given map of extensions.
77+
* Sets this Extensible's extension properties to the given map of extensions.
7378
*
7479
* @param extensions
7580
* map containing keys which start with "x-" and values which provide additional information
7681
*/
7782
void setExtensions(Map<String, Object> extensions);
7883

7984
/**
80-
* Checks whether an extension with the given name is present in this Extensible's map of extensions.
85+
* Checks whether an extension with the given name is present in this Extensible.
8186
*
8287
* @param name
8388
* the key used to access the extension object. Always prefixed by "x-".
@@ -93,7 +98,7 @@ default boolean hasExtension(String name) {
9398
}
9499

95100
/**
96-
* Returns the extension object with the given name from this Extensible's map of extensions.
101+
* Returns the object with the given name from this Extensible's extensions.
97102
*
98103
* @param name
99104
* the key used to access the extension object. Always prefixed by "x-".

api/src/main/java/org/eclipse/microprofile/openapi/models/media/Schema.java

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@
3939
* Any time a Schema Object can be used, a Reference Object can be used in its place. This allows referencing an
4040
* existing definition instead of defining the same Schema again.
4141
*
42+
* <h3 id="schema-extensions">Extensions</h3> Although Schema is {@link Extensible}, the behavior of extensions is only
43+
* defined for the OAS schema dialect identified by URI {@code https://spec.openapis.org/oas/3.1/dialect/base} (the
44+
* default if not specified). For this dialect, Schema instances will consider all unknown properties to be extensions.
45+
*
46+
* The behavior is undefined when adding an extension via one of the methods of the {@link Extensible} interface when
47+
* the name of the extension matches the name of a standard OAS schema property.
48+
*
4249
* @see <a href= "https://spec.openapis.org/oas/v3.1.0.html#schema-object">OpenAPI Specification Schema Object</a>
4350
*/
4451
public interface Schema extends Extensible<Schema>, Constructible, Reference<Schema> {
@@ -2060,6 +2067,9 @@ default Schema examples(List<Object> examples) {
20602067
* }
20612068
* </pre>
20622069
*
2070+
* <p>
2071+
* Note, if the property is an extension, this method is equivalent to {@link #getExtension(String)}.
2072+
*
20632073
* @param propertyName
20642074
* the property name
20652075
* @return the value of the named property, or {@code null} if a property with the given name is not set
@@ -2090,7 +2100,8 @@ default Schema examples(List<Object> examples) {
20902100
* </ul>
20912101
*
20922102
* <p>
2093-
* When using the standard schema dialect, values set by this method can be retrieved by other methods. E.g.
2103+
* When using the standard schema dialect, values set by this method can be retrieved by other methods provided the
2104+
* type is compatible. E.g.
20942105
*
20952106
* <pre>
20962107
* {@code
@@ -2099,6 +2110,9 @@ default Schema examples(List<Object> examples) {
20992110
* }
21002111
* </pre>
21012112
*
2113+
* <p>
2114+
* Note, if the property is an extension, this method is equivalent to {@link #addExtension(String, Object)}.
2115+
*
21022116
* @param propertyName
21032117
* the property name
21042118
* @param value
@@ -2122,12 +2136,104 @@ default Schema examples(List<Object> examples) {
21222136
/**
21232137
* Sets all properties of a schema.
21242138
* <p>
2125-
* Equivalent to clearing all properties and then setting each property with {@link #set(String, Object)}.
2139+
* Equivalent to clearing all properties, including extensions, and then setting each property with
2140+
* {@link #set(String, Object)}.
21262141
*
21272142
* @param allProperties
21282143
* the properties to set. Each value in the map must be valid according to the rules in
21292144
* {@link #set(String, Object)}
21302145
* @since 4.0
21312146
*/
21322147
void setAll(Map<String, ?> allProperties);
2148+
2149+
/**
2150+
* Returns the map of all extension properties of the schema.
2151+
*
2152+
* @return a map containing all of this schema's extension properties
2153+
*
2154+
* @see <a href="#schema-extensions">Schema Extensions</a>
2155+
**/
2156+
@Override
2157+
Map<String, Object> getExtensions();
2158+
2159+
/**
2160+
* Sets this schema's extension properties to the given map of extensions. Passing an empty map has the effect of
2161+
* clearing all existing extension properties.
2162+
*
2163+
* @param extensions
2164+
* map containing the extension properties for this schema
2165+
* @return the current instance
2166+
*
2167+
* @see <a href="#schema-extensions">Schema Extensions</a>
2168+
*/
2169+
@Override
2170+
@aQute.bnd.annotation.baseline.BaselineIgnore("4.2.0")
2171+
default Schema extensions(Map<String, Object> extensions) {
2172+
return Extensible.super.extensions(extensions);
2173+
}
2174+
2175+
/**
2176+
* Sets a schema extension property.
2177+
*
2178+
* @param name
2179+
* the extension property name
2180+
* @param value
2181+
* extension property value. null values will be rejected (implementation will throw an exception) or
2182+
* ignored.
2183+
* @return the current instance
2184+
*
2185+
* @see <a href="#schema-extensions">Schema Extensions</a>
2186+
*/
2187+
@Override
2188+
@aQute.bnd.annotation.baseline.BaselineIgnore("4.2.0")
2189+
Schema addExtension(String name, Object value);
2190+
2191+
/**
2192+
* Removes the given extension property from the schema.
2193+
*
2194+
* @param name
2195+
* the extension property name
2196+
*
2197+
* @see <a href="#schema-extensions">Schema Extensions</a>
2198+
*/
2199+
@Override
2200+
void removeExtension(String name);
2201+
2202+
/**
2203+
* Sets this schema's extension properties to the given map of extensions. Passing an empty map has the effect of
2204+
* clearing all existing extension properties.
2205+
*
2206+
* @param extensions
2207+
* map containing the extension properties for this schema
2208+
*
2209+
* @see <a href="#schema-extensions">Schema Extensions</a>
2210+
*/
2211+
@Override
2212+
void setExtensions(Map<String, Object> extensions);
2213+
2214+
/**
2215+
* Checks whether an extension property with the given name is present on this schema.
2216+
*
2217+
* @param name
2218+
* the extension property name
2219+
*
2220+
* @return {@code true} if an extension with the given name is present, otherwise {@code false}
2221+
*/
2222+
@Override
2223+
default boolean hasExtension(String name) {
2224+
return Extensible.super.hasExtension(name);
2225+
}
2226+
2227+
/**
2228+
* Returns the extension object with the given name from this schema.
2229+
*
2230+
* @param name
2231+
* the extension property name
2232+
*
2233+
* @return the corresponding extension object, or {@code null} if no extension with the given name is present
2234+
*/
2235+
@Override
2236+
default Object getExtension(String name) {
2237+
return Extensible.super.getExtension(name);
2238+
}
21332239
}

api/src/main/java/org/eclipse/microprofile/openapi/models/media/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@
3030
* </pre>
3131
*/
3232

33-
@org.osgi.annotation.versioning.Version("3.1")
33+
@org.osgi.annotation.versioning.Version("3.2")
3434
@org.osgi.annotation.versioning.ProviderType
3535
package org.eclipse.microprofile.openapi.models.media;

spec/src/main/asciidoc/release_notes.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ A full list of changes delivered in the 4.2 release can be found at link:https:/
2828
==== API/SPI changes
2929

3030
* Add `example` and `examples` to `@Header` and verify implementation support in TCK (https://github.com/microprofile/microprofile-open-api/issues/697)[697])
31+
* Override `@Extensible`'s methods in `@Schema`, providing clarification in the documentation on how the methods behave specifically for schemas (https://github.com/microprofile/microprofile-open-api/issues/698[698])
3132

3233
[[other_changes_42]]
3334
==== Other Changes
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.eclipse.microprofile.openapi.tck;
18+
19+
import static org.hamcrest.MatcherAssert.assertThat;
20+
import static org.hamcrest.Matchers.is;
21+
import static org.hamcrest.Matchers.sameInstance;
22+
23+
import java.util.Collections;
24+
import java.util.List;
25+
import java.util.Map;
26+
27+
import org.eclipse.microprofile.openapi.OASFactory;
28+
import org.eclipse.microprofile.openapi.models.media.Schema;
29+
import org.eclipse.microprofile.openapi.models.media.Schema.SchemaType;
30+
import org.testng.annotations.Test;
31+
32+
/**
33+
* This test covers the Extensible aspect of the Schema model, checking that properties are recognized correctly as
34+
* extensions by an implementation and handled according to the Schema documentation.
35+
*/
36+
public class SchemaExtensionPropertyTest {
37+
38+
private static final String EXTNAME = "my-extension";
39+
40+
@Test
41+
public void testExtensionSetForUnknownProperty() {
42+
Schema schema = OASFactory.createSchema();
43+
Object theExtension = new Object();
44+
schema.set(EXTNAME, theExtension);
45+
46+
assertThat(schema.get(EXTNAME), is(sameInstance(theExtension)));
47+
assertThat(schema.getExtension(EXTNAME), is(sameInstance(theExtension)));
48+
}
49+
50+
@Test
51+
public void testExtensionSetAllForUnknownProperty() {
52+
Schema schema = OASFactory.createSchema();
53+
Object theExtension = new Object();
54+
schema.setAll(Map.of(
55+
"type", List.of(SchemaType.STRING),
56+
EXTNAME, theExtension));
57+
58+
assertThat(schema.getType(), is(List.of(SchemaType.STRING)));
59+
assertThat(schema.get(EXTNAME), is(sameInstance(theExtension)));
60+
assertThat(schema.getExtension(EXTNAME), is(sameInstance(theExtension)));
61+
}
62+
63+
@Test
64+
public void testExtensionAvailableFromGet() {
65+
Schema schema = OASFactory.createSchema();
66+
Object theExtension = new Object();
67+
schema.addExtension(EXTNAME, theExtension);
68+
69+
assertThat(schema.get(EXTNAME), is(sameInstance(theExtension)));
70+
assertThat(schema.getExtension(EXTNAME), is(sameInstance(theExtension)));
71+
}
72+
73+
@Test
74+
public void testExtensionSetWithNonnullDialect() {
75+
Schema schema = OASFactory.createSchema();
76+
schema.setSchemaDialect("https://spec.openapis.org/oas/3.1/dialect/base");
77+
Object theExtension = new Object();
78+
schema.set(EXTNAME, theExtension);
79+
assertThat(schema.getExtension(EXTNAME), is(sameInstance(theExtension)));
80+
}
81+
82+
@Test
83+
public void testSetAllClearsExtensions() {
84+
Schema schema = OASFactory.createSchema();
85+
Object theExtension = new Object();
86+
schema.set(EXTNAME, theExtension);
87+
assertThat(schema.getExtension(EXTNAME), is(sameInstance(theExtension)));
88+
schema.setAll(Collections.emptyMap());
89+
assertThat(schema.hasExtension(EXTNAME), is(false));
90+
}
91+
92+
@Test
93+
public void testNullExtensionNotAdded() {
94+
Schema schema = OASFactory.createSchema();
95+
96+
try {
97+
schema.addExtension(EXTNAME, null);
98+
} catch (Exception e) {
99+
// May or may not be thrown
100+
}
101+
102+
assertThat(schema.hasExtension(EXTNAME), is(false));
103+
}
104+
}

0 commit comments

Comments
 (0)