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