Skip to content

Commit

Permalink
refs #4799 - allow composed constraint annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
wudip committed Dec 1, 2024
1 parent df88c8a commit 95bb9d6
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
import io.swagger.v3.oas.models.media.IntegerSchema;
import io.swagger.v3.oas.models.media.JsonSchema;
import io.swagger.v3.oas.models.media.MapSchema;
import io.swagger.v3.oas.models.media.NumberSchema;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
Expand Down Expand Up @@ -97,6 +96,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -1566,15 +1566,14 @@ protected void applyBeanValidatorAnnotations(BeanPropertyDefinition propDef, Sch
}
}

protected void applyBeanValidatorAnnotations(Schema property, Annotation[] annotations, Schema parent, boolean applyNotNullAnnotations) {
protected void applyBeanValidatorAnnotations(Schema property, Annotation[] directAnnotations, Schema parent, boolean applyNotNullAnnotations) {
Collection<Annotation> annotations = collectTransitiveAnnotations(directAnnotations); // Allows using composite constraints
Map<String, Annotation> annos = new HashMap<>();
if (annotations != null) {
for (Annotation anno : annotations) {
annos.put(anno.annotationType().getName(), anno);
}
for (Annotation anno : annotations) {
annos.put(anno.annotationType().getName(), anno);
}
if (parent != null && annotations != null && applyNotNullAnnotations) {
boolean requiredItem = Arrays.stream(annotations).anyMatch(annotation ->
if (parent != null && applyNotNullAnnotations) {
boolean requiredItem = annotations.stream().anyMatch(annotation ->
NOT_NULL_ANNOTATIONS.contains(annotation.annotationType().getSimpleName())
);
if (requiredItem) {
Expand Down Expand Up @@ -1633,6 +1632,24 @@ protected void applyBeanValidatorAnnotations(Schema property, Annotation[] annot
}
}

private Collection<Annotation> collectTransitiveAnnotations(Annotation[] annotations) {
if (annotations == null) {
return new HashSet<>();
}
LinkedHashSet<Annotation> annotationsToVisit = new LinkedHashSet<>(Arrays.asList(annotations));
Set<Annotation> collectedAnnotations = new HashSet<>();
while (!annotationsToVisit.isEmpty()) {
Annotation annotation = annotationsToVisit.iterator().next();
annotationsToVisit.remove(annotation);
if (!collectedAnnotations.contains(annotation)) {
collectedAnnotations.add(annotation);
Annotation[] annotationsOfAnnotation = annotation.annotationType().getAnnotations();
annotationsToVisit.addAll(Arrays.asList(annotationsOfAnnotation));
}
}
return collectedAnnotations;
}

private boolean resolveSubtypes(Schema model, BeanDescription bean, ModelConverterContext context, JsonView jsonViewAnnotation) {
final List<NamedType> types = _intr.findSubtypes(bean.getClassInfo());
if (types == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.swagger.v3.core.resolving;

import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverterContextImpl;
import io.swagger.v3.core.jackson.ModelResolver;
import io.swagger.v3.core.resolving.resources.JsonViewObject;
import io.swagger.v3.oas.models.media.Schema;
import org.junit.Test;
import org.testng.Assert;

import javax.validation.Constraint;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collections;
import java.util.Map;

import static io.swagger.v3.core.resolving.SwaggerTestBase.mapper;

public class Ticket4799Test {

@Test
@JsonView(JsonViewObject.View.Protected.class)
public void testCompositeConstraintsAreRespected() {
ObjectMapper mapper = mapper();
final ModelResolver modelResolver = new ModelResolver(mapper);
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

Schema model = context.resolve(new AnnotatedType(ClassWithAnnotation.class));

Map<String, Schema<?>> properties = model.getProperties();
Assert.assertEquals(properties.size(), 1);
Schema<?> nameSchema = properties.get("name");
Assert.assertNotNull(nameSchema);
Assert.assertEquals(nameSchema.getMinLength(), 5);
Assert.assertEquals(nameSchema.getMaxLength(), 10);
Assert.assertEquals(nameSchema.getPattern(), "^[0-9]*$");
Assert.assertEquals(model.getRequired(), Collections.singletonList("name"));
}

public static final class ClassWithAnnotation {
@Pattern(regexp = "^[0-9]*$")
@CompositeAnnotation
public String name;
}

@Size(min = 5, max = 10)
@NotNull
@Constraint(validatedBy = {})
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface CompositeAnnotation {
}
}

0 comments on commit 95bb9d6

Please sign in to comment.