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