diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiModelProp.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiModelProp.java index 522aa86f87..f587829380 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiModelProp.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiModelProp.java @@ -34,6 +34,7 @@ public interface OpenApiModelProp { String PROP_REF_DOLLAR = "$ref"; String PROP_HIDDEN = "hidden"; String PROP_EXAMPLE = "example"; + String PROP_EXAMPLE_SET_FLAG = "exampleSetFlag"; String PROP_EXAMPLES = "examples"; String PROP_NOT = "not"; String PROP_ALL_OF = "allOf"; diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiNormalizeUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiNormalizeUtils.java index e6b2a54934..f1dfeaaa21 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiNormalizeUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiNormalizeUtils.java @@ -26,8 +26,10 @@ import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.Paths; import io.swagger.v3.oas.models.examples.Example; +import io.swagger.v3.oas.models.headers.Header; import io.swagger.v3.oas.models.media.Content; import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.responses.ApiResponse; @@ -43,6 +45,7 @@ import static io.micronaut.openapi.visitor.SchemaUtils.EMPTY_SIMPLE_SCHEMA; import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_OBJECT; import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_STRING; +import static io.micronaut.openapi.visitor.SchemaUtils.setSpecVersion; /** * Normalization methods for openAPI objects. @@ -134,10 +137,37 @@ public static void normalizeOperation(Operation operation, VisitorContext contex if (CollectionUtils.isNotEmpty(operation.getResponses())) { for (ApiResponse apiResponse : operation.getResponses().values()) { normalizeContent(apiResponse.getContent(), context); + normalizeHeaders(apiResponse.getHeaders(), context); } } } + public static void normalizeHeaders(Map headers, VisitorContext context) { + if (CollectionUtils.isEmpty(headers)) { + return; + } + + for (var header : headers.values()) { + Schema headerSchema = header.getSchema(); + if (headerSchema == null) { + headerSchema = setSpecVersion(new StringSchema()); + header.setSchema(headerSchema); + } + Schema normalizedSchema = normalizeSchema(headerSchema, context); + if (normalizedSchema != null) { + header.setSchema(normalizedSchema); + } else if (headerSchema.equals(EMPTY_SIMPLE_SCHEMA)) { + headerSchema.setType(TYPE_OBJECT); + } + if (header.getExample() != null + && header.getExample() instanceof String exampleStr) { + header.setExample(ConvertUtils.parseByTypeAndFormat(exampleStr, header.getSchema().getType(), header.getSchema().getFormat(), context, false)); + } + normalizeExamples(header.getExamples()); + normalizeContent(header.getContent(), context); + } + } + public static void normalizeContent(Content content, VisitorContext context) { if (CollectionUtils.isEmpty(content)) { return; @@ -233,9 +263,7 @@ public static Schema normalizeSchema(Schema schema, VisitorContext context schema.setType(type); schema.setAllOf(allOf); schema.setDefault(defaultValue); - if (schema.getExample() == null) { - schema.setExampleSetFlag(false); - } else if (TYPE_STRING.equals(schema.getType()) && schema.getExample() instanceof String exampleStr) { + if (!TYPE_STRING.equals(schema.getType()) && schema.getExample() instanceof String exampleStr) { schema.setExample(ConvertUtils.parseByTypeAndFormat(exampleStr, type, schema.getFormat(), context, false)); } normalizeSchemaProperties(schema, context); @@ -303,9 +331,7 @@ private static void normalizeSchemaProperties(Schema schema, VisitorContext c String type = schema.getType(); - if (schema.getExample() == null) { - schema.setExampleSetFlag(false); - } else if (!TYPE_STRING.equals(type) && schema.getExample() instanceof String exampleStr) { + if (!TYPE_STRING.equals(type) && schema.getExample() instanceof String exampleStr) { schema.setExample(ConvertUtils.parseByTypeAndFormat(exampleStr, type, schema.getFormat(), context, false)); } } diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java index f9f17281aa..e3b548d820 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java @@ -164,6 +164,7 @@ import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ENUM; import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXAMPLE; import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXAMPLES; +import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXAMPLE_SET_FLAG; import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXPRESSION; import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXTENSIONS; import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_EXTERNAL_DOCS; @@ -1336,6 +1337,9 @@ public static Map toValueMap(Map val newValues.remove(PROP_WRITE_ONLY); } } else { + if (key.equals(PROP_EXAMPLE)) { + newValues.put(PROP_EXAMPLE_SET_FLAG, true); + } var parsedJsonValue = parseJsonString(value); newValues.put(key, parsedJsonValue != null ? parsedJsonValue : value); } @@ -2553,6 +2557,12 @@ private static Schema doBindSchemaAnnotationValue(VisitorContext context, Typ } catch (IOException e) { warn("Error reading Swagger Schema for element [" + element + "]: " + e.getMessage(), context, element); } + // fix for example = "null" + if (schemaToBind.getExample() == null + && !schemaToBind.getExampleSetFlag() + && schemaJson.get(PROP_EXAMPLE_SET_FLAG) != null) { + schemaToBind.setExampleSetFlag(true); + } String defaultValue = null; String[] allowableValues = null; @@ -2666,7 +2676,7 @@ private static void schemaToValueMap(Map valueMap, Schema< if (entry.getKey().equals("specVersion")) { continue; } - if (entry.getKey().equals("exampleSetFlag") && StringUtils.FALSE.equals(value.toString())) { + if (entry.getKey().equals(PROP_EXAMPLE_SET_FLAG) && StringUtils.FALSE.equals(value.toString())) { continue; } valueMap.put(entry.getKey(), value); diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaFieldSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaFieldSpec.groovy index e29d8c6b74..bca503a1af 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaFieldSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaFieldSpec.groovy @@ -1402,4 +1402,49 @@ class MyBean {} schemas.User.properties.id.format == "int32" schemas.User.properties.name.type == "string" } + + void "test example set null"() { + + when: + buildBeanDefinition('test.MyBean', ''' +package test; + +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Put;import io.swagger.v3.oas.annotations.media.Schema;import jakarta.validation.constraints.Pattern; +import java.util.List; + +@Controller +class HelloController { + + @Put("/sendModelWithDiscriminator") + Parent sendModelWithDiscriminator() { + return null; + } +} + +class Parent { + + @Schema(nullable = true, example = "null") + public Integer id; +} + +@jakarta.inject.Singleton +class MyBean {} +''') + then: "the state is correct" + Utils.testReference != null + + when: "The OpenAPI is retrieved" + def openApi = Utils.testReference + def schemas = openApi.components.schemas + + then: "the components are valid" + schemas.Parent + schemas.Parent.properties.id + schemas.Parent.properties.id.type == 'integer' + schemas.Parent.properties.id.format == 'int32' + schemas.Parent.properties.id.nullable + schemas.Parent.properties.id.exampleSetFlag + schemas.Parent.properties.id.example == null + } }