Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
<artifactId>osgi.annotation</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>biz.aQute.bnd</groupId>
<artifactId>biz.aQute.bnd.annotation</artifactId>
<version>7.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,5 @@

// Required for compilation, not used at runtime
requires static osgi.annotation;

requires static biz.aQute.bnd.annotation;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,24 @@
* The base interface for OpenAPI model objects that can contain extensions. Extensions contain data not required by the
* specification and may or may not be supported by the tools you use.
* <p>
* The extensions property names are always prefixed by "x-".
* The extension's property names are always prefixed by "x-" unless otherwise specified by sub-interfaces or Extensible
* methods overridden therein. Sub-interfaces may also include additional detail regarding the handling of extension
* properties and how they relate to other well-known properties of the interface.
*
* For example, {@link org.eclipse.microprofile.openapi.models.media.Schema Schema} defines such handling and relaxes
* the requirement that extension properties are prefixed by "x-".
*/
public interface Extensible<T extends Extensible<T>> {

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

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

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

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

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

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

/**
* Returns the extension object with the given name from this Extensible's map of extensions.
* Returns the object with the given name from this Extensible's extensions.
*
* @param name
* the key used to access the extension object. Always prefixed by "x-".
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
* Any time a Schema Object can be used, a Reference Object can be used in its place. This allows referencing an
* existing definition instead of defining the same Schema again.
*
* <h3 id="schema-extensions">Extensions</h3> Although Schema is {@link Extensible}, the behavior of extensions is only
* defined for the OAS schema dialect identified by URI {@code https://spec.openapis.org/oas/3.1/dialect/base} (the
* default if not specified). For this dialect, Schema instances will consider all unknown properties to be extensions.
*
* The behavior is undefined when adding an extension via one of the methods of the {@link Extensible} interface when
* the name of the extension matches the name of a standard OAS schema property.
*
* @see <a href= "https://spec.openapis.org/oas/v3.1.0.html#schema-object">OpenAPI Specification Schema Object</a>
*/
public interface Schema extends Extensible<Schema>, Constructible, Reference<Schema> {
Expand Down Expand Up @@ -2060,6 +2067,9 @@ default Schema examples(List<Object> examples) {
* }
* </pre>
*
* <p>
* Note, if the property is an extension, this method is equivalent to {@link #getExtension(String)}.
Comment thread
MikeEdgar marked this conversation as resolved.
*
* @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
Expand Down Expand Up @@ -2090,7 +2100,8 @@ default Schema examples(List<Object> examples) {
* </ul>
*
* <p>
* When using the standard schema dialect, values set by this method can be retrieved by other methods. E.g.
* When using the standard schema dialect, values set by this method can be retrieved by other methods provided the
* type is compatible. E.g.
*
* <pre>
* {@code
Expand All @@ -2099,6 +2110,9 @@ default Schema examples(List<Object> examples) {
* }
* </pre>
*
* <p>
* Note, if the property is an extension, this method is equivalent to {@link #addExtension(String, Object)}.
*
* @param propertyName
* the property name
* @param value
Expand All @@ -2122,12 +2136,104 @@ default Schema examples(List<Object> examples) {
/**
* Sets all properties of a schema.
* <p>
* Equivalent to clearing all properties and then setting each property with {@link #set(String, Object)}.
* Equivalent to clearing all properties, including extensions, 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)}
* @since 4.0
*/
void setAll(Map<String, ?> allProperties);

/**
* Returns the map of all extension properties of the schema.
*
* @return a map containing all of this schema's extension properties
*
* @see <a href="#schema-extensions">Schema Extensions</a>
**/
@Override
Map<String, Object> getExtensions();

/**
* Sets this schema's extension properties to the given map of extensions. Passing an empty map has the effect of
* clearing all existing extension properties.
*
* @param extensions
* map containing the extension properties for this schema
* @return the current instance
*
* @see <a href="#schema-extensions">Schema Extensions</a>
*/
@Override
@aQute.bnd.annotation.baseline.BaselineIgnore("4.2.0")
default Schema extensions(Map<String, Object> extensions) {
return Extensible.super.extensions(extensions);
}

/**
* Sets a schema extension property.
*
* @param name
* the extension property name
* @param value
* extension property value. null values will be rejected (implementation will throw an exception) or
* ignored.
* @return the current instance
*
* @see <a href="#schema-extensions">Schema Extensions</a>
*/
@Override
@aQute.bnd.annotation.baseline.BaselineIgnore("4.2.0")
Schema addExtension(String name, Object value);

/**
* Removes the given extension property from the schema.
*
* @param name
* the extension property name
*
* @see <a href="#schema-extensions">Schema Extensions</a>
*/
@Override
void removeExtension(String name);

/**
* Sets this schema's extension properties to the given map of extensions. Passing an empty map has the effect of
* clearing all existing extension properties.
*
* @param extensions
* map containing the extension properties for this schema
*
* @see <a href="#schema-extensions">Schema Extensions</a>
*/
@Override
void setExtensions(Map<String, Object> extensions);

/**
* Checks whether an extension property with the given name is present on this schema.
*
* @param name
* the extension property name
*
* @return {@code true} if an extension with the given name is present, otherwise {@code false}
*/
@Override
default boolean hasExtension(String name) {
return Extensible.super.hasExtension(name);
}

/**
* Returns the extension object with the given name from this schema.
*
* @param name
* the extension property name
*
* @return the corresponding extension object, or {@code null} if no extension with the given name is present
*/
@Override
default Object getExtension(String name) {
return Extensible.super.getExtension(name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@
* </pre>
*/

@org.osgi.annotation.versioning.Version("3.1")
@org.osgi.annotation.versioning.Version("3.2")
@org.osgi.annotation.versioning.ProviderType
package org.eclipse.microprofile.openapi.models.media;
1 change: 1 addition & 0 deletions spec/src/main/asciidoc/release_notes.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ A full list of changes delivered in the 4.2 release can be found at link:https:/
==== API/SPI changes

* Add `example` and `examples` to `@Header` and verify implementation support in TCK (https://github.com/microprofile/microprofile-open-api/issues/697)[697])
* 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])

[[other_changes_42]]
==== Other Changes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Copyright (c) 2026 Contributors to the Eclipse Foundation
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.eclipse.microprofile.openapi.OASFactory;
import org.eclipse.microprofile.openapi.models.media.Schema;
import org.eclipse.microprofile.openapi.models.media.Schema.SchemaType;
import org.testng.annotations.Test;

/**
* This test covers the Extensible aspect of the Schema model, checking that properties are recognized correctly as
* extensions by an implementation and handled according to the Schema documentation.
*/
public class SchemaExtensionPropertyTest {

private static final String EXTNAME = "my-extension";

@Test
public void testExtensionSetForUnknownProperty() {
Schema schema = OASFactory.createSchema();
Object theExtension = new Object();
schema.set(EXTNAME, theExtension);

assertThat(schema.get(EXTNAME), is(sameInstance(theExtension)));
assertThat(schema.getExtension(EXTNAME), is(sameInstance(theExtension)));
}

@Test
public void testExtensionSetAllForUnknownProperty() {
Schema schema = OASFactory.createSchema();
Object theExtension = new Object();
schema.setAll(Map.of(
"type", List.of(SchemaType.STRING),
EXTNAME, theExtension));

assertThat(schema.getType(), is(List.of(SchemaType.STRING)));
assertThat(schema.get(EXTNAME), is(sameInstance(theExtension)));
assertThat(schema.getExtension(EXTNAME), is(sameInstance(theExtension)));
}

@Test
public void testExtensionAvailableFromGet() {
Schema schema = OASFactory.createSchema();
Object theExtension = new Object();
schema.addExtension(EXTNAME, theExtension);

assertThat(schema.get(EXTNAME), is(sameInstance(theExtension)));
assertThat(schema.getExtension(EXTNAME), is(sameInstance(theExtension)));
}

@Test
public void testExtensionSetWithNonnullDialect() {
Schema schema = OASFactory.createSchema();
schema.setSchemaDialect("https://spec.openapis.org/oas/3.1/dialect/base");
Object theExtension = new Object();
schema.set(EXTNAME, theExtension);
assertThat(schema.getExtension(EXTNAME), is(sameInstance(theExtension)));
}

@Test
public void testSetAllClearsExtensions() {
Schema schema = OASFactory.createSchema();
Object theExtension = new Object();
schema.set(EXTNAME, theExtension);
assertThat(schema.getExtension(EXTNAME), is(sameInstance(theExtension)));
schema.setAll(Collections.emptyMap());
assertThat(schema.hasExtension(EXTNAME), is(false));
}

@Test
public void testNullExtensionNotAdded() {
Schema schema = OASFactory.createSchema();

try {
schema.addExtension(EXTNAME, null);
} catch (Exception e) {
// May or may not be thrown
}

assertThat(schema.hasExtension(EXTNAME), is(false));
}
}