Skip to content

Commit

Permalink
Fixed additionalProperties map with @NotEmpty annotation
Browse files Browse the repository at this point in the history
Fixed #1906
  • Loading branch information
altro3 committed Dec 15, 2024
1 parent b96d021 commit 8b93cf6
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ public class ModelDeserializer extends JsonDeserializer<Schema> {
protected boolean openapi31;

@Override
public Schema deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
public Schema deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);

Schema schema = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,10 @@ public static Schema<?> bindSchemaForElement(VisitorContext context, TypedElemen
}

if (addSchemaToBind) {
if (originalSchema.getAdditionalProperties() != null) {
SchemaUtils.appendSchema(originalSchema, schemaToBind);
return originalSchema;
}
if (TYPE_OBJECT.equals(originalSchema.getType()) && !(originalSchema instanceof MapSchema)) {
if (composedSchema.getType() == null) {
composedSchema.setType(TYPE_OBJECT);
Expand Down Expand Up @@ -2505,7 +2509,8 @@ private static boolean processJacksonPropertyAnn(Element element, ClassElement e

private static void processJakartaValidationAnnotations(Element element, ClassElement elementType, Schema<?> schemaToBind, VisitorContext context) {

final boolean isIterableOrMap = elementType.isIterable() || elementType.isAssignable(Map.class);
final boolean isAdditionalPropertiesMap = elementType.isAssignable(Map.class);
final boolean isIterableOrMap = elementType.isIterable() || isAdditionalPropertiesMap;

var messages = new HashMap<String, String>();

Expand All @@ -2515,7 +2520,12 @@ private static void processJakartaValidationAnnotations(Element element, ClassEl
if (isIterableOrMap) {
if (isAnnotationPresent(element, "javax.validation.constraints.NotEmpty$List")
|| isAnnotationPresent(element, "jakarta.validation.constraints.NotEmpty$List")) {
schemaToBind.setMinItems(1);

if (isAdditionalPropertiesMap) {
schemaToBind.setMinProperties(1);
} else {
schemaToBind.setMinItems(1);
}

addValidationAnnMessage(element, "javax.validation.constraints.NotEmpty$List", SIZE_MESSAGE, messages, context);
addValidationAnnMessage(element, "jakarta.validation.constraints.NotEmpty$List", SIZE_MESSAGE, messages, context);
Expand All @@ -2527,20 +2537,44 @@ private static void processJakartaValidationAnnotations(Element element, ClassEl
findAnnotation(element, "javax.validation.constraints.Size$List")
.ifPresent(listAnn -> listAnn.getValue(AnnotationValue.class)
.ifPresent(ann -> ann.intValue("min")
.ifPresent(schemaToBind::setMinItems)));
.ifPresent((v) -> {
if (isAdditionalPropertiesMap) {
schemaToBind.setMinProperties(v);
} else {
schemaToBind.setMinItems(v);
}
})));
findAnnotation(element, "jakarta.validation.constraints.Size$List")
.ifPresent(listAnn -> listAnn.getValue(AnnotationValue.class)
.ifPresent(ann -> ann.intValue("min")
.ifPresent(schemaToBind::setMinItems)));
.ifPresent((v) -> {
if (isAdditionalPropertiesMap) {
schemaToBind.setMinProperties(v);
} else {
schemaToBind.setMinItems(v);
}
})));

findAnnotation(element, "javax.validation.constraints.Size$List")
.ifPresent(listAnn -> listAnn.getValue(AnnotationValue.class)
.ifPresent(ann -> ann.intValue("max")
.ifPresent(schemaToBind::setMaxItems)));
.ifPresent((v) -> {
if (isAdditionalPropertiesMap) {
schemaToBind.setMaxProperties(v);
} else {
schemaToBind.setMaxItems(v);
}
})));
findAnnotation(element, "jakarta.validation.constraints.Size$List")
.ifPresent(listAnn -> listAnn.getValue(AnnotationValue.class)
.ifPresent(ann -> ann.intValue("max")
.ifPresent(schemaToBind::setMaxItems)));
.ifPresent((v) -> {
if (isAdditionalPropertiesMap) {
schemaToBind.setMaxProperties(v);
} else {
schemaToBind.setMaxItems(v);
}
})));

} else {
if (PrimitiveType.STRING.getCommonName().equals(schemaToBind.getType())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1752,4 +1752,68 @@ public class MyBean {}
responseSchema.oneOf
responseSchema.oneOf.size() == 2
}

@Issue("https://github.com/micronaut-projects/micronaut-openapi/issues/1906")
void "test additionalProperties map with NotEmpty"() {
when:
buildBeanDefinition("test.MyBean", '''
package test;
import io.micronaut.data.model.Page;
import io.micronaut.data.model.Pageable;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Status;
import io.micronaut.serde.annotation.Serdeable;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import java.util.Map;
import static io.micronaut.http.HttpResponse.ok;
import static io.micronaut.http.HttpStatus.OK;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
@Controller(MyController.BASE_ENDPOINT)
class MyController {
public static final String BASE_ENDPOINT = "/items";
@Status(OK)
@Get("/search")
public HttpResponse<Page<MyItem>> searchItems(Pageable pageable) {
return ok();
}
}
@Serdeable
record MyItem(
@Schema(description = "My 1st field", requiredMode = REQUIRED)
String field1,
@Schema(description = "An other field", requiredMode = NOT_REQUIRED)
Integer field2,
@NotEmpty
@Schema
Map<String, String> name
) {
}
@jakarta.inject.Singleton
public class MyBean {}
''')

OpenAPI openAPI = Utils.testReference
def itemSchema = openAPI.components.schemas.MyItem

then:

itemSchema
itemSchema.properties.name
itemSchema.properties.name.additionalProperties
itemSchema.properties.name.additionalProperties instanceof Schema
itemSchema.properties.name.minProperties == 1
((Schema) itemSchema.properties.name.additionalProperties).type == "string"
}
}

0 comments on commit 8b93cf6

Please sign in to comment.