1919import java .lang .annotation .Target ;
2020import java .util .Map ;
2121
22- import static org .testng .Assert .assertEquals ;
23- import static org .testng .Assert .assertNotNull ;
22+ import static org .testng .Assert .*;
2423
2524public class ComposedConstraintMetaAnnotationTest {
2625
@@ -56,11 +55,11 @@ public class ComposedConstraintMetaAnnotationTest {
5655 }
5756
5857 /**
59- * Mimics how Hibernate Validator's @ Range works: meta-annotations @Min/@Max carry default
58+ * Mimics how Hibernate Validator's {@code @ Range} works: meta-annotations @Min/@Max carry default
6059 * values, while the actual per-use values are meant to be applied via @OverridesAttribute.
6160 * Our implementation reads meta-annotations from the annotation *type definition*, so it
6261 * always sees the defaults (min=0, max=Long.MAX_VALUE) — not whatever the caller passes
63- * as @ ValidRange(min=5, max=50). This is a known limitation documented by the test below.
62+ * as {@code @ ValidRange(min=5, max=50)} . This is a known limitation documented by the test below.
6463 */
6564 @ Min (0 )
6665 @ Max (Long .MAX_VALUE )
@@ -79,6 +78,20 @@ public class ComposedConstraintMetaAnnotationTest {
7978 Class <? extends Payload >[] payload () default {};
8079 }
8180
81+ @ Min (4 )
82+ @ Max (Long .MAX_VALUE )
83+ @ Target ({ElementType .FIELD , ElementType .PARAMETER })
84+ @ Retention (RetentionPolicy .RUNTIME )
85+ @ Constraint (validatedBy = {})
86+ public @interface FourOrMore {
87+ @ OverridesAttribute (constraint = Max .class , name = "value" )
88+ long max () default Long .MAX_VALUE ;
89+
90+ String message () default "Out of range" ;
91+ Class <?>[] groups () default {};
92+ Class <? extends Payload >[] payload () default {};
93+ }
94+
8295 @ ValidStoreId
8396 @ Target ({ElementType .FIELD , ElementType .PARAMETER })
8497 @ Retention (RetentionPolicy .RUNTIME )
@@ -105,9 +118,6 @@ static class TestStoreDto {
105118 @ ValidEmail
106119 private String email ;
107120
108- @ ValidRange (min = 5 , max = 50 )
109- private Short rangeField ;
110-
111121 @ ValidStoreIdNested
112122 private Short nestedStoreId ;
113123
@@ -123,14 +133,27 @@ static class TestStoreDto {
123133 public void setName (String name ) { this .name = name ; }
124134 public String getEmail () { return email ; }
125135 public void setEmail (String email ) { this .email = email ; }
126- public Short getRangeField () { return rangeField ; }
127- public void setRangeField (Short rangeField ) { this .rangeField = rangeField ; }
128136 public Short getNestedStoreId () { return nestedStoreId ; }
129137 public void setNestedStoreId (Short nestedStoreId ) { this .nestedStoreId = nestedStoreId ; }
130138 public Short getPriorityStoreId () { return priorityStoreId ; }
131139 public void setPriorityStoreId (Short priorityStoreId ) { this .priorityStoreId = priorityStoreId ; }
132140 }
133141
142+ static class ComposedAnnotationsDto {
143+ @ ValidRange (min = 5 , max = 50 )
144+ private Short rangeField ;
145+
146+ @ FourOrMore (max = 10 )
147+ private Short partiallyOverriddenComposedField ;
148+
149+ public Short getRangeField () { return rangeField ; }
150+ public void setRangeField (Short rangeField ) { this .rangeField = rangeField ; }
151+ public Short getPartiallyOverriddenComposedField () { return partiallyOverriddenComposedField ; }
152+ public void setPartiallyOverriddenComposedField (Short partiallyOverriddenComposedField ) {
153+ this .partiallyOverriddenComposedField = partiallyOverriddenComposedField ;
154+ }
155+ }
156+
134157 @ Test
135158 public void readsComposedMinMaxConstraintOnDtoField () {
136159 Map <String , Schema > schemas = ModelConverters .getInstance ().readAll (TestStoreDto .class );
@@ -192,23 +215,36 @@ public void directAnnotationTakesPriorityOverMetaAnnotation() {
192215 }
193216
194217 /**
195- * Documents a known limitation: for @Range-style constraints that rely on @ OverridesAttribute
218+ * Documents a known limitation: for @Range-style constraints that rely on {@link OverridesAttribute}
196219 * to propagate per-use values (e.g. @ValidRange(min=5, max=50)) into their meta-annotations
197220 * (@Min/@Max), our implementation reads constraints from the annotation *type definition*
198221 * and therefore always sees the default values (min=0, max=Long.MAX_VALUE), not the
199- * caller-supplied ones. Handling @OverridesAttribute is not yet supported.
222+ * caller-supplied ones. Handling {@link OverridesAttribute} is not yet supported, and instead the annotation and
223+ * its composing/meta annotations are ignored entirely.
200224 */
201225 @ Test
202226 public void rangeStyleConstraintUsesDefaultsNotOverriddenValues () {
203- Map <String , Schema > schemas = ModelConverters .getInstance ().readAll (TestStoreDto .class );
204- Schema model = schemas .get ("TestStoreDto " );
227+ Map <String , Schema > schemas = ModelConverters .getInstance ().readAll (ComposedAnnotationsDto .class );
228+ Schema model = schemas .get ("ComposedAnnotationsDto " );
205229 Schema range = (Schema ) model .getProperties ().get ("rangeField" );
206230 assertNotNull (range , "rangeField property should exist" );
207231 // We pick up the *default* values from @Min(0) and @Max(Long.MAX_VALUE) on the type
208- // definition of @ValidRange, NOT the caller-supplied @ValidRange(min=5, max=50).
209- assertEquals (range .getMinimum ().longValue (), 0L ,
210- "expected default @Min(0) from type definition, not overridden min=5" );
211- assertEquals (range .getMaximum ().longValue (), Long .MAX_VALUE ,
212- "expected default @Max(Long.MAX_VALUE) from type definition, not overridden max=50" );
232+ // definition of @ValidRange, But we then drop them since we see that they are modified with an OverridesAttribute.
233+ assertNull (range .getMinimum (),
234+ "expected null since we drop the @Min from the overridden composed @ValidRange annotation" );
235+ assertNull (range .getMaximum (),
236+ "expected null since we drop the @Max from the overridden composed @ValidRange annotation" );
237+ }
238+
239+ @ Test
240+ public void composedStyleConstraintUsesOnlyNonOverrideableValues () {
241+ Map <String , Schema > schemas = ModelConverters .getInstance ().readAll (ComposedAnnotationsDto .class );
242+ Schema model = schemas .get ("ComposedAnnotationsDto" );
243+ Schema range = (Schema ) model .getProperties ().get ("partiallyOverriddenComposedField" );
244+ assertNotNull (range , "partiallyOverriddenComposedField property should exist" );
245+ assertEquals (range .getMinimum ().longValue (), 4L ,
246+ "expected 4 from type definition since it does not have an OverridesAttribute" );
247+ assertNull (range .getMaximum (),
248+ "expected null since we drop the @Max from the overridden composed @FourOrMore annotation" );
213249 }
214250}
0 commit comments