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