View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.httppart;
18  
19  import static org.apache.juneau.TestUtils.*;
20  import static org.apache.juneau.commons.utils.CollectionUtils.*;
21  import static org.apache.juneau.commons.utils.StringUtils.*;
22  import static org.apache.juneau.junit.bct.BctAssertions.*;
23  import static org.junit.jupiter.api.Assertions.*;
24  
25  import org.apache.juneau.*;
26  import org.apache.juneau.annotation.*;
27  import org.apache.juneau.commons.reflect.*;
28  import org.apache.juneau.http.annotation.*;
29  import org.junit.jupiter.api.*;
30  
31  class HttpPartSchema_Body_Test extends TestBase {
32  
33  	//-----------------------------------------------------------------------------------------------------------------
34  	// Basic test
35  	//-----------------------------------------------------------------------------------------------------------------
36  	@Test void a01_basic() {
37  		assertDoesNotThrow(()->HttpPartSchema.create().build());
38  	}
39  
40  	//-----------------------------------------------------------------------------------------------------------------
41  	// @Body
42  	//-----------------------------------------------------------------------------------------------------------------
43  
44  	@Content
45  	@Schema(
46  		d={"b1","b2"},
47  		$ref="c1",
48  		r=true
49  	)
50  	public static class A02 {}
51  
52  	@Test void a02_basic_onClass() {
53  		var s = HttpPartSchema.create().applyAll(Content.class, A02.class).noValidate().build();
54  		assertTrue(s.isRequired());
55  	}
56  
57  	public static class A03 {
58  		public void a(  // NOSONAR
59  				@Content
60  				@Schema(
61  					d={"b1","b2"},
62  					$ref="c1",
63  					r=true
64  				)
65  				String x
66  			) {
67  
68  		}
69  	}
70  
71  	@Test void a03_basic_onParameter() throws Exception {
72  		var mpi = MethodInfo.of(A03.class.getMethod("a", String.class)).getParameter(0);
73  		var s = HttpPartSchema.create().applyAll(Content.class, mpi).noValidate().build();
74  		assertTrue(s.isRequired());
75  	}
76  
77  	public static class A04 {
78  		public void a(  // NOSONAR
79  				@Content
80  				@Schema(
81  					d={"b3","b3"},
82  					$ref="c3",
83  					r=true
84  				)
85  				A02 x
86  			) {
87  
88  		}
89  	}
90  
91  	@Test void a04_basic_onParameterAndClass() throws Exception {
92  		var mpi = MethodInfo.of(A04.class.getMethod("a", A02.class)).getParameter(0);
93  		var s = HttpPartSchema.create().applyAll(Content.class, mpi).noValidate().build();
94  		assertTrue(s.isRequired());
95  	}
96  
97  	@Content
98  	@Schema(
99  		t="number",
100 		f="int32",
101 		max="1",
102 		min="2",
103 		mo="3",
104 		p="4",
105 		maxl=1,
106 		minl=2,
107 		maxi=3,
108 		mini=4,
109 		maxp=5,
110 		minp=6,
111 		emax=true,
112 		emin=true,
113 		ui=true,
114 		df={"c1","c2"},
115 		e="e1,e2",
116 		items=@Items(
117 			t="integer",
118 			f="int64",
119 			cf="ssv",
120 			max="5",
121 			min="6",
122 			mo="7",
123 			p="8",
124 			maxl=5,
125 			minl=6,
126 			maxi=7,
127 			mini=8,
128 			emax=false,
129 			emin=false,
130 			ui=false,
131 			df={"c3","c4"},
132 			e="e3,e4",
133 			items=@SubItems(
134 				t="string",
135 				f="float",
136 				cf="tsv",
137 				max="9",
138 				min="10",
139 				mo="11",
140 				p="12",
141 				maxl=9,
142 				minl=10,
143 				maxi=11,
144 				mini=12,
145 				emax=true,
146 				emin=true,
147 				ui=true,
148 				df={"c5","c6"},
149 				e="e5,e6",
150 				items={
151 					"type:'array',",
152 					"format:'double',",
153 					"collectionFormat:'pipes',",
154 					"maximum:'13',",
155 					"minimum:'14',",
156 					"multipleOf:'15',",
157 					"pattern:'16',",
158 					"maxLength:13,",
159 					"minLength:14,",
160 					"maxItems:15,",
161 					"minItems:16,",
162 					"exclusiveMaximum:false,",
163 					"exclusiveMinimum:false,",
164 					"uniqueItems:false,",
165 					"default:'c7\\nc8',",
166 					"enum:['e7','e8']",
167 				}
168 			)
169 		)
170 	)
171 	public static class A05 {}
172 
173 	@Test void a05_basic_nestedItems_onClass() {
174 		var s = HttpPartSchema.create().applyAll(Content.class, A05.class).noValidate().build();
175 
176 		assertBean(
177 			s,
178 			"type,format,maximum,minimum,multipleOf,pattern,maxLength,minLength,maxItems,minItems,maxProperties,minProperties,exclusiveMaximum,exclusiveMinimum,uniqueItems,enum,default",
179 			"NUMBER,INT32,1,2,3,4,1,2,3,4,5,6,true,true,true,[e1,e2],c1\nc2"
180 		);
181 
182 		var items = s.getItems();
183 		assertBean(
184 			items,
185 			"type,format,collectionFormat,maximum,minimum,multipleOf,pattern,maxLength,minLength,maxItems,minItems,exclusiveMaximum,exclusiveMinimum,uniqueItems,enum,default",
186 			"INTEGER,INT64,SSV,5,6,7,8,5,6,7,8,false,false,false,[e3,e4],c3\nc4"
187 		);
188 
189 		items = items.getItems();
190 		assertBean(
191 			items,
192 			"type,format,collectionFormat,maximum,minimum,multipleOf,pattern,maxLength,minLength,maxItems,minItems,exclusiveMaximum,exclusiveMinimum,uniqueItems,enum,default",
193 			"STRING,FLOAT,TSV,9,10,11,12,9,10,11,12,true,true,true,[e5,e6],c5\nc6"
194 		);
195 
196 		items = items.getItems();
197 		assertBean(
198 			items,
199 			"type,format,collectionFormat,maximum,minimum,multipleOf,pattern,maxLength,minLength,maxItems,minItems,exclusiveMaximum,exclusiveMinimum,uniqueItems,enum,default",
200 			"ARRAY,DOUBLE,PIPES,13,14,15,16,13,14,15,16,false,false,false,[e7,e8],c7\nc8"
201 		);
202 	}
203 
204 	//-----------------------------------------------------------------------------------------------------------------
205 	// String input validations.
206 	//-----------------------------------------------------------------------------------------------------------------
207 
208 	@Content @Schema(required=true)
209 	public static class B01a {}
210 
211 	@Test void b01a_required() throws Exception {
212 		var s = HttpPartSchema.create().applyAll(Content.class, B01a.class).build();
213 
214 		s.validateInput("x");
215 		assertThrowsWithMessage(SchemaValidationException.class, "No value specified.", ()->s.validateInput(null));
216 		assertThrowsWithMessage(SchemaValidationException.class, "Empty value not allowed.", ()->s.validateInput(""));
217 	}
218 
219 	@Content
220 	@Schema(p="x.*",aev=true)
221 	public static class B02a {}
222 
223 	@Test void b02a_pattern() throws Exception {
224 		var s = HttpPartSchema.create().applyAll(Content.class, B02a.class).build();
225 
226 		s.validateInput("x");
227 		s.validateInput("xx");
228 
229 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match expected pattern.  Must match pattern: x.*", ()->s.validateInput(""));
230 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match expected pattern.  Must match pattern: x.*", ()->s.validateInput("y"));
231 	}
232 
233 	@Content
234 	@Schema(
235 		items=@Items(
236 			p="w.*",
237 			items=@SubItems(
238 				p="x.*",
239 				items={
240 					"pattern:'y.*',",
241 					"items:{pattern:'z.*'}"
242 				}
243 			)
244 		)
245 	)
246 	public static class B02b {}
247 
248 	@Content
249 	@Schema(
250 		minl=2, maxl=3
251 	)
252 	public static class B03a {}
253 
254 	@Test void b03a_length() throws Exception {
255 		var s = HttpPartSchema.create().applyAll(Content.class, B03a.class).build();
256 		s.validateInput("12");
257 		s.validateInput("123");
258 		s.validateInput(null);
259 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.validateInput("1"));
260 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.validateInput("1234"));
261 	}
262 
263 	@Content
264 	@Schema(
265 		items=@Items(
266 			minl=2, maxl=3,
267 			items=@SubItems(
268 				minl=3, maxl=4,
269 				items={
270 					"minLength:4,maxLength:5,",
271 					"items:{minLength:5,maxLength:6}"
272 				}
273 			)
274 		)
275 	)
276 	public static class B03b {}
277 
278 	@Test void b03b_length_items() throws Exception {
279 		var s = HttpPartSchema.create().applyAll(Content.class, B03b.class).build();
280 
281 		s.getItems().validateInput("12");
282 		s.getItems().getItems().validateInput("123");
283 		s.getItems().getItems().getItems().validateInput("1234");
284 		s.getItems().getItems().getItems().getItems().validateInput("12345");
285 
286 		s.getItems().validateInput("123");
287 		s.getItems().getItems().validateInput("1234");
288 		s.getItems().getItems().getItems().validateInput("12345");
289 		s.getItems().getItems().getItems().getItems().validateInput("123456");
290 
291 		s.getItems().validateInput(null);
292 		s.getItems().getItems().validateInput(null);
293 		s.getItems().getItems().getItems().validateInput(null);
294 		s.getItems().getItems().getItems().getItems().validateInput(null);
295 
296 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.getItems().validateInput("1"));
297 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.getItems().getItems().validateInput("12"));
298 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.getItems().getItems().getItems().validateInput("123"));
299 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.getItems().getItems().getItems().getItems().validateInput("1234"));
300 
301 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.getItems().validateInput("1234"));
302 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.getItems().getItems().validateInput("12345"));
303 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.getItems().getItems().getItems().validateInput("123456"));
304 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateInput("1234567"));
305 	}
306 
307 	@Content
308 	@Schema(
309 		e="X,Y"
310 	)
311 	public static class B04a {}
312 
313 	@Test void b04a_enum() throws Exception {
314 		var s = HttpPartSchema.create().applyAll(Content.class, B04a.class).build();
315 		s.validateInput("X");
316 		s.validateInput("Y");
317 		s.validateInput(null);
318 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  X, Y", ()->s.validateInput("Z"));
319 	}
320 
321 	@Content
322 	@Schema(
323 		e=" X , Y "
324 	)
325 	public static class B04b {}
326 
327 	@Test void b04b_enum() throws Exception {
328 		var s = HttpPartSchema.create().applyAll(Content.class, B04b.class).build();
329 		s.validateInput("X");
330 		s.validateInput("Y");
331 		s.validateInput(null);
332 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  X, Y", ()->s.validateInput("Z"));
333 	}
334 
335 	@Content
336 	@Schema(
337 		e="X,Y"
338 	)
339 	public static class B04c {}
340 
341 	@Test void b04c_enum_json() throws Exception {
342 		var s = HttpPartSchema.create().applyAll(Content.class, B04c.class).build();
343 		s.validateInput("X");
344 		s.validateInput("Y");
345 		s.validateInput(null);
346 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  X, Y", ()->s.validateInput("Z"));
347 	}
348 
349 	@Content
350 	@Schema(
351 		items=@Items(
352 			e="W",
353 			items=@SubItems(
354 				e="X",
355 				items={
356 					"enum:['Y'],",
357 					"items:{enum:['Z']}"
358 				}
359 			)
360 		)
361 	)
362 	public static class B04d {}
363 
364 	@Test void b04d_enum_items() throws Exception {
365 		var s = HttpPartSchema.create().applyAll(Content.class, B04d.class).build();
366 
367 		s.getItems().validateInput("W");
368 		s.getItems().getItems().validateInput("X");
369 		s.getItems().getItems().getItems().validateInput("Y");
370 		s.getItems().getItems().getItems().getItems().validateInput("Z");
371 
372 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  W", ()->s.getItems().validateInput("V"));
373 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  X", ()->s.getItems().getItems().validateInput("V"));
374 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  Y", ()->s.getItems().getItems().getItems().validateInput("V"));
375 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  Z", ()->s.getItems().getItems().getItems().getItems().validateInput("V"));
376 	}
377 
378 	//-----------------------------------------------------------------------------------------------------------------
379 	// Numeric validations
380 	//-----------------------------------------------------------------------------------------------------------------
381 
382 	@Content
383 	@Schema(
384 		min="10", max="100"
385 	)
386 	public static class C01a {}
387 
388 	@Test void c01a_minmax_ints() throws Exception {
389 		var s = HttpPartSchema.create().applyAll(Content.class, C01a.class).build();
390 		s.validateOutput(10, BeanContext.DEFAULT);
391 		s.validateOutput(100, BeanContext.DEFAULT);
392 		s.validateOutput(null, BeanContext.DEFAULT);
393 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(9, BeanContext.DEFAULT));
394 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(101, BeanContext.DEFAULT));
395 	}
396 
397 	@Content
398 	@Schema(
399 		items=@Items(
400 			min="10", max="100",
401 			items=@SubItems(
402 				min="100", max="1000",
403 				items={
404 					"minimum:1000,maximum:10000,",
405 					"items:{minimum:10000,maximum:100000}"
406 				}
407 			)
408 		)
409 	)
410 	public static class C01b {}
411 
412 	@Test void c01b_minmax_ints_items() throws Exception {
413 		var s = HttpPartSchema.create().applyAll(Content.class, C01b.class).build();
414 
415 		s.getItems().validateOutput(10, BeanContext.DEFAULT);
416 		s.getItems().getItems().validateOutput(100, BeanContext.DEFAULT);
417 		s.getItems().getItems().getItems().validateOutput(1000, BeanContext.DEFAULT);
418 		s.getItems().getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT);
419 
420 		s.getItems().validateOutput(100, BeanContext.DEFAULT);
421 		s.getItems().getItems().validateOutput(1000, BeanContext.DEFAULT);
422 		s.getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT);
423 		s.getItems().getItems().getItems().getItems().validateOutput(100000, BeanContext.DEFAULT);
424 
425 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().validateOutput(9, BeanContext.DEFAULT));
426 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().validateOutput(99, BeanContext.DEFAULT));
427 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().validateOutput(999, BeanContext.DEFAULT));
428 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(9999, BeanContext.DEFAULT));
429 
430 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().validateOutput(101, BeanContext.DEFAULT));
431 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().validateOutput(1001, BeanContext.DEFAULT));
432 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().validateOutput(10001, BeanContext.DEFAULT));
433 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(100001, BeanContext.DEFAULT));
434 	}
435 
436 	@Content
437 	@Schema(
438 		min="10", max="100", emin=true, emax=true
439 	)
440 	public static class C02a {}
441 
442 	@Test void c02a_minmax_exclusive() throws Exception {
443 		var s = HttpPartSchema.create().applyAll(Content.class, C02a.class).build();
444 		s.validateOutput(11, BeanContext.DEFAULT);
445 		s.validateOutput(99, BeanContext.DEFAULT);
446 		s.validateOutput(null, BeanContext.DEFAULT);
447 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(10, BeanContext.DEFAULT));
448 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(100, BeanContext.DEFAULT));
449 	}
450 
451 	@Content
452 	@Schema(
453 		items=@Items(
454 			min="10", max="100", emin=true, emax=true,
455 			items=@SubItems(
456 				min="100", max="1000", emin=true, emax=true,
457 				items={
458 					"minimum:1000,maximum:10000,exclusiveMinimum:true,exclusiveMaximum:true,",
459 					"items:{minimum:10000,maximum:100000,exclusiveMinimum:true,exclusiveMaximum:true}"
460 				}
461 			)
462 		)
463 	)
464 	public static class C02b {}
465 
466 	@Test void c02b_minmax_exclusive_items() throws Exception {
467 		var s = HttpPartSchema.create().applyAll(Content.class, C02b.class).build();
468 
469 		s.getItems().validateOutput(11, BeanContext.DEFAULT);
470 		s.getItems().getItems().validateOutput(101, BeanContext.DEFAULT);
471 		s.getItems().getItems().getItems().validateOutput(1001, BeanContext.DEFAULT);
472 		s.getItems().getItems().getItems().getItems().validateOutput(10001, BeanContext.DEFAULT);
473 
474 		s.getItems().validateOutput(99, BeanContext.DEFAULT);
475 		s.getItems().getItems().validateOutput(999, BeanContext.DEFAULT);
476 		s.getItems().getItems().getItems().validateOutput(9999, BeanContext.DEFAULT);
477 		s.getItems().getItems().getItems().getItems().validateOutput(99999, BeanContext.DEFAULT);
478 
479 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().validateOutput(10, BeanContext.DEFAULT));
480 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().validateOutput(100, BeanContext.DEFAULT));
481 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().validateOutput(1000, BeanContext.DEFAULT));
482 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT));
483 
484 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().validateOutput(100, BeanContext.DEFAULT));
485 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().validateOutput(1000, BeanContext.DEFAULT));
486 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT));
487 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(100000, BeanContext.DEFAULT));
488 	}
489 
490 	@Content
491 	@Schema(
492 		min="10.1", max="100.1"
493 	)
494 	public static class C03a {}
495 
496 	@Test void c03_minmax_floats() throws Exception {
497 		var s = HttpPartSchema.create().applyAll(Content.class, C03a.class).build();
498 		s.validateOutput(10.1f, BeanContext.DEFAULT);
499 		s.validateOutput(100.1f, BeanContext.DEFAULT);
500 		s.validateOutput(null, BeanContext.DEFAULT);
501 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(10f, BeanContext.DEFAULT));
502 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(100.2f, BeanContext.DEFAULT));
503 	}
504 
505 	@Content
506 	@Schema(
507 		items=@Items(
508 			min="10.1", max="100.1",
509 			items=@SubItems(
510 				min="100.1", max="1000.1",
511 				items={
512 					"minimum:1000.1,maximum:10000.1,",
513 					"items:{minimum:10000.1,maximum:100000.1}"
514 				}
515 			)
516 		)
517 	)
518 	public static class C03b {}
519 
520 	@Test void c03b_minmax_floats_items() throws Exception {
521 		var s = HttpPartSchema.create().applyAll(Content.class, C03b.class).build();
522 
523 		s.getItems().validateOutput(10.1f, BeanContext.DEFAULT);
524 		s.getItems().getItems().validateOutput(100.1f, BeanContext.DEFAULT);
525 		s.getItems().getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT);
526 		s.getItems().getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT);
527 
528 		s.getItems().validateOutput(100.1f, BeanContext.DEFAULT);
529 		s.getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT);
530 		s.getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT);
531 		s.getItems().getItems().getItems().getItems().validateOutput(100000.1f, BeanContext.DEFAULT);
532 
533 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().validateOutput(10f, BeanContext.DEFAULT));
534 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().validateOutput(100f, BeanContext.DEFAULT));
535 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().validateOutput(1000f, BeanContext.DEFAULT));
536 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10000f, BeanContext.DEFAULT));
537 
538 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().validateOutput(100.2f, BeanContext.DEFAULT));
539 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().validateOutput(1000.2f, BeanContext.DEFAULT));
540 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().validateOutput(10000.2f, BeanContext.DEFAULT));
541 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(100000.2f, BeanContext.DEFAULT));
542 	}
543 
544 	@Content
545 	@Schema(
546 		min="10.1", max="100.1", emin=true, emax=true
547 	)
548 	public static class C04a {}
549 
550 	@Test void c04a_minmax_floats_exclusive() throws Exception {
551 		var s = HttpPartSchema.create().applyAll(Content.class, C04a.class).build();
552 		s.validateOutput(10.2f, BeanContext.DEFAULT);
553 		s.validateOutput(100f, BeanContext.DEFAULT);
554 		s.validateOutput(null, BeanContext.DEFAULT);
555 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(10.1f, BeanContext.DEFAULT));
556 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(100.1f, BeanContext.DEFAULT));
557 	}
558 
559 	@Content
560 	@Schema(
561 		items=@Items(
562 			min="10.1", max="100.1", emin=true, emax=true,
563 			items=@SubItems(
564 				min="100.1", max="1000.1", emin=true, emax=true,
565 				items={
566 					"minimum:1000.1,maximum:10000.1,exclusiveMinimum:true,exclusiveMaximum:true,",
567 					"items:{minimum:10000.1,maximum:100000.1,exclusiveMinimum:true,exclusiveMaximum:true}"
568 				}
569 			)
570 		)
571 	)
572 	public static class C04b {}
573 
574 	@Test void c04b_minmax_floats_exclusive_items() throws Exception {
575 		var s = HttpPartSchema.create().applyAll(Content.class, C04b.class).build();
576 
577 		s.getItems().validateOutput(10.2f, BeanContext.DEFAULT);
578 		s.getItems().getItems().validateOutput(100.2f, BeanContext.DEFAULT);
579 		s.getItems().getItems().getItems().validateOutput(1000.2f, BeanContext.DEFAULT);
580 		s.getItems().getItems().getItems().getItems().validateOutput(10000.2f, BeanContext.DEFAULT);
581 
582 		s.getItems().validateOutput(100f, BeanContext.DEFAULT);
583 		s.getItems().getItems().validateOutput(1000f, BeanContext.DEFAULT);
584 		s.getItems().getItems().getItems().validateOutput(10000f, BeanContext.DEFAULT);
585 		s.getItems().getItems().getItems().getItems().validateOutput(100000f, BeanContext.DEFAULT);
586 
587 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().validateOutput(10.1f, BeanContext.DEFAULT));
588 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().validateOutput(100.1f, BeanContext.DEFAULT));
589 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT));
590 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT));
591 
592 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().validateOutput(100.1f, BeanContext.DEFAULT));
593 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT));
594 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT));
595 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(100000.1f, BeanContext.DEFAULT));
596 	}
597 
598 	@Content
599 	@Schema(
600 		mo="10"
601 	)
602 	public static class C05a {}
603 
604 	@Test void c05a_multipleOf() throws Exception {
605 		var s = HttpPartSchema.create().applyAll(Content.class, C05a.class).build();
606 		s.validateOutput(0, BeanContext.DEFAULT);
607 		s.validateOutput(10, BeanContext.DEFAULT);
608 		s.validateOutput(20, BeanContext.DEFAULT);
609 		s.validateOutput(10f, BeanContext.DEFAULT);
610 		s.validateOutput(20f, BeanContext.DEFAULT);
611 		s.validateOutput(null, BeanContext.DEFAULT);
612 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.validateOutput(11, BeanContext.DEFAULT));
613 	}
614 
615 	@Content
616 	@Schema(
617 		items=@Items(
618 			mo="10",
619 			items=@SubItems(
620 				mo="100",
621 				items={
622 					"multipleOf:1000,",
623 					"items:{multipleOf:10000}"
624 				}
625 			)
626 		)
627 	)
628 	public static class C05b {}
629 
630 	@Test void c05b_multipleOf_items() throws Exception {
631 		var s = HttpPartSchema.create().applyAll(Content.class, C05b.class).build();
632 
633 		s.getItems().validateOutput(0, BeanContext.DEFAULT);
634 		s.getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
635 		s.getItems().getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
636 		s.getItems().getItems().getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
637 
638 		s.getItems().validateOutput(10, BeanContext.DEFAULT);
639 		s.getItems().getItems().validateOutput(100, BeanContext.DEFAULT);
640 		s.getItems().getItems().getItems().validateOutput(1000, BeanContext.DEFAULT);
641 		s.getItems().getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT);
642 
643 		s.getItems().validateOutput(20, BeanContext.DEFAULT);
644 		s.getItems().getItems().validateOutput(200, BeanContext.DEFAULT);
645 		s.getItems().getItems().getItems().validateOutput(2000, BeanContext.DEFAULT);
646 		s.getItems().getItems().getItems().getItems().validateOutput(20000, BeanContext.DEFAULT);
647 
648 		s.getItems().validateOutput(10f, BeanContext.DEFAULT);
649 		s.getItems().getItems().validateOutput(100f, BeanContext.DEFAULT);
650 		s.getItems().getItems().getItems().validateOutput(1000f, BeanContext.DEFAULT);
651 		s.getItems().getItems().getItems().getItems().validateOutput(10000f, BeanContext.DEFAULT);
652 
653 		s.getItems().validateOutput(20f, BeanContext.DEFAULT);
654 		s.getItems().getItems().validateOutput(200f, BeanContext.DEFAULT);
655 		s.getItems().getItems().getItems().validateOutput(2000f, BeanContext.DEFAULT);
656 		s.getItems().getItems().getItems().getItems().validateOutput(20000f, BeanContext.DEFAULT);
657 
658 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().validateOutput(11, BeanContext.DEFAULT));
659 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().validateOutput(101, BeanContext.DEFAULT));
660 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().getItems().validateOutput(1001, BeanContext.DEFAULT));
661 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10001, BeanContext.DEFAULT));
662 	}
663 
664 	@Content
665 	@Schema(
666 		mo="10.1"
667 	)
668 	public static class C06a {}
669 
670 	@Test void c06a_multipleOf_floats() throws Exception {
671 		var s = HttpPartSchema.create().applyAll(Content.class, C06a.class).build();
672 		s.validateOutput(0, BeanContext.DEFAULT);
673 		s.validateOutput(10.1f, BeanContext.DEFAULT);
674 		s.validateOutput(20.2f, BeanContext.DEFAULT);
675 		s.validateOutput(null, BeanContext.DEFAULT);
676 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.validateOutput(10.2f, BeanContext.DEFAULT));
677 	}
678 
679 	@Content
680 	@Schema(
681 		items=@Items(
682 			mo="10.1",
683 			items=@SubItems(
684 				mo="100.1",
685 				items={
686 					"multipleOf:1000.1,",
687 					"items:{multipleOf:10000.1}"
688 				}
689 			)
690 		)
691 	)
692 	public static class C06b {}
693 
694 	@Test void c06b_multipleOf_floats_items() throws Exception {
695 		var s = HttpPartSchema.create().applyAll(Content.class, C06b.class).build();
696 
697 		s.getItems().validateOutput(0, BeanContext.DEFAULT);
698 		s.getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
699 		s.getItems().getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
700 		s.getItems().getItems().getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
701 
702 		s.getItems().validateOutput(10.1f, BeanContext.DEFAULT);
703 		s.getItems().getItems().validateOutput(100.1f, BeanContext.DEFAULT);
704 		s.getItems().getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT);
705 		s.getItems().getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT);
706 
707 		s.getItems().validateOutput(20.2f, BeanContext.DEFAULT);
708 		s.getItems().getItems().validateOutput(200.2f, BeanContext.DEFAULT);
709 		s.getItems().getItems().getItems().validateOutput(2000.2f, BeanContext.DEFAULT);
710 		s.getItems().getItems().getItems().getItems().validateOutput(20000.2f, BeanContext.DEFAULT);
711 
712 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().validateOutput(10.2f, BeanContext.DEFAULT));
713 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().validateOutput(100.2f, BeanContext.DEFAULT));
714 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().getItems().validateOutput(1000.2f, BeanContext.DEFAULT));
715 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10000.2f, BeanContext.DEFAULT));
716 	}
717 
718 	//-----------------------------------------------------------------------------------------------------------------
719 	// Collections/Array validations
720 	//-----------------------------------------------------------------------------------------------------------------
721 
722 	@Content
723 	@Schema(
724 		items=@Items(
725 			ui=true,
726 			items=@SubItems(
727 				ui=true,
728 				items={
729 					"uniqueItems:true,",
730 					"items:{uniqueItems:true}"
731 				}
732 			)
733 		)
734 	)
735 	public static class D01 {}
736 
737 	@Test void d01a_uniqueItems_arrays() throws Exception {
738 		var s = HttpPartSchema.create().applyAll(Content.class, D01.class).build();
739 
740 		var good = split("a,b");
741 		var bad = split("a,a");
742 
743 		s.getItems().validateOutput(good, BeanContext.DEFAULT);
744 		s.getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
745 		s.getItems().getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
746 		s.getItems().getItems().getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
747 		s.getItems().validateOutput(null, BeanContext.DEFAULT);
748 
749 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().validateOutput(bad, BeanContext.DEFAULT));
750 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
751 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
752 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
753 	}
754 
755 	@Test void d01b_uniqueItems_collections() throws Exception {
756 		var s = HttpPartSchema.create().applyAll(Content.class, D01.class).build();
757 
758 		var good = l("a","b");
759 		var bad = l("a","a");
760 
761 		s.getItems().validateOutput(good, BeanContext.DEFAULT);
762 		s.getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
763 		s.getItems().getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
764 		s.getItems().getItems().getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
765 		s.getItems().validateOutput(null, BeanContext.DEFAULT);
766 
767 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().validateOutput(bad, BeanContext.DEFAULT));
768 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
769 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
770 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
771 	}
772 
773 	@Content
774 	@Schema(
775 		items=@Items(
776 			mini=1, maxi=2,
777 			items=@SubItems(
778 				mini=2, maxi=3,
779 				items={
780 					"minItems:3,maxItems:4,",
781 					"items:{minItems:4,maxItems:5}"
782 				}
783 			)
784 		)
785 	)
786 	public static class D02 {}
787 
788 	@Test void d02a_minMaxItems_arrays() throws Exception {
789 		var s = HttpPartSchema.create().applyAll(Content.class, D02.class).build();
790 
791 		s.getItems().validateOutput(split("1"), BeanContext.DEFAULT);
792 		s.getItems().getItems().validateOutput(split("1,2"), BeanContext.DEFAULT);
793 		s.getItems().getItems().getItems().validateOutput(split("1,2,3"), BeanContext.DEFAULT);
794 		s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3,4"), BeanContext.DEFAULT);
795 
796 		s.getItems().validateOutput(split("1,2"), BeanContext.DEFAULT);
797 		s.getItems().getItems().validateOutput(split("1,2,3"), BeanContext.DEFAULT);
798 		s.getItems().getItems().getItems().validateOutput(split("1,2,3,4"), BeanContext.DEFAULT);
799 		s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3,4,5"), BeanContext.DEFAULT);
800 
801 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum number of items not met.", ()->s.getItems().validateOutput(new String[0], BeanContext.DEFAULT));
802 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum number of items not met.", ()->s.getItems().getItems().validateOutput(split("1"), BeanContext.DEFAULT));
803 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum number of items not met.", ()->s.getItems().getItems().getItems().validateOutput(split("1,2"), BeanContext.DEFAULT));
804 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum number of items not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3"), BeanContext.DEFAULT));
805 
806 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum number of items exceeded.", ()->s.getItems().validateOutput(split("1,2,3"), BeanContext.DEFAULT));
807 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum number of items exceeded.", ()->s.getItems().getItems().validateOutput(split("1,2,3,4"), BeanContext.DEFAULT));
808 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum number of items exceeded.", ()->s.getItems().getItems().getItems().validateOutput(split("1,2,3,4,5"), BeanContext.DEFAULT));
809 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum number of items exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3,4,5,6"), BeanContext.DEFAULT));
810 	}
811 
812 	//-----------------------------------------------------------------------------------------------------------------
813 	// JSON Schema Draft 2020-12 validation tests
814 	//-----------------------------------------------------------------------------------------------------------------
815 
816 	@Content
817 	@Schema(const_="CONSTANT_VALUE")
818 	public static class D01a {}
819 
820 	@Test void d01a_const_valid() throws Exception {
821 		var s = HttpPartSchema.create().applyAll(Content.class, D01a.class).build();
822 		s.validateInput("CONSTANT_VALUE");
823 		s.validateInput(null);  // null is allowed when not required
824 	}
825 
826 	@Test void d01a_const_invalid() throws Exception {
827 		var s = HttpPartSchema.create().applyAll(Content.class, D01a.class).build();
828 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match constant.  Must be: CONSTANT_VALUE", ()->s.validateInput("OTHER_VALUE"));
829 	}
830 
831 	@Content
832 	@Schema(const_="CONSTANT_VALUE", required=true)
833 	public static class D01b {}
834 
835 	@Test void d01b_const_required() throws Exception {
836 		var s = HttpPartSchema.create().applyAll(Content.class, D01b.class).build();
837 		s.validateInput("CONSTANT_VALUE");
838 		assertThrowsWithMessage(SchemaValidationException.class, "No value specified.", ()->s.validateInput(null));
839 	}
840 
841 	@Content
842 	@Schema(t="integer", exclusiveMaximumValue="100", exclusiveMinimumValue="0")
843 	public static class D02a {}
844 
845 	@Test void d02a_exclusiveNumericBounds() throws Exception {
846 		var s = HttpPartSchema.create().applyAll(Content.class, D02a.class).build();
847 		s.validateOutput(1, BeanContext.DEFAULT);
848 		s.validateOutput(50, BeanContext.DEFAULT);
849 		s.validateOutput(99, BeanContext.DEFAULT);
850 		s.validateOutput(null, BeanContext.DEFAULT);
851 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(0, BeanContext.DEFAULT));
852 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(100, BeanContext.DEFAULT));
853 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(-1, BeanContext.DEFAULT));
854 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(101, BeanContext.DEFAULT));
855 	}
856 
857 	@Content
858 	@Schema(t="number", exclusiveMaximumValue="10.5", exclusiveMinimumValue="0.5")
859 	public static class D02b {}
860 
861 	@Test void d02b_exclusiveNumericBounds_doubles() throws Exception {
862 		var s = HttpPartSchema.create().applyAll(Content.class, D02b.class).build();
863 		s.validateOutput(0.6, BeanContext.DEFAULT);
864 		s.validateOutput(5.0, BeanContext.DEFAULT);
865 		s.validateOutput(10.4, BeanContext.DEFAULT);
866 		s.validateOutput(null, BeanContext.DEFAULT);
867 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(0.5, BeanContext.DEFAULT));
868 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(10.5, BeanContext.DEFAULT));
869 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(0.4, BeanContext.DEFAULT));
870 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(10.6, BeanContext.DEFAULT));
871 	}
872 
873 	//-----------------------------------------------------------------------------------------------------------------
874 	// Backward compatibility: Old boolean exclusiveMaximum/exclusiveMinimum
875 	//-----------------------------------------------------------------------------------------------------------------
876 
877 	@Content
878 	@Schema(t="integer", exclusiveMaximum=true, exclusiveMinimum=true, maximum="100", minimum="0")
879 	public static class D03a {}
880 
881 	@Test void d03a_exclusiveBooleanBounds() throws Exception {
882 		var s = HttpPartSchema.create().applyAll(Content.class, D03a.class).build();
883 		s.validateOutput(1, BeanContext.DEFAULT);
884 		s.validateOutput(50, BeanContext.DEFAULT);
885 		s.validateOutput(99, BeanContext.DEFAULT);
886 		s.validateOutput(null, BeanContext.DEFAULT);
887 		// With boolean flags, 0 and 100 are excluded
888 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(0, BeanContext.DEFAULT));
889 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(100, BeanContext.DEFAULT));
890 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(-1, BeanContext.DEFAULT));
891 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(101, BeanContext.DEFAULT));
892 	}
893 
894 	@Content
895 	@Schema(t="integer", exclusiveMaximum=false, exclusiveMinimum=false, maximum="100", minimum="0")
896 	public static class D03b {}
897 
898 	@Test void d03b_inclusiveBounds() throws Exception {
899 		var s = HttpPartSchema.create().applyAll(Content.class, D03b.class).build();
900 		// With boolean flags set to false, 0 and 100 are included
901 		s.validateOutput(0, BeanContext.DEFAULT);
902 		s.validateOutput(1, BeanContext.DEFAULT);
903 		s.validateOutput(50, BeanContext.DEFAULT);
904 		s.validateOutput(99, BeanContext.DEFAULT);
905 		s.validateOutput(100, BeanContext.DEFAULT);
906 		s.validateOutput(null, BeanContext.DEFAULT);
907 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(-1, BeanContext.DEFAULT));
908 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(101, BeanContext.DEFAULT));
909 	}
910 
911 	@Content
912 	@Schema(t="integer", exclusiveMaximumValue="100", exclusiveMinimumValue="0", exclusiveMaximum=false, exclusiveMinimum=false)
913 	public static class D03c {}
914 
915 	@Test void d03c_newStyleTakesPrecedence() throws Exception {
916 		var s = HttpPartSchema.create().applyAll(Content.class, D03c.class).build();
917 		// New numeric style should take precedence over old boolean flags
918 		s.validateOutput(1, BeanContext.DEFAULT);
919 		s.validateOutput(50, BeanContext.DEFAULT);
920 		s.validateOutput(99, BeanContext.DEFAULT);
921 		s.validateOutput(null, BeanContext.DEFAULT);
922 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(0, BeanContext.DEFAULT));
923 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(100, BeanContext.DEFAULT));
924 	}
925 
926 	//-----------------------------------------------------------------------------------------------------------------
927 	// Deprecated property (no validation, just ensures it's settable)
928 	//-----------------------------------------------------------------------------------------------------------------
929 
930 	@Content
931 	@Schema(deprecatedProperty=true)
932 	public static class D04a {}
933 
934 	@Test void d04a_deprecated() throws Exception {
935 		var s = HttpPartSchema.create().applyAll(Content.class, D04a.class).build();
936 		// deprecated is just a flag, doesn't affect validation
937 		assertBean(s, "deprecated", "true");
938 	}
939 
940 	//-----------------------------------------------------------------------------------------------------------------
941 	// Examples (no validation, documentation only)
942 	//-----------------------------------------------------------------------------------------------------------------
943 
944 	@Content
945 	@Schema(examples={"example1", "example2", "example3"})
946 	public static class D05a {}
947 
948 	@Test void d05a_examples() throws Exception {
949 		var s = HttpPartSchema.create().applyAll(Content.class, D05a.class).build();
950 		// examples are documentation only, doesn't affect validation
951 		assertBean(s, "examples", "[example1,example2,example3]");
952 	}
953 }