Skip to content

Commit a6c3c0c

Browse files
committed
Add examples parameter in @Schema annotation for OAS 3.1 spec
1 parent 3501c8a commit a6c3c0c

6 files changed

Lines changed: 145 additions & 5 deletions

File tree

‎modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ public class CodegenProperty implements Cloneable, IJsonSchemaValidationProperti
9898
*/
9999
@Getter @Setter
100100
public String example;
101+
/**
102+
* A free-form property to include a list of examples of an instance for this schema.
103+
*/
104+
@Getter @Setter
105+
public List<String> examples;
101106

102107
@Getter @Setter
103108
public String jsonSchema;
@@ -615,6 +620,9 @@ public CodegenProperty clone() {
615620
if (this.allowableValues != null) {
616621
cp.allowableValues = new HashMap<String, Object>(this.allowableValues);
617622
}
623+
if (this.examples != null) {
624+
cp.examples = new ArrayList<String>(this.examples);
625+
}
618626
if (this.items != null) {
619627
cp.items = this.items;
620628
}
@@ -996,6 +1004,7 @@ public String toString() {
9961004
sb.append(", minLength=").append(minLength);
9971005
sb.append(", pattern='").append(pattern).append('\'');
9981006
sb.append(", example='").append(example).append('\'');
1007+
sb.append(", examples=").append(examples);
9991008
sb.append(", jsonSchema='").append(jsonSchema).append('\'');
10001009
sb.append(", minimum='").append(minimum).append('\'');
10011010
sb.append(", maximum='").append(maximum).append('\'');
@@ -1183,6 +1192,7 @@ public boolean equals(Object o) {
11831192
Objects.equals(minLength, that.minLength) &&
11841193
Objects.equals(pattern, that.pattern) &&
11851194
Objects.equals(example, that.example) &&
1195+
Objects.equals(examples, that.examples) &&
11861196
Objects.equals(jsonSchema, that.jsonSchema) &&
11871197
Objects.equals(minimum, that.minimum) &&
11881198
Objects.equals(maximum, that.maximum) &&
@@ -1213,7 +1223,7 @@ public int hashCode() {
12131223
return Objects.hash(openApiType, baseName, complexType, getter, setter, description,
12141224
dataType, datatypeWithEnum, dataFormat, name, min, max, defaultValue,
12151225
defaultValueWithParam, baseType, containerType, containerTypeMapped, title, unescapedDescription,
1216-
maxLength, minLength, pattern, example, jsonSchema, minimum, maximum,
1226+
maxLength, minLength, pattern, example, examples, jsonSchema, minimum, maximum,
12171227
exclusiveMinimum, exclusiveMaximum, required, deprecated,
12181228
isPrimitiveType, isModel, isContainer, isString, isNumeric,
12191229
isInteger, isLong, isNumber, isFloat, isDouble, isDecimal, isByteArray, isBinary, isFile,

‎modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java‎

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.samskivert.mustache.Mustache;
2626
import com.samskivert.mustache.Mustache.Compiler;
2727
import com.samskivert.mustache.Mustache.Lambda;
28+
import com.fasterxml.jackson.databind.JsonNode;
2829
import io.swagger.v3.core.util.Json;
2930
import io.swagger.v3.oas.models.OpenAPI;
3031
import io.swagger.v3.oas.models.Operation;
@@ -2559,6 +2560,45 @@ public String toExampleValue(Schema schema) {
25592560
return "null";
25602561
}
25612562

2563+
/**
2564+
* Return the list of example values of the property.
2565+
* <p>
2566+
* This method should be overridden in the generator to meet its requirement.
2567+
*
2568+
* @param schema Property schema
2569+
* @return a string list of the example values of the property
2570+
*/
2571+
public List<String> toExampleValues(Schema schema) {
2572+
List<?> schemaExamples = schema.getExamples();
2573+
if (schemaExamples == null || schemaExamples.isEmpty()) {
2574+
return null;
2575+
}
2576+
List<String> examples = schemaExamples.stream()
2577+
.map(this::getExampleValueString)
2578+
.collect(Collectors.toList());
2579+
2580+
return examples.isEmpty() ? null : examples;
2581+
}
2582+
2583+
/**
2584+
* Return the string presentation of a single example value.
2585+
* <p>
2586+
* For value nodes, {@link JsonNode#asText()} is used to get the string without duplicated quotes.
2587+
*
2588+
* @param example the raw example value coming from the parsed schema
2589+
* @return string presentation of the example value
2590+
*/
2591+
protected String getExampleValueString(Object example) {
2592+
if (example == null) {
2593+
return "null";
2594+
}
2595+
if (example instanceof JsonNode) {
2596+
JsonNode node = (JsonNode) example;
2597+
return node.isValueNode() ? node.asText() : node.toString();
2598+
}
2599+
return example.toString();
2600+
}
2601+
25622602
/**
25632603
* Return the default value of the property
25642604
* <p>
@@ -4285,9 +4325,19 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo
42854325
try {
42864326
property.example = toExampleValue(p);
42874327
} catch (Exception e) {
4288-
LOGGER.error("Error in generating `example` for the property {}. Default to ERROR_TO_EXAMPLE_VALUE. Enable debugging for more info.", property.baseName);
4289-
LOGGER.debug("Exception from toExampleValue: {}", e.getMessage());
4290-
property.example = "ERROR_TO_EXAMPLE_VALUE";
4328+
property.example = handleExampleException(property.baseName, e);
4329+
}
4330+
4331+
if (p.getExamples() != null) {
4332+
List<String> examples = new ArrayList<>();
4333+
for (Object example : p.getExamples()) {
4334+
try {
4335+
examples.add(getExampleValueString(example));
4336+
} catch (Exception e) {
4337+
examples.add(handleExampleException(property.baseName, e));
4338+
}
4339+
}
4340+
property.examples = examples;
42914341
}
42924342

42934343
property.jsonSchema = Json.pretty(Json.mapper().convertValue(p, TreeMap.class));
@@ -4512,6 +4562,9 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo
45124562
if (original.getExample() != null) {
45134563
property.example = toExampleValue(original);
45144564
}
4565+
if (original.getExamples() != null) {
4566+
property.examples = toExampleValues(original);
4567+
}
45154568
}
45164569

45174570
// override defaultValue if it's not set and defaultToEmptyContainer is set
@@ -4528,6 +4581,12 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo
45284581
return property;
45294582
}
45304583

4584+
private String handleExampleException(String propertyName, Exception e) {
4585+
LOGGER.error("Error in generating `example` for the property {}. Default to ERROR_TO_EXAMPLE_VALUE. Enable debugging for more info.", propertyName);
4586+
LOGGER.debug("Exception from toExampleValue: {}", e.getMessage());
4587+
return "ERROR_TO_EXAMPLE_VALUE";
4588+
}
4589+
45314590
/**
45324591
* update container's default to empty container according rules provided by the user.
45334592
*

‎modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}
231231
{{#lambda.trim}}{{>notNull}}{{/lambda.trim}}
232232
{{/useBeanValidation}}
233233
{{#swagger2AnnotationLibrary}}
234-
@Schema(name = "{{{baseName}}}"{{#isReadOnly}}, accessMode = Schema.AccessMode.READ_ONLY{{/isReadOnly}}{{#example}}, example = "{{{.}}}"{{/example}}{{#description}}, description = "{{{.}}}"{{/description}}{{#deprecated}}, deprecated = true{{/deprecated}}, requiredMode = {{#required}}Schema.RequiredMode.REQUIRED{{/required}}{{^required}}Schema.RequiredMode.NOT_REQUIRED{{/required}}{{#isNullable}}, nullable = true{{/isNullable}})
234+
@Schema(name = "{{{baseName}}}"{{#isReadOnly}}, accessMode = Schema.AccessMode.READ_ONLY{{/isReadOnly}}{{#example}}, example = "{{{.}}}"{{/example}}{{#examples.0}}, examples = { {{#examples}}"{{{.}}}"{{^-last}}, {{/-last}}{{/examples}} }{{/examples.0}}{{#description}}, description = "{{{.}}}"{{/description}}{{#deprecated}}, deprecated = true{{/deprecated}}, requiredMode = {{#required}}Schema.RequiredMode.REQUIRED{{/required}}{{^required}}Schema.RequiredMode.NOT_REQUIRED{{/required}}{{#isNullable}}, nullable = true{{/isNullable}})
235235
{{/swagger2AnnotationLibrary}}
236236
{{#swagger1AnnotationLibrary}}
237237
@ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}")

‎modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,6 +2296,23 @@ public void testAllOfSingleRefSiblingExample() {
22962296
assertEquals("Ref-Property-Example", codegen.fromProperty("refProperty", (Schema) testProperties.get("refProperty")).example);
22972297
}
22982298

2299+
@Test
2300+
public void testOas31SchemaExamplesHandling() {
2301+
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/issue_24755.yaml");
2302+
final DefaultCodegen codegen = new DefaultCodegen();
2303+
codegen.setOpenAPI(openAPI);
2304+
2305+
final Map properties = openAPI.getComponents().getSchemas().get("Pet").getProperties();
2306+
2307+
assertThat(codegen.fromProperty("name", (Schema) properties.get("name")).examples)
2308+
.containsExactly("Fluffy", "Rex");
2309+
assertThat(codegen.fromProperty("age", (Schema) properties.get("age")).examples)
2310+
.containsExactly("3");
2311+
assertThat(codegen.fromProperty("tags", (Schema) properties.get("tags")).examples)
2312+
.containsExactly("[\"a\",\"b\"]");
2313+
assertNull(codegen.fromProperty("height", (Schema) properties.get("height")).examples);
2314+
}
2315+
22992316
@Test
23002317
public void testDeprecatedRef() {
23012318
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/model-deprecated.yaml");

‎modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10203,4 +10203,17 @@ void listItems_with_additionalItemsAnnotations() throws IOException {
1020310203
"private List<@NotNull @Min(0)Integer> listInteger",
1020410204
"private List<@NotNull @Size(max=10) String> listCode");
1020510205
}
10206+
10207+
@Test
10208+
public void shouldRenderOas31SchemaExamples() throws IOException {
10209+
Map<String, File> files = generateFromContract("src/test/resources/3_1/issue_24755.yaml", SPRING_BOOT);
10210+
10211+
JavaFileAssert.assertThat(files.get("Pet.java"))
10212+
.fileContains(
10213+
"example = \"Fluffy\"",
10214+
"examples = { \"Fluffy\", \"Rex\" }",
10215+
"examples = { \"3\" }",
10216+
"examples = { \"[a, b]\" }"
10217+
).fileDoesNotContain("examples = {}");
10218+
}
1020610219
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
openapi: 3.1.0
2+
info:
3+
title: Example API
4+
version: 1.0.0
5+
paths:
6+
/pets:
7+
get:
8+
operationId: listPets
9+
responses:
10+
'200':
11+
description: ok
12+
content:
13+
application/json:
14+
schema:
15+
type: array
16+
items:
17+
$ref: '#/components/schemas/Pet'
18+
components:
19+
schemas:
20+
Pet:
21+
type: object
22+
properties:
23+
name:
24+
type: string
25+
example: 'Fluffy'
26+
examples:
27+
- 'Fluffy'
28+
- 'Rex'
29+
age:
30+
type: integer
31+
examples:
32+
- 3
33+
height:
34+
type: integer
35+
examples: []
36+
tags:
37+
type: array
38+
items:
39+
type: string
40+
examples:
41+
- ['a', 'b']

0 commit comments

Comments
 (0)