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.junit.jupiter.api.Assertions.*;
21  
22  import org.apache.juneau.*;
23  import org.apache.juneau.annotation.*;
24  import org.apache.juneau.http.annotation.*;
25  import org.apache.juneau.reflect.*;
26  import org.junit.jupiter.api.*;
27  
28  class HttpPartSchema_Body_Test extends TestBase {
29  
30  	//-----------------------------------------------------------------------------------------------------------------
31  	// Basic test
32  	//-----------------------------------------------------------------------------------------------------------------
33  	@Test void a01_basic() {
34  		assertDoesNotThrow(()->HttpPartSchema.create().build());
35  	}
36  
37  	//-----------------------------------------------------------------------------------------------------------------
38  	// @Body
39  	//-----------------------------------------------------------------------------------------------------------------
40  
41  	@Content
42  	@Schema(
43  		d={"b1","b2"},
44  		$ref="c1",
45  		r=true
46  	)
47  	public static class A02 {}
48  
49  	@Test void a02_basic_onClass() {
50  		var s = HttpPartSchema.create().applyAll(Content.class, A02.class).noValidate().build();
51  		assertTrue(s.isRequired());
52  	}
53  
54  	public static class A03 {
55  		public void a(  // NOSONAR
56  				@Content
57  				@Schema(
58  					d={"b1","b2"},
59  					$ref="c1",
60  					r=true
61  				)
62  				String x
63  			) {
64  
65  		}
66  	}
67  
68  	@Test void a03_basic_onParameter() throws Exception {
69  		var mpi = MethodInfo.of(A03.class.getMethod("a", String.class)).getParam(0);
70  		var s = HttpPartSchema.create().applyAll(Content.class, mpi).noValidate().build();
71  		assertTrue(s.isRequired());
72  	}
73  
74  	public static class A04 {
75  		public void a(  // NOSONAR
76  				@Content
77  				@Schema(
78  					d={"b3","b3"},
79  					$ref="c3",
80  					r=true
81  				)
82  				A02 x
83  			) {
84  
85  		}
86  	}
87  
88  	@Test void a04_basic_onParameterAndClass() throws Exception {
89  		var mpi = MethodInfo.of(A04.class.getMethod("a", A02.class)).getParam(0);
90  		var s = HttpPartSchema.create().applyAll(Content.class, mpi).noValidate().build();
91  		assertTrue(s.isRequired());
92  	}
93  
94  	@Content
95  	@Schema(
96  		t="number",
97  		f="int32",
98  		max="1",
99  		min="2",
100 		mo="3",
101 		p="4",
102 		maxl=1,
103 		minl=2,
104 		maxi=3,
105 		mini=4,
106 		maxp=5,
107 		minp=6,
108 		emax=true,
109 		emin=true,
110 		ui=true,
111 		df={"c1","c2"},
112 		e="e1,e2",
113 		items=@Items(
114 			t="integer",
115 			f="int64",
116 			cf="ssv",
117 			max="5",
118 			min="6",
119 			mo="7",
120 			p="8",
121 			maxl=5,
122 			minl=6,
123 			maxi=7,
124 			mini=8,
125 			emax=false,
126 			emin=false,
127 			ui=false,
128 			df={"c3","c4"},
129 			e="e3,e4",
130 			items=@SubItems(
131 				t="string",
132 				f="float",
133 				cf="tsv",
134 				max="9",
135 				min="10",
136 				mo="11",
137 				p="12",
138 				maxl=9,
139 				minl=10,
140 				maxi=11,
141 				mini=12,
142 				emax=true,
143 				emin=true,
144 				ui=true,
145 				df={"c5","c6"},
146 				e="e5,e6",
147 				items={
148 					"type:'array',",
149 					"format:'double',",
150 					"collectionFormat:'pipes',",
151 					"maximum:'13',",
152 					"minimum:'14',",
153 					"multipleOf:'15',",
154 					"pattern:'16',",
155 					"maxLength:13,",
156 					"minLength:14,",
157 					"maxItems:15,",
158 					"minItems:16,",
159 					"exclusiveMaximum:false,",
160 					"exclusiveMinimum:false,",
161 					"uniqueItems:false,",
162 					"default:'c7\\nc8',",
163 					"enum:['e7','e8']",
164 				}
165 			)
166 		)
167 	)
168 	public static class A05 {}
169 
170 	@Test void a05_basic_nestedItems_onClass() {
171 		var s = HttpPartSchema.create().applyAll(Content.class, A05.class).noValidate().build();
172 
173 		assertBean(
174 			s,
175 			"type,format,maximum,minimum,multipleOf,pattern,maxLength,minLength,maxItems,minItems,maxProperties,minProperties,exclusiveMaximum,exclusiveMinimum,uniqueItems,enum,default",
176 			"NUMBER,INT32,1,2,3,4,1,2,3,4,5,6,true,true,true,[e1,e2],c1\nc2"
177 		);
178 
179 		var items = s.getItems();
180 		assertBean(
181 			items,
182 			"type,format,collectionFormat,maximum,minimum,multipleOf,pattern,maxLength,minLength,maxItems,minItems,exclusiveMaximum,exclusiveMinimum,uniqueItems,enum,default",
183 			"INTEGER,INT64,SSV,5,6,7,8,5,6,7,8,false,false,false,[e3,e4],c3\nc4"
184 		);
185 
186 		items = items.getItems();
187 		assertBean(
188 			items,
189 			"type,format,collectionFormat,maximum,minimum,multipleOf,pattern,maxLength,minLength,maxItems,minItems,exclusiveMaximum,exclusiveMinimum,uniqueItems,enum,default",
190 			"STRING,FLOAT,TSV,9,10,11,12,9,10,11,12,true,true,true,[e5,e6],c5\nc6"
191 		);
192 
193 		items = items.getItems();
194 		assertBean(
195 			items,
196 			"type,format,collectionFormat,maximum,minimum,multipleOf,pattern,maxLength,minLength,maxItems,minItems,exclusiveMaximum,exclusiveMinimum,uniqueItems,enum,default",
197 			"ARRAY,DOUBLE,PIPES,13,14,15,16,13,14,15,16,false,false,false,[e7,e8],c7\nc8"
198 		);
199 	}
200 
201 	//-----------------------------------------------------------------------------------------------------------------
202 	// String input validations.
203 	//-----------------------------------------------------------------------------------------------------------------
204 
205 	@Content @Schema(required=true)
206 	public static class B01a {}
207 
208 	@Test void b01a_required() throws Exception {
209 		var s = HttpPartSchema.create().applyAll(Content.class, B01a.class).build();
210 
211 		s.validateInput("x");
212 		assertThrowsWithMessage(SchemaValidationException.class, "No value specified.", ()->s.validateInput(null));
213 		assertThrowsWithMessage(SchemaValidationException.class, "Empty value not allowed.", ()->s.validateInput(""));
214 	}
215 
216 	@Content
217 	@Schema(p="x.*",aev=true)
218 	public static class B02a {}
219 
220 	@Test void b02a_pattern() throws Exception {
221 		var s = HttpPartSchema.create().applyAll(Content.class, B02a.class).build();
222 
223 		s.validateInput("x");
224 		s.validateInput("xx");
225 
226 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match expected pattern.  Must match pattern: x.*", ()->s.validateInput(""));
227 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match expected pattern.  Must match pattern: x.*", ()->s.validateInput("y"));
228 	}
229 
230 	@Content
231 	@Schema(
232 		items=@Items(
233 			p="w.*",
234 			items=@SubItems(
235 				p="x.*",
236 				items={
237 					"pattern:'y.*',",
238 					"items:{pattern:'z.*'}"
239 				}
240 			)
241 		)
242 	)
243 	public static class B02b {}
244 
245 	@Content
246 	@Schema(
247 		minl=2, maxl=3
248 	)
249 	public static class B03a {}
250 
251 	@Test void b03a_length() throws Exception {
252 		var s = HttpPartSchema.create().applyAll(Content.class, B03a.class).build();
253 		s.validateInput("12");
254 		s.validateInput("123");
255 		s.validateInput(null);
256 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.validateInput("1"));
257 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.validateInput("1234"));
258 	}
259 
260 	@Content
261 	@Schema(
262 		items=@Items(
263 			minl=2, maxl=3,
264 			items=@SubItems(
265 				minl=3, maxl=4,
266 				items={
267 					"minLength:4,maxLength:5,",
268 					"items:{minLength:5,maxLength:6}"
269 				}
270 			)
271 		)
272 	)
273 	public static class B03b {}
274 
275 	@Test void b03b_length_items() throws Exception {
276 		var s = HttpPartSchema.create().applyAll(Content.class, B03b.class).build();
277 
278 		s.getItems().validateInput("12");
279 		s.getItems().getItems().validateInput("123");
280 		s.getItems().getItems().getItems().validateInput("1234");
281 		s.getItems().getItems().getItems().getItems().validateInput("12345");
282 
283 		s.getItems().validateInput("123");
284 		s.getItems().getItems().validateInput("1234");
285 		s.getItems().getItems().getItems().validateInput("12345");
286 		s.getItems().getItems().getItems().getItems().validateInput("123456");
287 
288 		s.getItems().validateInput(null);
289 		s.getItems().getItems().validateInput(null);
290 		s.getItems().getItems().getItems().validateInput(null);
291 		s.getItems().getItems().getItems().getItems().validateInput(null);
292 
293 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.getItems().validateInput("1"));
294 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.getItems().getItems().validateInput("12"));
295 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.getItems().getItems().getItems().validateInput("123"));
296 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.getItems().getItems().getItems().getItems().validateInput("1234"));
297 
298 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.getItems().validateInput("1234"));
299 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.getItems().getItems().validateInput("12345"));
300 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.getItems().getItems().getItems().validateInput("123456"));
301 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateInput("1234567"));
302 	}
303 
304 	@Content
305 	@Schema(
306 		e="X,Y"
307 	)
308 	public static class B04a {}
309 
310 	@Test void b04a_enum() throws Exception {
311 		var s = HttpPartSchema.create().applyAll(Content.class, B04a.class).build();
312 		s.validateInput("X");
313 		s.validateInput("Y");
314 		s.validateInput(null);
315 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  X, Y", ()->s.validateInput("Z"));
316 	}
317 
318 	@Content
319 	@Schema(
320 		e=" X , Y "
321 	)
322 	public static class B04b {}
323 
324 	@Test void b04b_enum() throws Exception {
325 		var s = HttpPartSchema.create().applyAll(Content.class, B04b.class).build();
326 		s.validateInput("X");
327 		s.validateInput("Y");
328 		s.validateInput(null);
329 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  X, Y", ()->s.validateInput("Z"));
330 	}
331 
332 	@Content
333 	@Schema(
334 		e="X,Y"
335 	)
336 	public static class B04c {}
337 
338 	@Test void b04c_enum_json() throws Exception {
339 		var s = HttpPartSchema.create().applyAll(Content.class, B04c.class).build();
340 		s.validateInput("X");
341 		s.validateInput("Y");
342 		s.validateInput(null);
343 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  X, Y", ()->s.validateInput("Z"));
344 	}
345 
346 	@Content
347 	@Schema(
348 		items=@Items(
349 			e="W",
350 			items=@SubItems(
351 				e="X",
352 				items={
353 					"enum:['Y'],",
354 					"items:{enum:['Z']}"
355 				}
356 			)
357 		)
358 	)
359 	public static class B04d {}
360 
361 	@Test void b04d_enum_items() throws Exception {
362 		var s = HttpPartSchema.create().applyAll(Content.class, B04d.class).build();
363 
364 		s.getItems().validateInput("W");
365 		s.getItems().getItems().validateInput("X");
366 		s.getItems().getItems().getItems().validateInput("Y");
367 		s.getItems().getItems().getItems().getItems().validateInput("Z");
368 
369 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  W", ()->s.getItems().validateInput("V"));
370 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  X", ()->s.getItems().getItems().validateInput("V"));
371 		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"));
372 		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"));
373 	}
374 
375 	//-----------------------------------------------------------------------------------------------------------------
376 	// Numeric validations
377 	//-----------------------------------------------------------------------------------------------------------------
378 
379 	@Content
380 	@Schema(
381 		min="10", max="100"
382 	)
383 	public static class C01a {}
384 
385 	@Test void c01a_minmax_ints() throws Exception {
386 		var s = HttpPartSchema.create().applyAll(Content.class, C01a.class).build();
387 		s.validateOutput(10, BeanContext.DEFAULT);
388 		s.validateOutput(100, BeanContext.DEFAULT);
389 		s.validateOutput(null, BeanContext.DEFAULT);
390 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(9, BeanContext.DEFAULT));
391 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(101, BeanContext.DEFAULT));
392 	}
393 
394 	@Content
395 	@Schema(
396 		items=@Items(
397 			min="10", max="100",
398 			items=@SubItems(
399 				min="100", max="1000",
400 				items={
401 					"minimum:1000,maximum:10000,",
402 					"items:{minimum:10000,maximum:100000}"
403 				}
404 			)
405 		)
406 	)
407 	public static class C01b {}
408 
409 	@Test void c01b_minmax_ints_items() throws Exception {
410 		var s = HttpPartSchema.create().applyAll(Content.class, C01b.class).build();
411 
412 		s.getItems().validateOutput(10, BeanContext.DEFAULT);
413 		s.getItems().getItems().validateOutput(100, BeanContext.DEFAULT);
414 		s.getItems().getItems().getItems().validateOutput(1000, BeanContext.DEFAULT);
415 		s.getItems().getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT);
416 
417 		s.getItems().validateOutput(100, BeanContext.DEFAULT);
418 		s.getItems().getItems().validateOutput(1000, BeanContext.DEFAULT);
419 		s.getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT);
420 		s.getItems().getItems().getItems().getItems().validateOutput(100000, BeanContext.DEFAULT);
421 
422 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().validateOutput(9, BeanContext.DEFAULT));
423 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().validateOutput(99, BeanContext.DEFAULT));
424 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().validateOutput(999, BeanContext.DEFAULT));
425 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(9999, BeanContext.DEFAULT));
426 
427 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().validateOutput(101, BeanContext.DEFAULT));
428 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().validateOutput(1001, BeanContext.DEFAULT));
429 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().validateOutput(10001, BeanContext.DEFAULT));
430 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(100001, BeanContext.DEFAULT));
431 	}
432 
433 	@Content
434 	@Schema(
435 		min="10", max="100", emin=true, emax=true
436 	)
437 	public static class C02a {}
438 
439 	@Test void c02a_minmax_exclusive() throws Exception {
440 		var s = HttpPartSchema.create().applyAll(Content.class, C02a.class).build();
441 		s.validateOutput(11, BeanContext.DEFAULT);
442 		s.validateOutput(99, BeanContext.DEFAULT);
443 		s.validateOutput(null, BeanContext.DEFAULT);
444 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(10, BeanContext.DEFAULT));
445 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(100, BeanContext.DEFAULT));
446 	}
447 
448 	@Content
449 	@Schema(
450 		items=@Items(
451 			min="10", max="100", emin=true, emax=true,
452 			items=@SubItems(
453 				min="100", max="1000", emin=true, emax=true,
454 				items={
455 					"minimum:1000,maximum:10000,exclusiveMinimum:true,exclusiveMaximum:true,",
456 					"items:{minimum:10000,maximum:100000,exclusiveMinimum:true,exclusiveMaximum:true}"
457 				}
458 			)
459 		)
460 	)
461 	public static class C02b {}
462 
463 	@Test void c02b_minmax_exclusive_items() throws Exception {
464 		var s = HttpPartSchema.create().applyAll(Content.class, C02b.class).build();
465 
466 		s.getItems().validateOutput(11, BeanContext.DEFAULT);
467 		s.getItems().getItems().validateOutput(101, BeanContext.DEFAULT);
468 		s.getItems().getItems().getItems().validateOutput(1001, BeanContext.DEFAULT);
469 		s.getItems().getItems().getItems().getItems().validateOutput(10001, BeanContext.DEFAULT);
470 
471 		s.getItems().validateOutput(99, BeanContext.DEFAULT);
472 		s.getItems().getItems().validateOutput(999, BeanContext.DEFAULT);
473 		s.getItems().getItems().getItems().validateOutput(9999, BeanContext.DEFAULT);
474 		s.getItems().getItems().getItems().getItems().validateOutput(99999, BeanContext.DEFAULT);
475 
476 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().validateOutput(10, BeanContext.DEFAULT));
477 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().validateOutput(100, BeanContext.DEFAULT));
478 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().validateOutput(1000, BeanContext.DEFAULT));
479 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT));
480 
481 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().validateOutput(100, BeanContext.DEFAULT));
482 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().validateOutput(1000, BeanContext.DEFAULT));
483 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT));
484 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(100000, BeanContext.DEFAULT));
485 	}
486 
487 	@Content
488 	@Schema(
489 		min="10.1", max="100.1"
490 	)
491 	public static class C03a {}
492 
493 	@Test void c03_minmax_floats() throws Exception {
494 		var s = HttpPartSchema.create().applyAll(Content.class, C03a.class).build();
495 		s.validateOutput(10.1f, BeanContext.DEFAULT);
496 		s.validateOutput(100.1f, BeanContext.DEFAULT);
497 		s.validateOutput(null, BeanContext.DEFAULT);
498 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(10f, BeanContext.DEFAULT));
499 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(100.2f, BeanContext.DEFAULT));
500 	}
501 
502 	@Content
503 	@Schema(
504 		items=@Items(
505 			min="10.1", max="100.1",
506 			items=@SubItems(
507 				min="100.1", max="1000.1",
508 				items={
509 					"minimum:1000.1,maximum:10000.1,",
510 					"items:{minimum:10000.1,maximum:100000.1}"
511 				}
512 			)
513 		)
514 	)
515 	public static class C03b {}
516 
517 	@Test void c03b_minmax_floats_items() throws Exception {
518 		var s = HttpPartSchema.create().applyAll(Content.class, C03b.class).build();
519 
520 		s.getItems().validateOutput(10.1f, BeanContext.DEFAULT);
521 		s.getItems().getItems().validateOutput(100.1f, BeanContext.DEFAULT);
522 		s.getItems().getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT);
523 		s.getItems().getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT);
524 
525 		s.getItems().validateOutput(100.1f, BeanContext.DEFAULT);
526 		s.getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT);
527 		s.getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT);
528 		s.getItems().getItems().getItems().getItems().validateOutput(100000.1f, BeanContext.DEFAULT);
529 
530 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().validateOutput(10f, BeanContext.DEFAULT));
531 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().validateOutput(100f, BeanContext.DEFAULT));
532 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().validateOutput(1000f, BeanContext.DEFAULT));
533 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10000f, BeanContext.DEFAULT));
534 
535 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().validateOutput(100.2f, BeanContext.DEFAULT));
536 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().validateOutput(1000.2f, BeanContext.DEFAULT));
537 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().validateOutput(10000.2f, BeanContext.DEFAULT));
538 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(100000.2f, BeanContext.DEFAULT));
539 	}
540 
541 	@Content
542 	@Schema(
543 		min="10.1", max="100.1", emin=true, emax=true
544 	)
545 	public static class C04a {}
546 
547 	@Test void c04a_minmax_floats_exclusive() throws Exception {
548 		var s = HttpPartSchema.create().applyAll(Content.class, C04a.class).build();
549 		s.validateOutput(10.2f, BeanContext.DEFAULT);
550 		s.validateOutput(100f, BeanContext.DEFAULT);
551 		s.validateOutput(null, BeanContext.DEFAULT);
552 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(10.1f, BeanContext.DEFAULT));
553 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(100.1f, BeanContext.DEFAULT));
554 	}
555 
556 	@Content
557 	@Schema(
558 		items=@Items(
559 			min="10.1", max="100.1", emin=true, emax=true,
560 			items=@SubItems(
561 				min="100.1", max="1000.1", emin=true, emax=true,
562 				items={
563 					"minimum:1000.1,maximum:10000.1,exclusiveMinimum:true,exclusiveMaximum:true,",
564 					"items:{minimum:10000.1,maximum:100000.1,exclusiveMinimum:true,exclusiveMaximum:true}"
565 				}
566 			)
567 		)
568 	)
569 	public static class C04b {}
570 
571 	@Test void c04b_minmax_floats_exclusive_items() throws Exception {
572 		var s = HttpPartSchema.create().applyAll(Content.class, C04b.class).build();
573 
574 		s.getItems().validateOutput(10.2f, BeanContext.DEFAULT);
575 		s.getItems().getItems().validateOutput(100.2f, BeanContext.DEFAULT);
576 		s.getItems().getItems().getItems().validateOutput(1000.2f, BeanContext.DEFAULT);
577 		s.getItems().getItems().getItems().getItems().validateOutput(10000.2f, BeanContext.DEFAULT);
578 
579 		s.getItems().validateOutput(100f, BeanContext.DEFAULT);
580 		s.getItems().getItems().validateOutput(1000f, BeanContext.DEFAULT);
581 		s.getItems().getItems().getItems().validateOutput(10000f, BeanContext.DEFAULT);
582 		s.getItems().getItems().getItems().getItems().validateOutput(100000f, BeanContext.DEFAULT);
583 
584 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().validateOutput(10.1f, BeanContext.DEFAULT));
585 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().validateOutput(100.1f, BeanContext.DEFAULT));
586 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT));
587 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT));
588 
589 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().validateOutput(100.1f, BeanContext.DEFAULT));
590 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT));
591 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT));
592 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(100000.1f, BeanContext.DEFAULT));
593 	}
594 
595 	@Content
596 	@Schema(
597 		mo="10"
598 	)
599 	public static class C05a {}
600 
601 	@Test void c05a_multipleOf() throws Exception {
602 		var s = HttpPartSchema.create().applyAll(Content.class, C05a.class).build();
603 		s.validateOutput(0, BeanContext.DEFAULT);
604 		s.validateOutput(10, BeanContext.DEFAULT);
605 		s.validateOutput(20, BeanContext.DEFAULT);
606 		s.validateOutput(10f, BeanContext.DEFAULT);
607 		s.validateOutput(20f, BeanContext.DEFAULT);
608 		s.validateOutput(null, BeanContext.DEFAULT);
609 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.validateOutput(11, BeanContext.DEFAULT));
610 	}
611 
612 	@Content
613 	@Schema(
614 		items=@Items(
615 			mo="10",
616 			items=@SubItems(
617 				mo="100",
618 				items={
619 					"multipleOf:1000,",
620 					"items:{multipleOf:10000}"
621 				}
622 			)
623 		)
624 	)
625 	public static class C05b {}
626 
627 	@Test void c05b_multipleOf_items() throws Exception {
628 		var s = HttpPartSchema.create().applyAll(Content.class, C05b.class).build();
629 
630 		s.getItems().validateOutput(0, BeanContext.DEFAULT);
631 		s.getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
632 		s.getItems().getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
633 		s.getItems().getItems().getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
634 
635 		s.getItems().validateOutput(10, BeanContext.DEFAULT);
636 		s.getItems().getItems().validateOutput(100, BeanContext.DEFAULT);
637 		s.getItems().getItems().getItems().validateOutput(1000, BeanContext.DEFAULT);
638 		s.getItems().getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT);
639 
640 		s.getItems().validateOutput(20, BeanContext.DEFAULT);
641 		s.getItems().getItems().validateOutput(200, BeanContext.DEFAULT);
642 		s.getItems().getItems().getItems().validateOutput(2000, BeanContext.DEFAULT);
643 		s.getItems().getItems().getItems().getItems().validateOutput(20000, BeanContext.DEFAULT);
644 
645 		s.getItems().validateOutput(10f, BeanContext.DEFAULT);
646 		s.getItems().getItems().validateOutput(100f, BeanContext.DEFAULT);
647 		s.getItems().getItems().getItems().validateOutput(1000f, BeanContext.DEFAULT);
648 		s.getItems().getItems().getItems().getItems().validateOutput(10000f, BeanContext.DEFAULT);
649 
650 		s.getItems().validateOutput(20f, BeanContext.DEFAULT);
651 		s.getItems().getItems().validateOutput(200f, BeanContext.DEFAULT);
652 		s.getItems().getItems().getItems().validateOutput(2000f, BeanContext.DEFAULT);
653 		s.getItems().getItems().getItems().getItems().validateOutput(20000f, BeanContext.DEFAULT);
654 
655 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().validateOutput(11, BeanContext.DEFAULT));
656 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().validateOutput(101, BeanContext.DEFAULT));
657 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().getItems().validateOutput(1001, BeanContext.DEFAULT));
658 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10001, BeanContext.DEFAULT));
659 	}
660 
661 	@Content
662 	@Schema(
663 		mo="10.1"
664 	)
665 	public static class C06a {}
666 
667 	@Test void c06a_multipleOf_floats() throws Exception {
668 		var s = HttpPartSchema.create().applyAll(Content.class, C06a.class).build();
669 		s.validateOutput(0, BeanContext.DEFAULT);
670 		s.validateOutput(10.1f, BeanContext.DEFAULT);
671 		s.validateOutput(20.2f, BeanContext.DEFAULT);
672 		s.validateOutput(null, BeanContext.DEFAULT);
673 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.validateOutput(10.2f, BeanContext.DEFAULT));
674 	}
675 
676 	@Content
677 	@Schema(
678 		items=@Items(
679 			mo="10.1",
680 			items=@SubItems(
681 				mo="100.1",
682 				items={
683 					"multipleOf:1000.1,",
684 					"items:{multipleOf:10000.1}"
685 				}
686 			)
687 		)
688 	)
689 	public static class C06b {}
690 
691 	@Test void c06b_multipleOf_floats_items() throws Exception {
692 		var s = HttpPartSchema.create().applyAll(Content.class, C06b.class).build();
693 
694 		s.getItems().validateOutput(0, BeanContext.DEFAULT);
695 		s.getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
696 		s.getItems().getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
697 		s.getItems().getItems().getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
698 
699 		s.getItems().validateOutput(10.1f, BeanContext.DEFAULT);
700 		s.getItems().getItems().validateOutput(100.1f, BeanContext.DEFAULT);
701 		s.getItems().getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT);
702 		s.getItems().getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT);
703 
704 		s.getItems().validateOutput(20.2f, BeanContext.DEFAULT);
705 		s.getItems().getItems().validateOutput(200.2f, BeanContext.DEFAULT);
706 		s.getItems().getItems().getItems().validateOutput(2000.2f, BeanContext.DEFAULT);
707 		s.getItems().getItems().getItems().getItems().validateOutput(20000.2f, BeanContext.DEFAULT);
708 
709 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().validateOutput(10.2f, BeanContext.DEFAULT));
710 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().validateOutput(100.2f, BeanContext.DEFAULT));
711 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().getItems().validateOutput(1000.2f, BeanContext.DEFAULT));
712 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10000.2f, BeanContext.DEFAULT));
713 	}
714 
715 	//-----------------------------------------------------------------------------------------------------------------
716 	// Collections/Array validations
717 	//-----------------------------------------------------------------------------------------------------------------
718 
719 	@Content
720 	@Schema(
721 		items=@Items(
722 			ui=true,
723 			items=@SubItems(
724 				ui=true,
725 				items={
726 					"uniqueItems:true,",
727 					"items:{uniqueItems:true}"
728 				}
729 			)
730 		)
731 	)
732 	public static class D01 {}
733 
734 	@Test void d01a_uniqueItems_arrays() throws Exception {
735 		var s = HttpPartSchema.create().applyAll(Content.class, D01.class).build();
736 
737 		var good = split("a,b");
738 		var bad = split("a,a");
739 
740 		s.getItems().validateOutput(good, BeanContext.DEFAULT);
741 		s.getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
742 		s.getItems().getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
743 		s.getItems().getItems().getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
744 		s.getItems().validateOutput(null, BeanContext.DEFAULT);
745 
746 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().validateOutput(bad, BeanContext.DEFAULT));
747 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
748 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
749 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
750 	}
751 
752 	@Test void d01b_uniqueItems_collections() throws Exception {
753 		var s = HttpPartSchema.create().applyAll(Content.class, D01.class).build();
754 
755 		var good = alist("a","b");
756 		var bad = alist("a","a");
757 
758 		s.getItems().validateOutput(good, BeanContext.DEFAULT);
759 		s.getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
760 		s.getItems().getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
761 		s.getItems().getItems().getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
762 		s.getItems().validateOutput(null, BeanContext.DEFAULT);
763 
764 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().validateOutput(bad, BeanContext.DEFAULT));
765 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
766 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
767 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
768 	}
769 
770 	@Content
771 	@Schema(
772 		items=@Items(
773 			mini=1, maxi=2,
774 			items=@SubItems(
775 				mini=2, maxi=3,
776 				items={
777 					"minItems:3,maxItems:4,",
778 					"items:{minItems:4,maxItems:5}"
779 				}
780 			)
781 		)
782 	)
783 	public static class D02 {}
784 
785 	@Test void d02a_minMaxItems_arrays() throws Exception {
786 		var s = HttpPartSchema.create().applyAll(Content.class, D02.class).build();
787 
788 		s.getItems().validateOutput(split("1"), BeanContext.DEFAULT);
789 		s.getItems().getItems().validateOutput(split("1,2"), BeanContext.DEFAULT);
790 		s.getItems().getItems().getItems().validateOutput(split("1,2,3"), BeanContext.DEFAULT);
791 		s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3,4"), BeanContext.DEFAULT);
792 
793 		s.getItems().validateOutput(split("1,2"), BeanContext.DEFAULT);
794 		s.getItems().getItems().validateOutput(split("1,2,3"), BeanContext.DEFAULT);
795 		s.getItems().getItems().getItems().validateOutput(split("1,2,3,4"), BeanContext.DEFAULT);
796 		s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3,4,5"), BeanContext.DEFAULT);
797 
798 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum number of items not met.", ()->s.getItems().validateOutput(new String[0], BeanContext.DEFAULT));
799 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum number of items not met.", ()->s.getItems().getItems().validateOutput(split("1"), BeanContext.DEFAULT));
800 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum number of items not met.", ()->s.getItems().getItems().getItems().validateOutput(split("1,2"), BeanContext.DEFAULT));
801 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum number of items not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3"), BeanContext.DEFAULT));
802 
803 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum number of items exceeded.", ()->s.getItems().validateOutput(split("1,2,3"), BeanContext.DEFAULT));
804 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum number of items exceeded.", ()->s.getItems().getItems().validateOutput(split("1,2,3,4"), BeanContext.DEFAULT));
805 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum number of items exceeded.", ()->s.getItems().getItems().getItems().validateOutput(split("1,2,3,4,5"), BeanContext.DEFAULT));
806 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum number of items exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3,4,5,6"), BeanContext.DEFAULT));
807 	}
808 }