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