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