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