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_Query_Test extends TestBase {
31  
32  	//-----------------------------------------------------------------------------------------------------------------
33  	// Basic test
34  	//-----------------------------------------------------------------------------------------------------------------
35  	@Test void a01_basic() {
36  		assertDoesNotThrow(()->HttpPartSchema.create().build());
37  	}
38  
39  	//-----------------------------------------------------------------------------------------------------------------
40  	// @Query
41  	//-----------------------------------------------------------------------------------------------------------------
42  
43  	@Query("x")
44  	public static class A01 {}
45  
46  	@Test void a01_value() {
47  		var s = HttpPartSchema.create().applyAll(Query.class, A01.class).build();
48  		assertEquals("x", s.getName());
49  	}
50  
51  	@Query("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(Query.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  				@Query("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(Query.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 				@Query("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(Query.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 	@Query("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(Query.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 	@Query @Schema(r=true)
279 	public static class B01a {}
280 
281 	@Test void b01a_required() throws Exception {
282 		var s = HttpPartSchema.create().applyAll(Query.class, B01a.class).build();
283 
284 		s.validateInput("x");
285 
286 		assertThrowsWithMessage(SchemaValidationException.class, "No value specified.", ()->s.validateInput(null));
287 		assertThrowsWithMessage(SchemaValidationException.class, "Empty value not allowed.", ()->s.validateInput(""));
288 	}
289 
290 	@Query @Schema(aev=true)
291 	public static class B01b {}
292 
293 	@Test void b01b_allowEmptyValue() throws Exception {
294 		var s = HttpPartSchema.create().applyAll(Query.class, B01b.class).build();
295 
296 		s.validateInput("");
297 		s.validateInput(null);
298 	}
299 
300 	@Query @Schema(r=true,aev=true)
301 	public static class B01c {}
302 
303 	@Test void b01b_required_allowEmptyValue() throws Exception {
304 		var s = HttpPartSchema.create().applyAll(Query.class, B01c.class).build();
305 
306 		s.validateInput("");
307 
308 		assertThrowsWithMessage(SchemaValidationException.class, "No value specified.", ()->s.validateInput(null));
309 	}
310 
311 	@Query @Schema(p="x.*")
312 	public static class B02a {}
313 
314 	@Test void b02a_pattern() throws Exception {
315 		var s = HttpPartSchema.create().applyAll(Query.class, B02a.class).build();
316 		s.validateInput("x");
317 		s.validateInput("xx");
318 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match expected pattern.  Must match pattern: x.*", ()->s.validateInput("y"));
319 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match expected pattern.  Must match pattern: x.*", ()->s.validateInput("yx"));
320 		assertThrowsWithMessage(SchemaValidationException.class, "Empty value not allowed.", ()->s.validateInput(""));
321 	}
322 
323 	@Query @Schema(minl=2, maxl=3)
324 	public static class B03a {}
325 
326 	@Test void b03a_length() throws Exception {
327 		var s = HttpPartSchema.create().applyAll(Query.class, B03a.class).build();
328 		s.validateInput("12");
329 		s.validateInput("123");
330 		s.validateInput(null);
331 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.validateInput("1"));
332 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.validateInput("1234"));
333 	}
334 
335 	@Query
336 	@Schema(
337 		items=@Items(
338 			minl=2, maxl=3,
339 			items=@SubItems(
340 				minl=3, maxl=4,
341 				items={
342 					"minLength:4,maxLength:5,",
343 					"items:{minLength:5,maxLength:6}"
344 				}
345 			)
346 		)
347 	)
348 	public static class B03b {}
349 
350 	@Test void b03b_length_items() throws Exception {
351 		var s = HttpPartSchema.create().applyAll(Query.class, B03b.class).build();
352 
353 		s.getItems().validateInput("12");
354 		s.getItems().getItems().validateInput("123");
355 		s.getItems().getItems().getItems().validateInput("1234");
356 		s.getItems().getItems().getItems().getItems().validateInput("12345");
357 
358 		s.getItems().validateInput("123");
359 		s.getItems().getItems().validateInput("1234");
360 		s.getItems().getItems().getItems().validateInput("12345");
361 		s.getItems().getItems().getItems().getItems().validateInput("123456");
362 
363 		s.getItems().validateInput(null);
364 		s.getItems().getItems().validateInput(null);
365 		s.getItems().getItems().getItems().validateInput(null);
366 		s.getItems().getItems().getItems().getItems().validateInput(null);
367 
368 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.getItems().validateInput("1"));
369 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.getItems().getItems().validateInput("12"));
370 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.getItems().getItems().getItems().validateInput("123"));
371 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum length of value not met.", ()->s.getItems().getItems().getItems().getItems().validateInput("1234"));
372 
373 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.getItems().validateInput("1234"));
374 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.getItems().getItems().validateInput("12345"));
375 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.getItems().getItems().getItems().validateInput("123456"));
376 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum length of value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateInput("1234567"));
377 	}
378 
379 	@Query @Schema(e="X,Y")
380 	public static class B04a {}
381 
382 	@Test void b04a_enum() throws Exception {
383 		var s = HttpPartSchema.create().applyAll(Query.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 	@Query @Schema(e=" X , Y ")
391 	public static class B04b {}
392 
393 	@Test void b04b_enum() throws Exception {
394 		var s = HttpPartSchema.create().applyAll(Query.class, B04b.class).build();
395 		s.validateInput("X");
396 		s.validateInput("Y");
397 		s.validateInput(null);
398 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  X, Y", ()->s.validateInput("Z"));
399 	}
400 
401 	@Query @Schema(e="X,Y")
402 	public static class B04c {}
403 
404 	@Test void b04c_enum_json() throws Exception {
405 		var s = HttpPartSchema.create().applyAll(Query.class, B04c.class).build();
406 		s.validateInput("X");
407 		s.validateInput("Y");
408 		s.validateInput(null);
409 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  X, Y", ()->s.validateInput("Z"));
410 	}
411 
412 	@Query
413 	@Schema(
414 		items=@Items(
415 			e="W",
416 			items=@SubItems(
417 				e="X",
418 				items={
419 					"enum:['Y'],",
420 					"items:{enum:['Z']}"
421 				}
422 			)
423 		)
424 	)
425 	public static class B04d {}
426 
427 	@Test void b04d_enum_items() throws Exception {
428 		var s = HttpPartSchema.create().applyAll(Query.class, B04d.class).build();
429 
430 		s.getItems().validateInput("W");
431 		s.getItems().getItems().validateInput("X");
432 		s.getItems().getItems().getItems().validateInput("Y");
433 		s.getItems().getItems().getItems().getItems().validateInput("Z");
434 
435 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  W", ()->s.getItems().validateInput("V"));
436 		assertThrowsWithMessage(SchemaValidationException.class, "Value does not match one of the expected values.  Must be one of the following:  X", ()->s.getItems().getItems().validateInput("V"));
437 		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"));
438 		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"));
439 	}
440 
441 	//-----------------------------------------------------------------------------------------------------------------
442 	// Numeric validations
443 	//-----------------------------------------------------------------------------------------------------------------
444 
445 	@Query @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(Query.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 	@Query
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(Query.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 	@Query @Schema(min="10", max="100", emin=true, emax=true)
497 	public static class C02a {}
498 
499 	@Test void c02a_minmax_exclusive() throws Exception {
500 		var s = HttpPartSchema.create().applyAll(Query.class, C02a.class).build();
501 		s.validateOutput(11, BeanContext.DEFAULT);
502 		s.validateOutput(99, BeanContext.DEFAULT);
503 		s.validateOutput(null, BeanContext.DEFAULT);
504 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(10, BeanContext.DEFAULT));
505 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(100, BeanContext.DEFAULT));
506 	}
507 
508 	@Query
509 	@Schema(
510 		items=@Items(
511 			min="10", max="100", emin=true, emax=true,
512 			items=@SubItems(
513 				min="100", max="1000", emin=true, emax=true,
514 				items={
515 					"minimum:1000,maximum:10000,exclusiveMinimum:true,exclusiveMaximum:true,",
516 					"items:{minimum:10000,maximum:100000,exclusiveMinimum:true,exclusiveMaximum:true}"
517 				}
518 			)
519 		)
520 	)
521 	public static class C02b {}
522 
523 	@Test void c02b_minmax_exclusive_items() throws Exception {
524 		var s = HttpPartSchema.create().applyAll(Query.class, C02b.class).build();
525 
526 		s.getItems().validateOutput(11, BeanContext.DEFAULT);
527 		s.getItems().getItems().validateOutput(101, BeanContext.DEFAULT);
528 		s.getItems().getItems().getItems().validateOutput(1001, BeanContext.DEFAULT);
529 		s.getItems().getItems().getItems().getItems().validateOutput(10001, BeanContext.DEFAULT);
530 
531 		s.getItems().validateOutput(99, BeanContext.DEFAULT);
532 		s.getItems().getItems().validateOutput(999, BeanContext.DEFAULT);
533 		s.getItems().getItems().getItems().validateOutput(9999, BeanContext.DEFAULT);
534 		s.getItems().getItems().getItems().getItems().validateOutput(99999, BeanContext.DEFAULT);
535 
536 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().validateOutput(10, BeanContext.DEFAULT));
537 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().validateOutput(100, BeanContext.DEFAULT));
538 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().validateOutput(1000, BeanContext.DEFAULT));
539 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT));
540 
541 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().validateOutput(100, BeanContext.DEFAULT));
542 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().validateOutput(1000, BeanContext.DEFAULT));
543 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT));
544 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(100000, BeanContext.DEFAULT));
545 	}
546 
547 	@Query @Schema(min="10.1", max="100.1")
548 	public static class C03a {}
549 
550 	@Test void c03_minmax_floats() throws Exception {
551 		var s = HttpPartSchema.create().applyAll(Query.class, C03a.class).build();
552 		s.validateOutput(10.1f, BeanContext.DEFAULT);
553 		s.validateOutput(100.1f, BeanContext.DEFAULT);
554 		s.validateOutput(null, BeanContext.DEFAULT);
555 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(10f, BeanContext.DEFAULT));
556 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(100.2f, BeanContext.DEFAULT));
557 	}
558 
559 	@Query
560 	@Schema(
561 		items=@Items(
562 			min="10.1", max="100.1",
563 			items=@SubItems(
564 				min="100.1", max="1000.1",
565 				items={
566 					"minimum:1000.1,maximum:10000.1,",
567 					"items:{minimum:10000.1,maximum:100000.1}"
568 				}
569 			)
570 		)
571 	)
572 	public static class C03b {}
573 
574 	@Test void c03b_minmax_floats_items() throws Exception {
575 		var s = HttpPartSchema.create().applyAll(Query.class, C03b.class).build();
576 
577 		s.getItems().validateOutput(10.1f, BeanContext.DEFAULT);
578 		s.getItems().getItems().validateOutput(100.1f, BeanContext.DEFAULT);
579 		s.getItems().getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT);
580 		s.getItems().getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT);
581 
582 		s.getItems().validateOutput(100.1f, BeanContext.DEFAULT);
583 		s.getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT);
584 		s.getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT);
585 		s.getItems().getItems().getItems().getItems().validateOutput(100000.1f, BeanContext.DEFAULT);
586 
587 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().validateOutput(10f, BeanContext.DEFAULT));
588 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().validateOutput(100f, BeanContext.DEFAULT));
589 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().validateOutput(1000f, BeanContext.DEFAULT));
590 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10000f, BeanContext.DEFAULT));
591 
592 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().validateOutput(100.2f, BeanContext.DEFAULT));
593 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().validateOutput(1000.2f, BeanContext.DEFAULT));
594 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().validateOutput(10000.2f, BeanContext.DEFAULT));
595 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(100000.2f, BeanContext.DEFAULT));
596 	}
597 
598 	@Query @Schema(min="10.1", max="100.1", emin=true, emax=true)
599 	public static class C04a {}
600 
601 	@Test void c04a_minmax_floats_exclusive() throws Exception {
602 		var s = HttpPartSchema.create().applyAll(Query.class, C04a.class).build();
603 		s.validateOutput(10.2f, BeanContext.DEFAULT);
604 		s.validateOutput(100f, BeanContext.DEFAULT);
605 		s.validateOutput(null, BeanContext.DEFAULT);
606 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.validateOutput(10.1f, BeanContext.DEFAULT));
607 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.validateOutput(100.1f, BeanContext.DEFAULT));
608 	}
609 
610 	@Query
611 	@Schema(
612 		items=@Items(
613 			min="10.1", max="100.1", emin=true, emax=true,
614 			items=@SubItems(
615 				min="100.1", max="1000.1", emin=true, emax=true,
616 				items={
617 					"minimum:1000.1,maximum:10000.1,exclusiveMinimum:true,exclusiveMaximum:true,",
618 					"items:{minimum:10000.1,maximum:100000.1,exclusiveMinimum:true,exclusiveMaximum:true}"
619 				}
620 			)
621 		)
622 	)
623 	public static class C04b {}
624 
625 	@Test void c04b_minmax_floats_exclusive_items() throws Exception {
626 		var s = HttpPartSchema.create().applyAll(Query.class, C04b.class).build();
627 
628 		s.getItems().validateOutput(10.2f, BeanContext.DEFAULT);
629 		s.getItems().getItems().validateOutput(100.2f, BeanContext.DEFAULT);
630 		s.getItems().getItems().getItems().validateOutput(1000.2f, BeanContext.DEFAULT);
631 		s.getItems().getItems().getItems().getItems().validateOutput(10000.2f, BeanContext.DEFAULT);
632 
633 		s.getItems().validateOutput(100f, BeanContext.DEFAULT);
634 		s.getItems().getItems().validateOutput(1000f, BeanContext.DEFAULT);
635 		s.getItems().getItems().getItems().validateOutput(10000f, BeanContext.DEFAULT);
636 		s.getItems().getItems().getItems().getItems().validateOutput(100000f, BeanContext.DEFAULT);
637 
638 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().validateOutput(10.1f, BeanContext.DEFAULT));
639 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().validateOutput(100.1f, BeanContext.DEFAULT));
640 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT));
641 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum value not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT));
642 
643 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().validateOutput(100.1f, BeanContext.DEFAULT));
644 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT));
645 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT));
646 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum value exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(100000.1f, BeanContext.DEFAULT));
647 	}
648 
649 	@Query @Schema(mo="10")
650 	public static class C05a {}
651 
652 	@Test void c05a_multipleOf() throws Exception {
653 		var s = HttpPartSchema.create().applyAll(Query.class, C05a.class).build();
654 		s.validateOutput(0, BeanContext.DEFAULT);
655 		s.validateOutput(10, BeanContext.DEFAULT);
656 		s.validateOutput(20, BeanContext.DEFAULT);
657 		s.validateOutput(10f, BeanContext.DEFAULT);
658 		s.validateOutput(20f, BeanContext.DEFAULT);
659 		s.validateOutput(null, BeanContext.DEFAULT);
660 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.validateOutput(11, BeanContext.DEFAULT));
661 	}
662 
663 	@Query
664 	@Schema(
665 		items=@Items(
666 			mo="10",
667 			items=@SubItems(
668 				mo="100",
669 				items={
670 					"multipleOf:1000,",
671 					"items:{multipleOf:10000}"
672 				}
673 			)
674 		)
675 	)
676 	public static class C05b {}
677 
678 	@Test void c05b_multipleOf_items() throws Exception {
679 		var s = HttpPartSchema.create().applyAll(Query.class, C05b.class).build();
680 
681 		s.getItems().validateOutput(0, BeanContext.DEFAULT);
682 		s.getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
683 		s.getItems().getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
684 		s.getItems().getItems().getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
685 
686 		s.getItems().validateOutput(10, BeanContext.DEFAULT);
687 		s.getItems().getItems().validateOutput(100, BeanContext.DEFAULT);
688 		s.getItems().getItems().getItems().validateOutput(1000, BeanContext.DEFAULT);
689 		s.getItems().getItems().getItems().getItems().validateOutput(10000, BeanContext.DEFAULT);
690 
691 		s.getItems().validateOutput(20, BeanContext.DEFAULT);
692 		s.getItems().getItems().validateOutput(200, BeanContext.DEFAULT);
693 		s.getItems().getItems().getItems().validateOutput(2000, BeanContext.DEFAULT);
694 		s.getItems().getItems().getItems().getItems().validateOutput(20000, BeanContext.DEFAULT);
695 
696 		s.getItems().validateOutput(10f, BeanContext.DEFAULT);
697 		s.getItems().getItems().validateOutput(100f, BeanContext.DEFAULT);
698 		s.getItems().getItems().getItems().validateOutput(1000f, BeanContext.DEFAULT);
699 		s.getItems().getItems().getItems().getItems().validateOutput(10000f, BeanContext.DEFAULT);
700 
701 		s.getItems().validateOutput(20f, BeanContext.DEFAULT);
702 		s.getItems().getItems().validateOutput(200f, BeanContext.DEFAULT);
703 		s.getItems().getItems().getItems().validateOutput(2000f, BeanContext.DEFAULT);
704 		s.getItems().getItems().getItems().getItems().validateOutput(20000f, BeanContext.DEFAULT);
705 
706 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().validateOutput(11, BeanContext.DEFAULT));
707 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().validateOutput(101, BeanContext.DEFAULT));
708 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().getItems().validateOutput(1001, BeanContext.DEFAULT));
709 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10001, BeanContext.DEFAULT));
710 	}
711 
712 	@Query @Schema(mo="10.1")
713 	public static class C06a {}
714 
715 	@Test void c06a_multipleOf_floats() throws Exception {
716 		var s = HttpPartSchema.create().applyAll(Query.class, C06a.class).build();
717 		s.validateOutput(0, BeanContext.DEFAULT);
718 		s.validateOutput(10.1f, BeanContext.DEFAULT);
719 		s.validateOutput(20.2f, BeanContext.DEFAULT);
720 		s.validateOutput(null, BeanContext.DEFAULT);
721 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.validateOutput(10.2f, BeanContext.DEFAULT));
722 	}
723 
724 	@Query
725 	@Schema(
726 		items=@Items(
727 			mo="10.1",
728 			items=@SubItems(
729 				mo="100.1",
730 				items={
731 					"multipleOf:1000.1,",
732 					"items:{multipleOf:10000.1}"
733 				}
734 			)
735 		)
736 	)
737 	public static class C06b {}
738 
739 	@Test void c06b_multipleOf_floats_items() throws Exception {
740 		var s = HttpPartSchema.create().applyAll(Query.class, C06b.class).build();
741 
742 		s.getItems().validateOutput(0, BeanContext.DEFAULT);
743 		s.getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
744 		s.getItems().getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
745 		s.getItems().getItems().getItems().getItems().validateOutput(0, BeanContext.DEFAULT);
746 
747 		s.getItems().validateOutput(10.1f, BeanContext.DEFAULT);
748 		s.getItems().getItems().validateOutput(100.1f, BeanContext.DEFAULT);
749 		s.getItems().getItems().getItems().validateOutput(1000.1f, BeanContext.DEFAULT);
750 		s.getItems().getItems().getItems().getItems().validateOutput(10000.1f, BeanContext.DEFAULT);
751 
752 		s.getItems().validateOutput(20.2f, BeanContext.DEFAULT);
753 		s.getItems().getItems().validateOutput(200.2f, BeanContext.DEFAULT);
754 		s.getItems().getItems().getItems().validateOutput(2000.2f, BeanContext.DEFAULT);
755 		s.getItems().getItems().getItems().getItems().validateOutput(20000.2f, BeanContext.DEFAULT);
756 
757 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().validateOutput(10.2f, BeanContext.DEFAULT));
758 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().validateOutput(100.2f, BeanContext.DEFAULT));
759 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().getItems().validateOutput(1000.2f, BeanContext.DEFAULT));
760 		assertThrowsWithMessage(SchemaValidationException.class, "Multiple-of not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(10000.2f, BeanContext.DEFAULT));
761 	}
762 
763 	//-----------------------------------------------------------------------------------------------------------------
764 	// Collections/Array validations
765 	//-----------------------------------------------------------------------------------------------------------------
766 
767 	@Query
768 	@Schema(
769 		items=@Items(
770 			ui=true,
771 			items=@SubItems(
772 				ui=true,
773 				items={
774 					"uniqueItems:true,",
775 					"items:{uniqueItems:true}"
776 				}
777 			)
778 		)
779 
780 	)
781 	public static class D01 {}
782 
783 	@Test void d01a_uniqueItems_arrays() throws Exception {
784 		var s = HttpPartSchema.create().applyAll(Query.class, D01.class).build();
785 
786 		var good = split("a,b");
787 		var bad = split("a,a");
788 
789 		s.getItems().validateOutput(good, BeanContext.DEFAULT);
790 		s.getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
791 		s.getItems().getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
792 		s.getItems().getItems().getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
793 		s.getItems().validateOutput(null, BeanContext.DEFAULT);
794 
795 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().validateOutput(bad, BeanContext.DEFAULT));
796 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
797 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
798 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
799 	}
800 
801 	@Test void d01b_uniqueItems_collections() throws Exception {
802 		var s = HttpPartSchema.create().applyAll(Query.class, D01.class).build();
803 
804 		List<String>
805 			good = alist("a","b"),
806 			bad = alist("a","a");
807 
808 		s.getItems().validateOutput(good, BeanContext.DEFAULT);
809 		s.getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
810 		s.getItems().getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
811 		s.getItems().getItems().getItems().getItems().validateOutput(good, BeanContext.DEFAULT);
812 		s.getItems().validateOutput(null, BeanContext.DEFAULT);
813 
814 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().validateOutput(bad, BeanContext.DEFAULT));
815 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
816 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
817 		assertThrowsWithMessage(SchemaValidationException.class, "Duplicate items not allowed.", ()->s.getItems().getItems().getItems().getItems().validateOutput(bad, BeanContext.DEFAULT));
818 	}
819 
820 	@Query
821 	@Schema(
822 		items=@Items(
823 			mini=1, maxi=2,
824 			items=@SubItems(
825 				mini=2, maxi=3,
826 				items={
827 					"minItems:3,maxItems:4,",
828 					"items:{minItems:4,maxItems:5}"
829 				}
830 			)
831 		)
832 
833 	)
834 	public static class D02 {}
835 
836 	@Test void d02a_minMaxItems_arrays() throws Exception {
837 		var s = HttpPartSchema.create().applyAll(Query.class, D02.class).build();
838 
839 		s.getItems().validateOutput(split("1"), BeanContext.DEFAULT);
840 		s.getItems().getItems().validateOutput(split("1,2"), BeanContext.DEFAULT);
841 		s.getItems().getItems().getItems().validateOutput(split("1,2,3"), BeanContext.DEFAULT);
842 		s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3,4"), BeanContext.DEFAULT);
843 
844 		s.getItems().validateOutput(split("1,2"), BeanContext.DEFAULT);
845 		s.getItems().getItems().validateOutput(split("1,2,3"), BeanContext.DEFAULT);
846 		s.getItems().getItems().getItems().validateOutput(split("1,2,3,4"), BeanContext.DEFAULT);
847 		s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3,4,5"), BeanContext.DEFAULT);
848 
849 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum number of items not met.", ()->s.getItems().validateOutput(new String[0], BeanContext.DEFAULT));
850 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum number of items not met.", ()->s.getItems().getItems().validateOutput(split("1"), BeanContext.DEFAULT));
851 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum number of items not met.", ()->s.getItems().getItems().getItems().validateOutput(split("1,2"), BeanContext.DEFAULT));
852 		assertThrowsWithMessage(SchemaValidationException.class, "Minimum number of items not met.", ()->s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3"), BeanContext.DEFAULT));
853 
854 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum number of items exceeded.", ()->s.getItems().validateOutput(split("1,2,3"), BeanContext.DEFAULT));
855 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum number of items exceeded.", ()->s.getItems().getItems().validateOutput(split("1,2,3,4"), BeanContext.DEFAULT));
856 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum number of items exceeded.", ()->s.getItems().getItems().getItems().validateOutput(split("1,2,3,4,5"), BeanContext.DEFAULT));
857 		assertThrowsWithMessage(SchemaValidationException.class, "Maximum number of items exceeded.", ()->s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3,4,5,6"), BeanContext.DEFAULT));
858 	}
859 }