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