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 @@ -917,7 +917,7 @@ private static void setExampleSchema(io.swagger.v3.oas.annotations.media.Schema
// Only parse "null" as null value when nullable=true
if (node.isNull() && schema.nullable()) {
schemaObject.setExample(null);
} else if (node.isObject() || node.isArray()) {
} else if (node.isObject() || node.isArray() || SchemaTypeUtils.isNumberSchema(schemaObject)) {
schemaObject.setExample(node);
} else {
schemaObject.setExample(exampleValue);
Expand Down Expand Up @@ -983,7 +983,12 @@ public static Schema resolveSchemaFromType(Class<?> schemaImplementation, Compon
public static Schema resolveSchemaFromType(Class<?> schemaImplementation, Components components, JsonView jsonViewAnnotation, boolean openapi31) {
return resolveSchemaFromType(schemaImplementation, components, jsonViewAnnotation, openapi31, null, null, null);
}
public static Schema resolveSchemaFromType(Class<?> schemaImplementation, Components components, JsonView jsonViewAnnotation, boolean openapi31, io.swagger.v3.oas.annotations.media.Schema schemaAnnotation, io.swagger.v3.oas.annotations.media.ArraySchema arrayAnnotation, ModelConverterContext context) {
public static Schema resolveSchemaFromType(Class<?> schemaImplementation,
Components components,
JsonView jsonViewAnnotation,
boolean openapi31,
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation,
io.swagger.v3.oas.annotations.media.ArraySchema arrayAnnotation, ModelConverterContext context) {
Schema schemaObject;
PrimitiveType primitiveType = PrimitiveType.fromType(schemaImplementation);
if (primitiveType != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package io.swagger.v3.core.converting;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.core.converter.ResolvedSchema;
import io.swagger.v3.core.util.Json31;
import org.testng.annotations.Test;

import java.math.BigDecimal;

import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;

/**
* test documenting how example values for numbers/non-number differ in their JSON-representation.
*/
public class Issue5061Test {


@Test
public void testExampleValuesAreSerializedAsJsonDifferentlyBetweenStringAndNumber() throws Exception {
ResolvedSchema schema = ModelConverters.getInstance(true).readAllAsResolvedSchema(
ModelWithDifferentCombinationOfNumberFieldsWithExamples.class
);

assertNotNull(schema, "Schema should resolve");
String json = Json31.pretty(schema);
assertNotNull(json);
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);

JsonNode stringFieldType = root.at("/schema/properties/stringFieldType");
assertExampleIsString(stringFieldType);

JsonNode stringFieldTypeWithExplicitStringSchemaType = root.at("/schema/properties/stringFieldTypeWithExplicitStringSchemaType");
assertExampleIsString(stringFieldTypeWithExplicitStringSchemaType);

JsonNode stringFieldTypeWithExplicitNumberSchemaType = root.at("/schema/properties/stringFieldTypeWithExplicitNumberSchemaType");
assertExampleIsNumber(stringFieldTypeWithExplicitNumberSchemaType);

JsonNode stringFieldTypeWithExplicitIntegerSchemaType = root.at("/schema/properties/stringFieldTypeWithExplicitIntegerSchemaType");
assertExampleIsNumber(stringFieldTypeWithExplicitIntegerSchemaType);

JsonNode bigDecimalFieldTypeWithExplicitStringSchemaType = root.at("/schema/properties/bigDecimalFieldTypeWithExplicitStringSchemaType");
assertExampleIsString(bigDecimalFieldTypeWithExplicitStringSchemaType);

JsonNode bigDecimalFieldType = root.at("/schema/properties/bigDecimalFieldType");
assertExampleIsNumber(bigDecimalFieldType);
}

private void assertExampleIsNumber(JsonNode node) {
assertTrue(node.get("example").isNumber(), "should be a number");
}

private void assertExampleIsString(JsonNode node) {
assertTrue(node.get("example").isTextual(), "should be a string");
}

public static class ModelWithDifferentCombinationOfNumberFieldsWithExamples {

@io.swagger.v3.oas.annotations.media.Schema(example = "5 lacs per annum")
String stringFieldType;

@io.swagger.v3.oas.annotations.media.Schema(type = "string", example = "5 lacs per annum")
String stringFieldTypeWithExplicitStringSchemaType;

@io.swagger.v3.oas.annotations.media.Schema(type = "number", example = "10")
String stringFieldTypeWithExplicitNumberSchemaType;

@io.swagger.v3.oas.annotations.media.Schema(type = "integer", example = "5")
String stringFieldTypeWithExplicitIntegerSchemaType;

@io.swagger.v3.oas.annotations.media.Schema(type = "string", example = "13.37")
BigDecimal bigDecimalFieldTypeWithExplicitStringSchemaType;

@io.swagger.v3.oas.annotations.media.Schema(example = "13.37")
BigDecimal bigDecimalFieldType;

public String getStringFieldType() {
return stringFieldType;
}

public String getStringFieldTypeWithExplicitStringSchemaType() {
return stringFieldTypeWithExplicitStringSchemaType;
}

public String getStringFieldTypeWithExplicitNumberSchemaType() {
return stringFieldTypeWithExplicitNumberSchemaType;
}

public String getStringFieldTypeWithExplicitIntegerSchemaType() {
return stringFieldTypeWithExplicitIntegerSchemaType;
}

public BigDecimal getBigDecimalFieldTypeWithExplicitStringSchemaType() {
return bigDecimalFieldTypeWithExplicitStringSchemaType;
}

public BigDecimal getBigDecimalFieldType() {
return bigDecimalFieldType;
}

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.swagger.v3.core.issues;

import com.fasterxml.jackson.databind.node.NullNode;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverterContextImpl;
import io.swagger.v3.core.jackson.ModelResolver;
Expand Down Expand Up @@ -514,8 +515,8 @@ public void testNonNullableIntegerWithNullExampleAndDefault_OAS31() {
(io.swagger.v3.oas.models.media.Schema) model.getProperties().get("integerField");
assertNotNull(integerField, "integerField property should exist");

assertEquals(integerField.getExample(), "null",
"Example should be the string \"null\", not null value");
assertEquals(integerField.getExample(), NullNode.getInstance(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the addition of support for example/default "null", it was stated that a null should only occur if the field is also nullable. But I would assume that that is not the case here when the schema is a Number schema, and that the correct behavior is instead to retain the null as a JsonNode like the change here. Otherwise the value will not align with the type.

Ignoring null altogether could also be an option for this scenario. But I would need someone to state the expected behavior then. I could also implement so that default and example share their definition, since it seems that that should actually be the case? But that is for a separate PR most likely.

"Example should be the NullNode \"null\", not null value");
assertEquals(integerField.getDefault(), "null",
"Default should be the string \"null\", not null value");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.swagger.v3.core.util;

import com.fasterxml.jackson.databind.node.IntNode;
import com.google.common.collect.ImmutableMap;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand Down Expand Up @@ -104,6 +105,12 @@ class DummyClass implements Serializable {}
static class ExampleHolder {
@io.swagger.v3.oas.annotations.media.Schema(type = "string", example = "5 lacs per annum")
String value;

@io.swagger.v3.oas.annotations.media.Schema(type = "number", example = "10")
String numberValue;

@io.swagger.v3.oas.annotations.media.Schema(type = "integer", example = "5")
String integerValue;
}

@Test
Expand All @@ -128,6 +135,50 @@ public void testExampleStartingWithNumberShouldBeString() throws Exception {
assertEquals(schema.get().getExample(), "5 lacs per annum");
}

@Test
public void testExampleWithNumberTypeShouldHaveExampleAsNumber() throws Exception {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
ExampleHolder.class
.getDeclaredField("numberValue")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);

Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);

assertTrue(schema.isPresent());
assertEquals(schema.get().getExample(), IntNode.valueOf(10));
}

@Test
public void testExampleWithIntegerTypeShouldHaveExampleAsNumber() throws Exception {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
ExampleHolder.class
.getDeclaredField("integerValue")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);

Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);

assertTrue(schema.isPresent());
assertEquals(schema.get().getExample(), IntNode.valueOf(5));
}

// --- mergeSchemaAnnotations defaultValue tests ---

@io.swagger.v3.oas.annotations.media.Schema(description = "type-level description")
Expand Down