Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ public class CodegenProperty implements Cloneable, IJsonSchemaValidationProperti
*/
@Getter @Setter
public String example;
/**
* A free-form property to include a list of examples of an instance for this schema.
*/
@Getter @Setter
public List<String> examples;

@Getter @Setter
public String jsonSchema;
Expand Down Expand Up @@ -615,6 +620,9 @@ public CodegenProperty clone() {
if (this.allowableValues != null) {
cp.allowableValues = new HashMap<String, Object>(this.allowableValues);
}
if (this.examples != null) {
cp.examples = new ArrayList<String>(this.examples);
}
if (this.items != null) {
cp.items = this.items;
}
Expand Down Expand Up @@ -996,6 +1004,7 @@ public String toString() {
sb.append(", minLength=").append(minLength);
sb.append(", pattern='").append(pattern).append('\'');
sb.append(", example='").append(example).append('\'');
sb.append(", examples=").append(examples);
sb.append(", jsonSchema='").append(jsonSchema).append('\'');
sb.append(", minimum='").append(minimum).append('\'');
sb.append(", maximum='").append(maximum).append('\'');
Expand Down Expand Up @@ -1183,6 +1192,7 @@ public boolean equals(Object o) {
Objects.equals(minLength, that.minLength) &&
Objects.equals(pattern, that.pattern) &&
Objects.equals(example, that.example) &&
Objects.equals(examples, that.examples) &&
Objects.equals(jsonSchema, that.jsonSchema) &&
Objects.equals(minimum, that.minimum) &&
Objects.equals(maximum, that.maximum) &&
Expand Down Expand Up @@ -1213,7 +1223,7 @@ public int hashCode() {
return Objects.hash(openApiType, baseName, complexType, getter, setter, description,
dataType, datatypeWithEnum, dataFormat, name, min, max, defaultValue,
defaultValueWithParam, baseType, containerType, containerTypeMapped, title, unescapedDescription,
maxLength, minLength, pattern, example, jsonSchema, minimum, maximum,
maxLength, minLength, pattern, example, examples, jsonSchema, minimum, maximum,
exclusiveMinimum, exclusiveMaximum, required, deprecated,
isPrimitiveType, isModel, isContainer, isString, isNumeric,
isInteger, isLong, isNumber, isFloat, isDouble, isDecimal, isByteArray, isBinary, isFile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Mustache.Lambda;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
Expand Down Expand Up @@ -2553,11 +2554,45 @@ public void setParameterEncodingValues(CodegenParameter codegenParameter, MediaT
* @return string presentation of the example value of the property
*/
public String toExampleValue(Schema schema) {
if (schema.getExample() != null) {
return schema.getExample().toString();
return getExampleValueToString(schema.getExample());
}

/**
* Return the list of example values of the property.
* <p>
* This method should be overridden in the generator to meet its requirement.
*
* @param schema Property schema
* @return a string list of the example values of the property
*/
public List<String> toExampleValues(Schema schema) {
List<?> schemaExamples = schema.getExamples();
if (schemaExamples == null || schemaExamples.isEmpty()) {
return null;
}
return schemaExamples.stream()
.map(this::getExampleValueToString)
.collect(Collectors.toList());
}

return "null";
/**
* Return the string presentation of a single example value.
* <p>
* This method should be overridden in the generator to meet its requirement.
* For value nodes, {@link JsonNode#asText()} is used to get the string without duplicated quotes.
*
* @param example the raw example value coming from the parsed schema
* @return string presentation of the example value
*/
protected String getExampleValueToString(Object example) {
Comment thread
peter-em marked this conversation as resolved.
if (example == null) {
return "null";
}
if (example instanceof JsonNode) {
JsonNode node = (JsonNode) example;
return node.isValueNode() ? node.asText() : node.toString();
}
return example.toString();
}

/**
Expand Down Expand Up @@ -4286,9 +4321,19 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo
try {
property.example = toExampleValue(p);
} catch (Exception e) {
LOGGER.error("Error in generating `example` for the property {}. Default to ERROR_TO_EXAMPLE_VALUE. Enable debugging for more info.", property.baseName);
LOGGER.debug("Exception from toExampleValue: {}", e.getMessage());
property.example = "ERROR_TO_EXAMPLE_VALUE";
property.example = handleExampleException(property.baseName, e);
}

if (p.getExamples() != null) {
List<String> examples = new ArrayList<>();
for (Object example : p.getExamples()) {
try {
examples.add(getExampleValueToString(example));
Comment thread
peter-em marked this conversation as resolved.
} catch (Exception e) {
examples.add(handleExampleException(property.baseName, e));
}
}
property.examples = examples;
}

property.jsonSchema = Json.pretty(Json.mapper().convertValue(p, TreeMap.class));
Expand Down Expand Up @@ -4513,6 +4558,9 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo
if (original.getExample() != null) {
property.example = toExampleValue(original);
}
if (original.getExamples() != null) {
property.examples = toExampleValues(original);
}
}

// override defaultValue if it's not set and defaultToEmptyContainer is set
Expand All @@ -4529,6 +4577,12 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo
return property;
}

private String handleExampleException(String propertyName, Exception e) {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
LOGGER.error("Error in generating example for the property {}. Default to ERROR_TO_EXAMPLE_VALUE. Enable debugging for more info.", propertyName);
LOGGER.debug("Exception from example handling: {}", e.getMessage());
return "ERROR_TO_EXAMPLE_VALUE";
}

/**
* update container's default to empty container according rules provided by the user.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1971,16 +1971,15 @@ private String escapeStringLiteral(String input) {
}

@Override
public String toExampleValue(Schema p) {
if (p.getExample() != null) {
if (p.getExample() instanceof Date) {
Date date = (Date) p.getExample();
return DateTimeFormatter.ISO_LOCAL_DATE.format(ZonedDateTime.ofInstant(date.toInstant(), UTC));
}
return escapeText(p.getExample().toString());
} else {
protected String getExampleValueToString(Object example) {
if (example == null) {
return null;
}
if (example instanceof Date) {
Date date = (Date) example;
return DateTimeFormatter.ISO_LOCAL_DATE.format(ZonedDateTime.ofInstant(date.toInstant(), UTC));
Comment thread
peter-em marked this conversation as resolved.
}
return escapeText(super.getExampleValueToString(example));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}
{{>beanValidation}}{{! prevent indent}}
{{/useBeanValidation}}
{{#swagger2AnnotationLibrary}}
@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}})
@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}})
Comment thread
peter-em marked this conversation as resolved.
{{/swagger2AnnotationLibrary}}
{{#swagger1AnnotationLibrary}}
@ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2296,6 +2296,23 @@ public void testAllOfSingleRefSiblingExample() {
assertEquals("Ref-Property-Example", codegen.fromProperty("refProperty", (Schema) testProperties.get("refProperty")).example);
}

@Test
public void testOas31SchemaExamplesHandling() {
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/issue_24755.yaml");
final DefaultCodegen codegen = new DefaultCodegen();
codegen.setOpenAPI(openAPI);

final Map properties = openAPI.getComponents().getSchemas().get("Pet").getProperties();

assertThat(codegen.fromProperty("name", (Schema) properties.get("name")).examples)
.containsExactly("Fluffy-\\s;]\"a", "Rex");
assertThat(codegen.fromProperty("age", (Schema) properties.get("age")).examples)
.containsExactly("3");
assertThat(codegen.fromProperty("tags", (Schema) properties.get("tags")).examples)
.containsExactly("[\"a\\\"c\",\"b\"]");
assertThat(codegen.fromProperty("height", (Schema) properties.get("height")).examples).isNullOrEmpty();
}

@Test
public void testDeprecatedRef() {
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/model-deprecated.yaml");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11592,4 +11592,17 @@ public void useRecordsLogsNoModelThatHasNoFile() throws IOException {

assertThat(messages).noneMatch(m -> m.contains("model 'URI'"));
}

@Test
public void shouldRenderOas31SchemaExamples() throws IOException {
Map<String, File> files = generateFromContract("src/test/resources/3_1/issue_24755.yaml", SPRING_BOOT);

JavaFileAssert.assertThat(files.get("Pet.java"))
.fileContains(
"example = \"Fluffy-\\\\s;]\\\"a\"",
"examples = { \"Fluffy-\\\\s;]\\\"a\", \"Rex\" }",
"examples = { \"3\" }",
"examples = { \"[a\\\"c, b]\" }"
Comment thread
peter-em marked this conversation as resolved.
).fileDoesNotContain("examples = { }");
}
}
41 changes: 41 additions & 0 deletions modules/openapi-generator/src/test/resources/3_1/issue_24755.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
openapi: 3.1.0
info:
title: Example API
version: 1.0.0
paths:
/pets:
get:
operationId: listPets
responses:
'200':
description: ok
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
components:
schemas:
Pet:
type: object
properties:
name:
type: string
example: 'Fluffy-\s;]"a'
examples:
- 'Fluffy-\s;]"a'
- 'Rex'
age:
type: integer
examples:
- 3
height:
type: integer
examples: []
tags:
type: array
items:
type: string
examples:
- ['a"c', 'b']
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ export function HolidaysFromJSONTyped(json: any, ignoreDiscriminator: boolean):
return json;
}
if (Array.isArray(json)) {
if (json.every(item => !(isNaN(parseDate(item).getTime())))) {
return json.map(value => parseDate(value));
if (json.every(item => typeof item === 'number')) {
return json;
}
}
if (Array.isArray(json)) {
if (json.every(item => typeof item === 'number')) {
return json;
if (json.every(item => !(isNaN(parseDate(item).getTime())))) {
return json.map(value => parseDate(value));
}
}
return {} as any;
Expand All @@ -50,13 +50,13 @@ export function HolidaysToJSONTyped(value?: Holidays | null, ignoreDiscriminator
return value;
}
if (Array.isArray(value)) {
if (value.every(item => item instanceof Date)) {
return value.map(value => serializeDate(value));
if (value.every(item => typeof item === 'number')) {
return value;
}
}
if (Array.isArray(value)) {
if (value.every(item => typeof item === 'number')) {
return value;
if (value.every(item => item instanceof Date)) {
return value.map(value => serializeDate(value));
}
}
return {};
Expand Down
Loading