001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.marshall.httppart; 018 019import static java.util.Collections.*; 020import static org.apache.juneau.commons.httppart.HttpPartDataType.*; 021import static org.apache.juneau.commons.httppart.HttpPartFormat.*; 022import static org.apache.juneau.commons.reflect.ReflectionUtils.*; 023import static org.apache.juneau.commons.utils.ClassUtils.*; 024import static org.apache.juneau.commons.utils.CollectionUtils.*; 025import static org.apache.juneau.commons.utils.CollectionUtils.list; 026import static org.apache.juneau.commons.utils.ObjectUtils.*; 027import static org.apache.juneau.commons.utils.Shorts.*; 028import static org.apache.juneau.commons.utils.Shorts.neq; 029import static org.apache.juneau.commons.utils.Shorts.eq; 030import static org.apache.juneau.commons.utils.StringUtils.*; 031import static org.apache.juneau.commons.utils.StringUtils.isEmpty; 032import static org.apache.juneau.commons.utils.ThrowableUtils.*; 033import static org.apache.juneau.marshall.Constants.*; 034 035import java.lang.annotation.*; 036import java.lang.reflect.*; 037import java.math.*; 038import java.util.*; 039import java.util.concurrent.atomic.*; 040import java.util.function.*; 041import java.util.regex.*; 042 043import org.apache.juneau.commons.*; 044import org.apache.juneau.commons.BasicRuntimeException; 045import org.apache.juneau.commons.collections.*; 046import org.apache.juneau.commons.httppart.*; 047import org.apache.juneau.commons.lang.*; 048import org.apache.juneau.commons.reflect.*; 049import org.apache.juneau.commons.utils.*; 050import org.apache.juneau.marshall.*; 051import org.apache.juneau.marshall.collections.*; 052import org.apache.juneau.marshall.parser.*; 053 054/** 055 * Represents an OpenAPI schema definition. 056 * 057 * <p> 058 * The schema definition can be applied to any HTTP parts such as bodies, headers, query/form parameters, and URL path parts. 059 * <br>The API is generic enough to apply to any path part although some attributes may only applicable for certain parts. 060 * 061 * <p> 062 * Schema objects are created via builders instantiated through the {@link #create()} method. 063 * 064 * <h5 class='section'>Jakarta Bean Validation Support:</h5> 065 * <p> 066 * As of 9.2.0, this class supports Jakarta Bean Validation constraint annotations (e.g., <c>@NotNull</c>, <c>@Size</c>, <c>@Min</c>, <c>@Max</c>). 067 * When these annotations are encountered during schema building, they are automatically mapped to corresponding OpenAPI schema properties: 068 * <ul> 069 * <li><c>@NotNull</c> → <c>required(true)</c> 070 * <li><c>@Size(min=x, max=y)</c> → <c>minLength/maxLength</c> and <c>minItems/maxItems</c> 071 * <li><c>@Min(value)</c> → <c>minimum(value)</c> 072 * <li><c>@Max(value)</c> → <c>maximum(value)</c> 073 * <li><c>@Pattern(regexp)</c> → <c>pattern(regexp)</c> 074 * <li><c>@Email</c> → <c>format("email")</c> 075 * <li><c>@Positive/@PositiveOrZero/@Negative/@NegativeOrZero</c> → Corresponding min/max constraints 076 * <li><c>@NotEmpty</c> → <c>required(true) + minLength(1)/minItems(1)</c> 077 * <li><c>@NotBlank</c> → <c>required(true) + minLength(1) + pattern</c> 078 * <li><c>@DecimalMin/@DecimalMax</c> → <c>minimum/maximum</c> with optional <c>exclusiveMinimum/exclusiveMaximum</c> 079 * </ul> 080 * <p> 081 * This integration uses pure reflection and does not require <c>jakarta.validation-api</c> as a dependency. 082 * The annotations are detected and processed automatically when present. 083 * 084 * <h5 class='section'>Notes:</h5><ul> 085 * <li class='note'>This class is thread safe and reusable. 086 * </ul> 087 * 088 * <h5 class='section'>See Also:</h5><ul> 089 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/OpenApiSupport">OpenApi</a> 090 * </ul> 091 */ 092@SuppressWarnings({ 093 "java:S100", // Builder methods const_, default_ and enum_ use a trailing underscore to avoid Java keyword clashes with the OpenAPI keywords 094 "java:S115", // Constants use UPPER_camelCase convention (e.g., ANN_inclusive) 095 "java:S116", // Field names use trailing underscores (default_, enum_, const_) to avoid Java keyword conflicts 096 "java:S1168", // Builder helpers (toNumber, toSet, joinnlOrNull, getAnnotationString) return null as the absent-value sentinel that callers test for 097 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 098 "java:S1452", // Wildcard required for parsed type metadata 099 "java:S3776", // The HttpPartSchema(Builder) constructor and annotation-applying code branch over every OpenAPI type/format/constraint combination 100 "java:S5843", // Complex regex patterns needed for RFC compliance (IPv6, ISO 8601 duration, date-time formats) 101 "java:S6539", // Monster class; HttpPartSchema is intentionally a single cohesive OpenAPI/HTTP-part schema model+validator 102 "java:S6541", // The HttpPartSchema(Builder) constructor runs the full per-type validation switch in one place 103 "unused" // Method kept for future use or API completeness 104}) 105public class HttpPartSchema { 106 107 private static final AnnotationProvider AP = AnnotationProvider.INSTANCE; 108 109 /** 110 * Builder class. 111 */ 112 public static class Builder { 113 String name; 114 String default_; 115 Set<Integer> codes; 116 Set<String> enum_; 117 Boolean allowEmptyValue; 118 Boolean exclusiveMaximum; 119 Boolean exclusiveMinimum; 120 Boolean required; 121 Boolean uniqueItems; 122 Boolean skipIfEmpty; 123 HttpPartCollectionFormat collectionFormat = HttpPartCollectionFormat.NO_COLLECTION_FORMAT; 124 HttpPartDataType type = HttpPartDataType.NO_TYPE; 125 HttpPartFormat format = HttpPartFormat.NO_FORMAT; 126 Pattern pattern; 127 Number maximum; 128 Number minimum; 129 Number multipleOf; 130 Long maxLength; 131 Long minLength; 132 Long maxItems; 133 Long minItems; 134 Long maxProperties; 135 Long minProperties; 136 Map<String,Object> properties; 137 Object items; 138 Object additionalProperties; 139 boolean noValidate; 140 Class<?> parser; 141 Class<?> serializer; 142 // JSON Schema Draft 2020-12 properties 143 String const_; 144 String[] examples; 145 Boolean deprecated; 146 Number exclusiveMaximumValue; 147 Number exclusiveMinimumValue; 148 149 /** 150 * <mk>const</mk> field (JSON Schema Draft 2020-12). 151 * 152 * <p> 153 * Defines a constant value for this schema. 154 * The instance must be equal to this value to validate. 155 * 156 * @param value 157 * The new value for this property. 158 * @return This object. 159 */ 160 public Builder const_(String value) { 161 const_ = value; 162 return this; 163 } 164 165 /** 166 * <mk>default</mk> field. 167 * 168 * <p> 169 * Declares the value of the parameter that the server will use if none is provided, for example a "count" to control the number of results per page might default to 100 if not supplied by the client in the request. 170 * <br>(Note: "default" has no meaning for required parameters.) 171 * 172 * <p> 173 * Applicable to the following Swagger schema objects: 174 * <ul> 175 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 176 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 177 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 178 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 179 * </ul> 180 * 181 * @param value 182 * The new value for this property. 183 * <br>Ignored if value is <jk>null</jk>. 184 * @return This object. 185 */ 186 public Builder default_(String value) { 187 if (ine(value)) 188 default_ = value; 189 return this; 190 } 191 192 /** 193 * <mk>enum</mk> field. 194 * 195 * <p> 196 * If specified, the input validates successfully if it is equal to one of the elements in this array. 197 * 198 * <p> 199 * Applicable to the following Swagger schema objects: 200 * <ul> 201 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 202 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 203 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 204 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 205 * </ul> 206 * 207 * @param value 208 * The new value for this property. 209 * <br>Ignored if value is <jk>null</jk> or an empty set. 210 * @return This object. 211 */ 212 public Builder enum_(Set<String> value) { 213 if (nn(value) && ! value.isEmpty()) 214 enum_ = value; 215 return this; 216 } 217 218 /** 219 * <mk>enum</mk> field. 220 * 221 * <p> 222 * Same as {@link #enum_(Set)} but takes in a var-args array. 223 * 224 * @param values 225 * The new values for this property. 226 * <br>Ignored if value is empty. 227 * @return This object. 228 */ 229 public Builder enum_(String...values) { 230 return enum_(set(values)); 231 } 232 233 /** 234 * <mk>additionalProperties</mk> field. 235 * 236 * <p> 237 * Applicable to the following Swagger schema objects: 238 * <ul> 239 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 240 * </ul> 241 * 242 * @param value 243 * The new value for this property. 244 * <br>Ignored if value is <jk>null</jk> or empty. 245 * @return This object. 246 */ 247 public Builder additionalProperties(Builder value) { 248 if (nn(value)) 249 additionalProperties = value; 250 return this; 251 } 252 253 /** 254 * <mk>additionalProperties</mk> field. 255 * 256 * <p> 257 * Applicable to the following Swagger schema objects: 258 * <ul> 259 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 260 * </ul> 261 * 262 * @param value 263 * The new value for this property. 264 * <br>Ignored if value is <jk>null</jk> or empty. 265 * @return This object. 266 */ 267 public Builder additionalProperties(HttpPartSchema value) { 268 if (nn(value)) 269 additionalProperties = value; 270 return this; 271 } 272 273 /** 274 * Synonym for {@link #allowEmptyValue()}. 275 * 276 * @return This object. 277 */ 278 public Builder aev() { 279 return allowEmptyValue(true); 280 } 281 282 /** 283 * Synonym for {@link #allowEmptyValue(Boolean)}. 284 * 285 * @param value 286 * The new value for this property. 287 * @return This object. 288 */ 289 public Builder aev(Boolean value) { 290 return allowEmptyValue(value); 291 } 292 293 /** 294 * Synonym for {@link #allowEmptyValue(String)}. 295 * 296 * @param value 297 * The new value for this property. 298 * @return This object. 299 */ 300 public Builder aev(String value) { 301 return allowEmptyValue(value); 302 } 303 304 /** 305 * <mk>allowEmptyValue</mk> field. 306 * 307 * <p> 308 * Shortcut for calling <code>allowEmptyValue(<jk>true</jk>);</code>. 309 * 310 * @return This object. 311 */ 312 public Builder allowEmptyValue() { 313 return allowEmptyValue(true); 314 } 315 316 /** 317 * <mk>allowEmptyValue</mk> field. 318 * 319 * <p> 320 * Sets the ability to pass empty-valued parameters. 321 * <br>This is valid only for either query or formData parameters and allows you to send a parameter with a name only or an empty value. 322 * <br>The default value is <jk>false</jk>. 323 * 324 * <p> 325 * Applicable to the following Swagger schema objects: 326 * <ul> 327 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 328 * </ul> 329 * 330 * @param value 331 * The new value for this property. 332 * <br>Ignored if value is <jk>null</jk>. 333 * @return This object. 334 */ 335 public Builder allowEmptyValue(Boolean value) { 336 allowEmptyValue = resolve(value, allowEmptyValue); 337 return this; 338 } 339 340 /** 341 * <mk>allowEmptyValue</mk> field. 342 * 343 * <p> 344 * Same as {@link #allowEmptyValue(Boolean)} but takes in a string boolean value. 345 * 346 * @param value 347 * The new value for this property. 348 * <br>Ignored if value is <jk>null</jk> or empty. 349 * @return This object. 350 */ 351 public Builder allowEmptyValue(String value) { 352 allowEmptyValue = resolve(value, allowEmptyValue); 353 return this; 354 } 355 356 /** 357 * Shortcut for <c>additionalProperties(value)</c> 358 * 359 * <p> 360 * Applicable to the following Swagger schema objects: 361 * <ul> 362 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 363 * </ul> 364 * 365 * @param value 366 * The new value for this property. 367 * <br>Ignored if value is <jk>null</jk> or empty. 368 * @return This object. 369 */ 370 public Builder ap(Builder value) { 371 return additionalProperties(value); 372 } 373 374 /** 375 * Shortcut for <c>additionalProperties(value)</c> 376 * 377 * <p> 378 * Applicable to the following Swagger schema objects: 379 * <ul> 380 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 381 * </ul> 382 * 383 * @param value 384 * The new value for this property. 385 * <br>Ignored if value is <jk>null</jk> or empty. 386 * @return This object. 387 */ 388 public Builder ap(HttpPartSchema value) { 389 return additionalProperties(value); 390 } 391 392 /** 393 * Apply the specified annotation to this schema. 394 * 395 * @param a The annotation to apply. Must not be <jk>null</jk>. 396 * @return This object. 397 */ 398 public Builder apply(Annotation a) { 399 if (a instanceof Schema a2) 400 return apply(a2); 401 if (a instanceof Items a2) 402 return apply(a2); 403 if (a instanceof SubItems a2) 404 return apply(a2); 405 if (a instanceof HttpPartMarshalling a2) 406 return apply(a2); 407 if (cn(a.annotationType()).startsWith("jakarta.validation.constraints.")) { 408 applyJakartaValidation(a); 409 return this; 410 } 411 return applyHttpAnnotation(a); 412 } 413 414 /** 415 * Instantiates a new {@link HttpPartSchema} object based on the configuration of this builder. 416 * 417 * <p> 418 * This method can be called multiple times to produce new schema objects. 419 * 420 * @return 421 * A new {@link HttpPartSchema} object. 422 * <br>Never <jk>null</jk>. 423 */ 424 public HttpPartSchema build() { 425 return new HttpPartSchema(this); 426 } 427 428 /** 429 * Synonym for {@link #collectionFormat(HttpPartCollectionFormat)}. 430 * 431 * @param value 432 * The new value for this property. 433 * @return This object. 434 */ 435 public Builder cf(HttpPartCollectionFormat value) { 436 return collectionFormat(value); 437 } 438 439 /** 440 * Synonym for {@link #collectionFormat(String)}. 441 * 442 * @param value 443 * The new value for this property. 444 * @return This object. 445 */ 446 public Builder cf(String value) { 447 return collectionFormat(value); 448 } 449 450 /** 451 * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.CSV)</c>. 452 * 453 * @return This object. 454 */ 455 public Builder cfCsv() { 456 return collectionFormat(HttpPartCollectionFormat.CSV); 457 } 458 459 /** 460 * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.MULTI)</c>. 461 * 462 * @return This object. 463 */ 464 public Builder cfMulti() { 465 return collectionFormat(HttpPartCollectionFormat.MULTI); 466 } 467 468 /** 469 * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.NO_COLLECTION_FORMAT)</c>. 470 * 471 * @return This object. 472 */ 473 public Builder cfNone() { 474 return collectionFormat(HttpPartCollectionFormat.NO_COLLECTION_FORMAT); 475 } 476 477 /** 478 * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.PIPES)</c>. 479 * 480 * @return This object. 481 */ 482 public Builder cfPipes() { 483 return collectionFormat(HttpPartCollectionFormat.PIPES); 484 } 485 486 /** 487 * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.SSV)</c>. 488 * 489 * @return This object. 490 */ 491 public Builder cfSsv() { 492 return collectionFormat(HttpPartCollectionFormat.SSV); 493 } 494 495 /** 496 * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.TSV)</c>. 497 * 498 * @return This object. 499 */ 500 public Builder cfTsv() { 501 return collectionFormat(HttpPartCollectionFormat.TSV); 502 } 503 504 /** 505 * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.UONC)</c>. 506 * 507 * @return This object. 508 */ 509 public Builder cfUon() { 510 return collectionFormat(HttpPartCollectionFormat.UONC); 511 } 512 513 /** 514 * <mk>httpStatusCode</mk> key. 515 * 516 * <p> 517 * Applicable to the following Swagger schema objects: 518 * <ul> 519 * <li><a class="doclink" href="https://swagger.io/specification/v2#responsesObject">Responses</a> 520 * </ul> 521 * 522 * @param value 523 * The new value for this property. 524 * <br>Ignored if value is <c>0</c>. 525 * @return This object. 526 */ 527 public Builder code(int value) { 528 if (value != 0) { 529 if (codes == null) 530 codes = new TreeSet<>(); 531 codes.add(value); 532 } 533 return this; 534 } 535 536 /** 537 * <mk>httpStatusCode</mk> key. 538 * 539 * <p> 540 * Applicable to the following Swagger schema objects: 541 * <ul> 542 * <li><a class="doclink" href="https://swagger.io/specification/v2#responsesObject">Responses</a> 543 * </ul> 544 * 545 * @param value 546 * The new value for this property. 547 * <br>Ignored if <jk>null</jk> or an empty array. 548 * @return This object. 549 */ 550 public Builder codes(int[] value) { 551 if (nn(value) && value.length != 0) 552 for (var v : value) 553 code(v); 554 return this; 555 } 556 557 /** 558 * <mk>collectionFormat</mk> field. 559 * 560 * <p> 561 * Determines the format of the array if <c>type</c> <js>"array"</js> is used. 562 * <br>Can only be used if <c>type</c> is <js>"array"</js>. 563 * 564 * <p> 565 * Applicable to the following Swagger schema objects: 566 * <ul> 567 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 568 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 569 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 570 * </ul> 571 * 572 * <p> 573 * Note that for collections/arrays parameters with POJO element types, the input is broken into a string array before being converted into POJO elements. 574 * 575 * <ul class='values javatree'> 576 * <ul class='jc'>{@link HttpPartCollectionFormat} 577 * <ul> 578 * <li> 579 * {@link HttpPartCollectionFormat#CSV CSV} (default) - Comma-separated values (e.g. <js>"foo,bar"</js>). 580 * <li> 581 * {@link HttpPartCollectionFormat#SSV SSV} - Space-separated values (e.g. <js>"foo bar"</js>). 582 * <li> 583 * {@link HttpPartCollectionFormat#TSV TSV} - Tab-separated values (e.g. <js>"foo\tbar"</js>). 584 * <li> 585 * {@link HttpPartCollectionFormat#PIPES PIPES} - Pipe-separated values (e.g. <js>"foo|bar"</js>). 586 * <li> 587 * {@link HttpPartCollectionFormat#MULTI MULTI} - Corresponds to multiple parameter instances instead of multiple values for a single instance (e.g. <js>"foo=bar&foo=baz"</js>). 588 * <li> 589 * {@link HttpPartCollectionFormat#UONC UONC} - UON collection notation (e.g. <js>"@(foo,bar)"</js>). 590 * </ul> 591 * </ul> 592 * 593 * @param value 594 * The new value for this property. 595 * @return This object. 596 */ 597 public Builder collectionFormat(HttpPartCollectionFormat value) { 598 // Guard null for parity with the String overload (which ine()-guards); a null must not overwrite the field. 599 if (value != null) 600 collectionFormat = value; 601 return this; 602 } 603 604 /** 605 * <mk>collectionFormat</mk> field. 606 * 607 * <p> 608 * Determines the format of the array if <c>type</c> <js>"array"</js> is used. 609 * <br>Can only be used if <c>type</c> is <js>"array"</js>. 610 * 611 * <p> 612 * Applicable to the following Swagger schema objects: 613 * <ul> 614 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 615 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 616 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 617 * </ul> 618 * 619 * <p> 620 * Note that for collections/arrays parameters with POJO element types, the input is broken into a string array before being converted into POJO elements. 621 * 622 * <ul class='values'> 623 * <li> 624 * <js>"csv"</js> (default) - Comma-separated values (e.g. <js>"foo,bar"</js>). 625 * <li> 626 * <js>"ssv"</js> - Space-separated values (e.g. <js>"foo bar"</js>). 627 * <li> 628 * <js>"tsv"</js> - Tab-separated values (e.g. <js>"foo\tbar"</js>). 629 * <li> 630 * <js>"pipes</js> - Pipe-separated values (e.g. <js>"foo|bar"</js>). 631 * <li> 632 * <js>"multi"</js> - Corresponds to multiple parameter instances instead of multiple values for a single instance (e.g. <js>"foo=bar&foo=baz"</js>). 633 * <li> 634 * <js>"uon"</js> - UON notation (e.g. <js>"@(foo,bar)"</js>). 635 * <li> 636 * </ul> 637 * 638 * @param value 639 * The new value for this property. 640 * <br>Ignored if value is <jk>null</jk> or empty. 641 * @return This object. 642 */ 643 public Builder collectionFormat(String value) { 644 try { 645 if (ine(value)) 646 this.collectionFormat = HttpPartCollectionFormat.fromString(value); 647 } catch (Exception e) { 648 throw new BasicRuntimeException(e, "Invalid value '%s' passed in as collectionFormat value. Valid values: %s", value, HttpPartCollectionFormat.values()); 649 } 650 return this; 651 } 652 653 /** 654 * <mk>deprecated</mk> field (JSON Schema Draft 2020-12). 655 * 656 * <p> 657 * Indicates that applications should refrain from usage of this property. 658 * This is used for documentation purposes only and does not affect validation. 659 * 660 * @param value 661 * The new value for this property. 662 * @return This object. 663 */ 664 public Builder deprecated(Boolean value) { 665 deprecated = resolve(value, deprecated); 666 return this; 667 } 668 669 /** 670 * Synonym for {@link #default_(String)}. 671 * 672 * @param value 673 * The new value for this property. 674 * @return This object. 675 */ 676 public Builder df(String value) { 677 return default_(value); 678 } 679 680 /** 681 * Synonym for {@link #enum_(Set)}. 682 * 683 * @param value 684 * The new value for this property. 685 * @return This object. 686 */ 687 public Builder e(Set<String> value) { 688 return enum_(value); 689 } 690 691 /** 692 * Synonym for {@link #enum_(String...)}. 693 * 694 * @param values 695 * The new values for this property. 696 * @return This object. 697 */ 698 public Builder e(String...values) { 699 return enum_(values); 700 } 701 702 /** 703 * Synonym for {@link #exclusiveMaximum()}. 704 * 705 * @return This object. 706 */ 707 public Builder emax() { 708 return exclusiveMaximum(); 709 } 710 711 /** 712 * Synonym for {@link #exclusiveMaximum(Boolean)}. 713 * 714 * @param value 715 * The new value for this property. 716 * @return This object. 717 */ 718 public Builder emax(Boolean value) { 719 return exclusiveMaximum(value); 720 } 721 722 /** 723 * Synonym for {@link #exclusiveMaximum(String)}. 724 * 725 * @param value 726 * The new value for this property. 727 * @return This object. 728 */ 729 public Builder emax(String value) { 730 return exclusiveMaximum(value); 731 } 732 733 /** 734 * Synonym for {@link #exclusiveMinimum()}. 735 * 736 * @return This object. 737 */ 738 public Builder emin() { 739 return exclusiveMinimum(); 740 } 741 742 /** 743 * Synonym for {@link #exclusiveMinimum(Boolean)}. 744 * 745 * @param value 746 * The new value for this property. 747 * @return This object. 748 */ 749 public Builder emin(Boolean value) { 750 return exclusiveMinimum(value); 751 } 752 753 /** 754 * Synonym for {@link #exclusiveMinimum(String)}. 755 * 756 * @param value 757 * The new value for this property. 758 * @return This object. 759 */ 760 public Builder emin(String value) { 761 return exclusiveMinimum(value); 762 } 763 764 /** 765 * <mk>examples</mk> field (JSON Schema Draft 2020-12). 766 * 767 * <p> 768 * An array of example values. 769 * This is used for documentation purposes only and does not affect validation. 770 * 771 * @param value 772 * The new value for this property. 773 * @return This object. 774 */ 775 public Builder examples(String...value) { 776 examples = value; 777 return this; 778 } 779 780 /** 781 * <mk>exclusiveMaximum</mk> field. 782 * 783 * <p> 784 * Shortcut for calling <code>exclusiveMaximum(<jk>true</jk>);</code>. 785 * 786 * @return This object. 787 */ 788 public Builder exclusiveMaximum() { 789 return exclusiveMaximum(true); 790 } 791 792 /** 793 * <mk>exclusiveMaximum</mk> field. 794 * 795 * <p> 796 * Defines whether the maximum is matched exclusively. 797 * 798 * <p> 799 * Only allowed for the following types: <js>"integer"</js>, <js>"number"</js>. 800 * <br>If <jk>true</jk>, must be accompanied with <c>maximum</c>. 801 * 802 * <p> 803 * Applicable to the following Swagger schema objects: 804 * <ul> 805 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 806 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 807 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 808 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 809 * </ul> 810 * 811 * @param value 812 * The new value for this property. 813 * <br>Ignored if value is <jk>null</jk>. 814 * @return This object. 815 */ 816 public Builder exclusiveMaximum(Boolean value) { 817 exclusiveMaximum = resolve(value, exclusiveMaximum); 818 return this; 819 } 820 821 /** 822 * <mk>exclusiveMaximum</mk> field. 823 * 824 * <p> 825 * Same as {@link #exclusiveMaximum(Boolean)} but takes in a string boolean value. 826 * 827 * @param value 828 * The new value for this property. 829 * <br>Ignored if value is <jk>null</jk> or empty. 830 * @return This object. 831 */ 832 public Builder exclusiveMaximum(String value) { 833 exclusiveMaximum = resolve(value, exclusiveMaximum); 834 return this; 835 } 836 837 /** 838 * <mk>exclusiveMaximum</mk> field with numeric value (JSON Schema Draft 2020-12). 839 * 840 * <p> 841 * Defines the exclusive maximum value for numeric types. 842 * The instance is valid if it is strictly less than (not equal to) this value. 843 * 844 * <p> 845 * This is the Draft 2020-12 version that uses a numeric value instead of a boolean flag. 846 * If this is set, it takes precedence over the boolean {@link #exclusiveMaximum(Boolean)} property. 847 * 848 * @param value 849 * The new value for this property. 850 * @return This object. 851 */ 852 public Builder exclusiveMaximumValue(Number value) { 853 exclusiveMaximumValue = value; 854 return this; 855 } 856 857 /** 858 * <mk>exclusiveMinimum</mk> field. 859 * 860 * <p> 861 * Shortcut for calling <code>exclusiveMinimum(<jk>true</jk>);</code>. 862 * 863 * @return This object. 864 */ 865 public Builder exclusiveMinimum() { 866 return exclusiveMinimum(true); 867 } 868 869 /** 870 * <mk>exclusiveMinimum</mk> field. 871 * 872 * <p> 873 * Defines whether the minimum is matched exclusively. 874 * 875 * <p> 876 * Only allowed for the following types: <js>"integer"</js>, <js>"number"</js>. 877 * <br>If <jk>true</jk>, must be accompanied with <c>minimum</c>. 878 * 879 * <p> 880 * Applicable to the following Swagger schema objects: 881 * <ul> 882 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 883 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 884 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 885 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 886 * </ul> 887 * 888 * @param value 889 * The new value for this property. 890 * <br>Ignored if value is <jk>null</jk>. 891 * @return This object. 892 */ 893 public Builder exclusiveMinimum(Boolean value) { 894 exclusiveMinimum = resolve(value, exclusiveMinimum); 895 return this; 896 } 897 898 /** 899 * <mk>exclusiveMinimum</mk> field. 900 * 901 * <p> 902 * Same as {@link #exclusiveMinimum(Boolean)} but takes in a string boolean value. 903 * 904 * @param value 905 * The new value for this property. 906 * <br>Ignored if value is <jk>null</jk> or empty. 907 * @return This object. 908 */ 909 public Builder exclusiveMinimum(String value) { 910 exclusiveMinimum = resolve(value, exclusiveMinimum); 911 return this; 912 } 913 914 /** 915 * <mk>exclusiveMinimum</mk> field with numeric value (JSON Schema Draft 2020-12). 916 * 917 * <p> 918 * Defines the exclusive minimum value for numeric types. 919 * The instance is valid if it is strictly greater than (not equal to) this value. 920 * 921 * <p> 922 * This is the Draft 2020-12 version that uses a numeric value instead of a boolean flag. 923 * If this is set, it takes precedence over the boolean {@link #exclusiveMinimum(Boolean)} property. 924 * 925 * @param value 926 * The new value for this property. 927 * @return This object. 928 */ 929 public Builder exclusiveMinimumValue(Number value) { 930 exclusiveMinimumValue = value; 931 return this; 932 } 933 934 /** 935 * Synonym for {@link #format(HttpPartFormat)}. 936 * 937 * @param value 938 * The new value for this property. 939 * @return This object. 940 */ 941 public Builder f(HttpPartFormat value) { 942 return format(value); 943 } 944 945 /** 946 * Synonym for {@link #format(String)}. 947 * 948 * @param value 949 * The new value for this property. 950 * @return This object. 951 */ 952 public Builder f(String value) { 953 return format(value); 954 } 955 956 /** 957 * Shortcut for <c>format(HttpPartFormat.BINARY)</c>. 958 * 959 * @return This object. 960 */ 961 public Builder fBinary() { 962 format = HttpPartFormat.BINARY; 963 return this; 964 } 965 966 /** 967 * Shortcut for <c>format(HttpPartFormat.BINARY_SPACED)</c>. 968 * 969 * @return This object. 970 */ 971 public Builder fBinarySpaced() { 972 format = HttpPartFormat.BINARY_SPACED; 973 return this; 974 } 975 976 /** 977 * Shortcut for <c>format(HttpPartFormat.BYTE)</c>. 978 * 979 * @return This object. 980 */ 981 public Builder fByte() { 982 format = HttpPartFormat.BYTE; 983 return this; 984 } 985 986 /** 987 * Shortcut for <c>format(HttpPartFormat.DATE)</c>. 988 * 989 * @return This object. 990 */ 991 public Builder fDate() { 992 format = HttpPartFormat.DATE; 993 return this; 994 } 995 996 /** 997 * Shortcut for <c>format(HttpPartFormat.DATE_TIME)</c>. 998 * 999 * @return This object. 1000 */ 1001 public Builder fDateTime() { 1002 format = HttpPartFormat.DATE_TIME; 1003 return this; 1004 } 1005 1006 /** 1007 * Shortcut for <c>format(HttpPartFormat.DOUBLE)</c>. 1008 * 1009 * @return This object. 1010 */ 1011 public Builder fDouble() { 1012 format = HttpPartFormat.DOUBLE; 1013 return this; 1014 } 1015 1016 /** 1017 * Shortcut for <c>format(HttpPartFormat.FLOAT)</c>. 1018 * 1019 * @return This object. 1020 */ 1021 public Builder fFloat() { 1022 format = HttpPartFormat.FLOAT; 1023 return this; 1024 } 1025 1026 /** 1027 * Shortcut for <c>format(HttpPartFormat.INT32)</c>. 1028 * 1029 * @return This object. 1030 */ 1031 public Builder fInt32() { 1032 format = HttpPartFormat.INT32; 1033 return this; 1034 } 1035 1036 /** 1037 * Shortcut for <c>format(HttpPartFormat.INT64)</c>. 1038 * 1039 * @return This object. 1040 */ 1041 public Builder fInt64() { 1042 format = HttpPartFormat.INT64; 1043 return this; 1044 } 1045 1046 /** 1047 * Shortcut for <c>format(HttpPartFormat.NO_FORMAT)</c>. 1048 * 1049 * @return This object. 1050 */ 1051 public Builder fNone() { 1052 format = HttpPartFormat.NO_FORMAT; 1053 return this; 1054 } 1055 1056 /** 1057 * <mk>format</mk> field. 1058 * 1059 * <p> 1060 * The extending format for the previously mentioned <a class="doclink" href="https://swagger.io/specification/v2#parameterType">parameter type</a>. 1061 * 1062 * <p> 1063 * Applicable to the following Swagger schema objects: 1064 * <ul> 1065 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1066 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1067 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 1068 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1069 * </ul> 1070 * 1071 * <ul class='values javatree'> 1072 * <ul class='jc'>{@link HttpPartFormat} 1073 * <ul> 1074 * <li class='jf'> 1075 * {@link HttpPartFormat#INT32 INT32} - Signed 32 bits. 1076 * <br>Only valid with type <js>"integer"</js>. 1077 * <li class='jf'> 1078 * {@link HttpPartFormat#INT64 INT64} - Signed 64 bits. 1079 * <br>Only valid with type <js>"integer"</js>. 1080 * <li class='jf'> 1081 * {@link HttpPartFormat#FLOAT FLOAT} - 32-bit floating point number. 1082 * <br>Only valid with type <js>"number"</js>. 1083 * <li class='jf'> 1084 * {@link HttpPartFormat#DOUBLE DOUBLE} - 64-bit floating point number. 1085 * <br>Only valid with type <js>"number"</js>. 1086 * <li class='jf'> 1087 * {@link HttpPartFormat#BYTE BYTE} - BASE-64 encoded characters. 1088 * <br>Only valid with type <js>"string"</js>. 1089 * <br>Parameters of type POJO convertible from string are converted after the string has been decoded. 1090 * <li class='jf'> 1091 * {@link HttpPartFormat#BINARY BINARY} - Hexadecimal encoded octets (e.g. <js>"00FF"</js>). 1092 * <br>Only valid with type <js>"string"</js>. 1093 * <br>Parameters of type POJO convertible from string are converted after the string has been decoded. 1094 * <li class='jf'> 1095 * {@link HttpPartFormat#BINARY_SPACED BINARY_SPACED} - Hexadecimal encoded octets, spaced (e.g. <js>"00 FF"</js>). 1096 * <br>Only valid with type <js>"string"</js>. 1097 * <br>Parameters of type POJO convertible from string are converted after the string has been decoded. 1098 * <li class='jf'> 1099 * {@link HttpPartFormat#DATE DATE} - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 full-date</a>. 1100 * <br>Only valid with type <js>"string"</js>. 1101 * <li class='jf'> 1102 * {@link HttpPartFormat#DATE_TIME DATE_TIME} - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 date-time</a>. 1103 * <br>Only valid with type <js>"string"</js>. 1104 * <li class='jf'> 1105 * {@link HttpPartFormat#PASSWORD PASSWORD} - Used to hint UIs the input needs to be obscured. 1106 * <br>This format does not affect the serialization or parsing of the parameter. 1107 * <li class='jf'> 1108 * {@link HttpPartFormat#UON UON} - UON notation (e.g. <js>"(foo=bar,baz=@(qux,123))"</js>). 1109 * <br>Only valid with type <js>"object"</js>. 1110 * <br>If not specified, then the input is interpreted as plain-text and is converted to a POJO directly. 1111 * </ul> 1112 * </ul> 1113 * 1114 * <h5 class='section'>See Also:</h5><ul> 1115 * <li class='extlink'><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Swagger Schema Object</a> 1116 * </ul> 1117 * 1118 * @param value 1119 * The new value for this property. 1120 * @return This object. 1121 */ 1122 public Builder format(HttpPartFormat value) { 1123 // Guard null for parity with the String overload (which ine()-guards); a null must not overwrite the field. 1124 if (value != null) 1125 format = value; 1126 return this; 1127 } 1128 1129 /** 1130 * <mk>format</mk> field. 1131 * 1132 * <p> 1133 * The extending format for the previously mentioned <a class="doclink" href="https://swagger.io/specification/v2#parameterType">parameter type</a>. 1134 * 1135 * <p> 1136 * Applicable to the following Swagger schema objects: 1137 * <ul> 1138 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1139 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1140 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 1141 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1142 * </ul> 1143 * 1144 * <ul class='values'> 1145 * <li> 1146 * <js>"int32"</js> - Signed 32 bits. 1147 * <br>Only valid with type <js>"integer"</js>. 1148 * <li> 1149 * <js>"int64"</js> - Signed 64 bits. 1150 * <br>Only valid with type <js>"integer"</js>. 1151 * <li> 1152 * <js>"float"</js> - 32-bit floating point number. 1153 * <br>Only valid with type <js>"number"</js>. 1154 * <li> 1155 * <js>"double"</js> - 64-bit floating point number. 1156 * <br>Only valid with type <js>"number"</js>. 1157 * <li> 1158 * <js>"byte"</js> - BASE-64 encoded characters. 1159 * <br>Only valid with type <js>"string"</js>. 1160 * <br>Parameters of type POJO convertible from string are converted after the string has been decoded. 1161 * <li> 1162 * <js>"binary"</js> - Hexadecimal encoded octets (e.g. <js>"00FF"</js>). 1163 * <br>Only valid with type <js>"string"</js>. 1164 * <br>Parameters of type POJO convertible from string are converted after the string has been decoded. 1165 * <li> 1166 * <js>"binary-spaced"</js> - Hexadecimal encoded octets, spaced (e.g. <js>"00 FF"</js>). 1167 * <br>Only valid with type <js>"string"</js>. 1168 * <br>Parameters of type POJO convertible from string are converted after the string has been decoded. 1169 * <li> 1170 * <js>"date"</js> - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 full-date</a>. 1171 * <br>Only valid with type <js>"string"</js>. 1172 * <li> 1173 * <js>"date-time"</js> - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 date-time</a>. 1174 * <br>Only valid with type <js>"string"</js>. 1175 * <li> 1176 * <js>"password"</js> - Used to hint UIs the input needs to be obscured. 1177 * <br>This format does not affect the serialization or parsing of the parameter. 1178 * <li> 1179 * <js>"uon"</js> - UON notation (e.g. <js>"(foo=bar,baz=@(qux,123))"</js>). 1180 * <br>Only valid with type <js>"object"</js>. 1181 * <br>If not specified, then the input is interpreted as plain-text and is converted to a POJO directly. 1182 * </ul> 1183 * 1184 * <h5 class='section'>See Also:</h5><ul> 1185 * <li class='extlink'><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Swagger Schema Object</a> 1186 * </ul> 1187 * 1188 * @param value 1189 * The new value for this property. 1190 * <br>Ignored if value is <jk>null</jk> or an empty string. 1191 * @return This object. 1192 */ 1193 public Builder format(String value) { 1194 try { 1195 if (ine(value)) 1196 format = HttpPartFormat.fromString(value); 1197 } catch (Exception e) { 1198 throw new BasicRuntimeException(e, "Invalid value '%s' passed in as format value. Valid values: %s", value, HttpPartFormat.values()); 1199 } 1200 return this; 1201 } 1202 1203 /** 1204 * Shortcut for <c>format(HttpPartFormat.PASSWORD)</c>. 1205 * 1206 * @return This object. 1207 */ 1208 public Builder fPassword() { 1209 format = HttpPartFormat.PASSWORD; 1210 return this; 1211 } 1212 1213 /** 1214 * Shortcut for <c>format(HttpPartFormat.UON)</c>. 1215 * 1216 * @return This object. 1217 */ 1218 public Builder fUon() { 1219 format = HttpPartFormat.UON; 1220 return this; 1221 } 1222 1223 /** 1224 * Synonym for {@link #items(HttpPartSchema.Builder)}. 1225 * 1226 * @param value 1227 * The new value for this property. 1228 * @return This object. 1229 */ 1230 public Builder i(Builder value) { 1231 return items(value); 1232 } 1233 1234 /** 1235 * Synonym for {@link #items(HttpPartSchema)}. 1236 * 1237 * @param value 1238 * The new value for this property. 1239 * @return This object. 1240 */ 1241 public Builder i(HttpPartSchema value) { 1242 return items(value); 1243 } 1244 1245 /** 1246 * <mk>items</mk> field. 1247 * 1248 * <p> 1249 * Describes the type of items in the array. 1250 * <p> 1251 * Required if <c>type</c> is <js>"array"</js>. 1252 * <br>Can only be used if <c>type</c> is <js>"array"</js>. 1253 * 1254 * <p> 1255 * Applicable to the following Swagger schema objects: 1256 * <ul> 1257 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1258 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1259 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 1260 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1261 * </ul> 1262 * 1263 * @param value 1264 * The new value for this property. 1265 * <br>Ignored if value is <jk>null</jk> or empty. 1266 * @return This object. 1267 */ 1268 public Builder items(Builder value) { 1269 if (nn(value)) 1270 items = value; 1271 return this; 1272 } 1273 1274 /** 1275 * <mk>items</mk> field. 1276 * 1277 * <p> 1278 * Describes the type of items in the array. 1279 * <p> 1280 * Required if <c>type</c> is <js>"array"</js>. 1281 * <br>Can only be used if <c>type</c> is <js>"array"</js>. 1282 * 1283 * <p> 1284 * Applicable to the following Swagger schema objects: 1285 * <ul> 1286 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1287 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1288 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 1289 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1290 * </ul> 1291 * 1292 * @param value 1293 * The new value for this property. 1294 * <br>Ignored if value is <jk>null</jk> or empty. 1295 * @return This object. 1296 */ 1297 public Builder items(HttpPartSchema value) { 1298 if (nn(value)) 1299 items = value; 1300 return this; 1301 } 1302 1303 /** 1304 * Synonym for {@link #maximum(Number)}. 1305 * 1306 * @param value 1307 * The new value for this property. 1308 * @return This object. 1309 */ 1310 public Builder max(Number value) { 1311 return maximum(value); 1312 } 1313 1314 /** 1315 * Synonym for {@link #maxItems(Long)}. 1316 * 1317 * @param value 1318 * The new value for this property. 1319 * @return This object. 1320 */ 1321 public Builder maxi(Long value) { 1322 return maxItems(value); 1323 } 1324 1325 /** 1326 * Synonym for {@link #maxItems(String)}. 1327 * 1328 * @param value 1329 * The new value for this property. 1330 * @return This object. 1331 */ 1332 public Builder maxi(String value) { 1333 return maxItems(value); 1334 } 1335 1336 /** 1337 * <mk>maximum</mk> field. 1338 * 1339 * <p> 1340 * Defines the maximum value for a parameter of numeric types. 1341 * 1342 * <p> 1343 * Only allowed for the following types: <js>"integer"</js>, <js>"number"</js>. 1344 * 1345 * <p> 1346 * Applicable to the following Swagger schema objects: 1347 * <ul> 1348 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1349 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1350 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 1351 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1352 * </ul> 1353 * 1354 * @param value 1355 * The new value for this property. 1356 * <br>Ignored if value is <jk>null</jk>. 1357 * @return This object. 1358 */ 1359 public Builder maximum(Number value) { 1360 if (nn(value)) 1361 maximum = value; 1362 return this; 1363 } 1364 1365 /** 1366 * <mk>maxItems</mk> field. 1367 * 1368 * <p> 1369 * An array or collection is valid if its size is less than, or equal to, the value of this keyword. 1370 * 1371 * <p> 1372 * Only allowed for the following types: <js>"array"</js>. 1373 * 1374 * <p> 1375 * Applicable to the following Swagger schema objects: 1376 * <ul> 1377 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1378 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1379 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 1380 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1381 * </ul> 1382 * 1383 * @param value 1384 * The new value for this property. 1385 * <br>Ignored if value is <jk>null</jk> or <c>-1</c>. 1386 * @return This object. 1387 */ 1388 public Builder maxItems(Long value) { 1389 maxItems = resolve(value, maxItems); 1390 return this; 1391 } 1392 1393 /** 1394 * <mk>maxItems</mk> field. 1395 * 1396 * <p> 1397 * Same as {@link #maxItems(Long)} but takes in a string number. 1398 * 1399 * @param value 1400 * The new value for this property. 1401 * <br>Ignored if value is <jk>null</jk> or empty. 1402 * @return This object. 1403 */ 1404 public Builder maxItems(String value) { 1405 maxItems = resolve(value, maxItems); 1406 return this; 1407 } 1408 1409 /** 1410 * Synonym for {@link #maxLength(Long)}. 1411 * 1412 * @param value 1413 * The new value for this property. 1414 * @return This object. 1415 */ 1416 public Builder maxl(Long value) { 1417 return maxLength(value); 1418 } 1419 1420 /** 1421 * Synonym for {@link #maxLength(String)}. 1422 * 1423 * @param value 1424 * The new value for this property. 1425 * @return This object. 1426 */ 1427 public Builder maxl(String value) { 1428 return maxLength(value); 1429 } 1430 1431 /** 1432 * <mk>maxLength</mk> field. 1433 * 1434 * <p> 1435 * A string instance is valid against this keyword if its length is less than, or equal to, the value of this keyword. 1436 * <br>The length of a string instance is defined as the number of its characters as defined by <a href='https://tools.ietf.org/html/rfc4627'>RFC 4627</a>. 1437 * 1438 * <p> 1439 * Only allowed for the following types: <js>"string"</js>. 1440 * 1441 * <p> 1442 * Applicable to the following Swagger schema objects: 1443 * <ul> 1444 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1445 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1446 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 1447 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1448 * </ul> 1449 * 1450 * @param value 1451 * The new value for this property. 1452 * <br>Ignored if value is <jk>null</jk> or <c>-1</c>. 1453 * @return This object. 1454 */ 1455 public Builder maxLength(Long value) { 1456 maxLength = resolve(value, maxLength); 1457 return this; 1458 } 1459 1460 /** 1461 * <mk>maxLength</mk> field. 1462 * 1463 * <p> 1464 * Same as {@link #maxLength(Long)} but takes in a string number. 1465 * 1466 * @param value 1467 * The new value for this property. 1468 * <br>Ignored if value is <jk>null</jk> or empty. 1469 * @return This object. 1470 */ 1471 public Builder maxLength(String value) { 1472 maxLength = resolve(value, maxLength); 1473 return this; 1474 } 1475 1476 /** 1477 * Synonym for {@link #maxProperties(Long)}. 1478 * 1479 * @param value 1480 * The new value for this property. 1481 * @return This object. 1482 */ 1483 public Builder maxp(Long value) { 1484 return maxProperties(value); 1485 } 1486 1487 /** 1488 * Synonym for {@link #maxProperties(String)}. 1489 * 1490 * @param value 1491 * The new value for this property. 1492 * @return This object. 1493 */ 1494 public Builder maxp(String value) { 1495 return maxProperties(value); 1496 } 1497 1498 /** 1499 * <mk>mapProperties</mk> field. 1500 * 1501 * <p> 1502 * Applicable to the following Swagger schema objects: 1503 * <ul> 1504 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1505 * </ul> 1506 * 1507 * @param value 1508 * The new value for this property. 1509 * <br>Ignored if value is <jk>null</jk> or <c>-1</c>. 1510 * @return This object. 1511 */ 1512 public Builder maxProperties(Long value) { 1513 maxProperties = resolve(value, maxProperties); 1514 return this; 1515 } 1516 1517 /** 1518 * <mk>mapProperties</mk> field. 1519 * 1520 * <p> 1521 * Same as {@link #maxProperties(Long)} but takes in a string number. 1522 * 1523 * @param value 1524 * The new value for this property. 1525 * <br>Ignored if value is <jk>null</jk> or empty. 1526 * @return This object. 1527 */ 1528 public Builder maxProperties(String value) { 1529 maxProperties = resolve(value, maxProperties); 1530 return this; 1531 } 1532 1533 /** 1534 * Synonym for {@link #minimum(Number)}. 1535 * 1536 * @param value 1537 * The new value for this property. 1538 * @return This object. 1539 */ 1540 public Builder min(Number value) { 1541 return minimum(value); 1542 } 1543 1544 /** 1545 * Synonym for {@link #minItems(Long)}. 1546 * 1547 * @param value 1548 * The new value for this property. 1549 * @return This object. 1550 */ 1551 public Builder mini(Long value) { 1552 return minItems(value); 1553 } 1554 1555 /** 1556 * Synonym for {@link #minItems(String)}. 1557 * 1558 * @param value 1559 * The new value for this property. 1560 * @return This object. 1561 */ 1562 public Builder mini(String value) { 1563 return minItems(value); 1564 } 1565 1566 /** 1567 * <mk>minimum</mk> field. 1568 * 1569 * <p> 1570 * Defines the minimum value for a parameter of numeric types. 1571 * 1572 * <p> 1573 * Only allowed for the following types: <js>"integer"</js>, <js>"number"</js>. 1574 * 1575 * <p> 1576 * Applicable to the following Swagger schema objects: 1577 * <ul> 1578 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1579 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1580 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 1581 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1582 * </ul> 1583 * 1584 * @param value 1585 * The new value for this property. 1586 * <br>Ignored if value is <jk>null</jk>. 1587 * @return This object. 1588 */ 1589 public Builder minimum(Number value) { 1590 if (nn(value)) 1591 minimum = value; 1592 return this; 1593 } 1594 1595 /** 1596 * <mk>minItems</mk> field. 1597 * 1598 * <p> 1599 * An array or collection is valid if its size is greater than, or equal to, the value of this keyword. 1600 * 1601 * <p> 1602 * Only allowed for the following types: <js>"array"</js>. 1603 * 1604 * <p> 1605 * Applicable to the following Swagger schema objects: 1606 * <ul> 1607 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1608 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1609 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 1610 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1611 * </ul> 1612 * 1613 * @param value 1614 * The new value for this property. 1615 * <br>Ignored if value is <jk>null</jk> or <c>-1</c>. 1616 * @return This object. 1617 */ 1618 public Builder minItems(Long value) { 1619 minItems = resolve(value, minItems); 1620 return this; 1621 } 1622 1623 /** 1624 * <mk>minItems</mk> field. 1625 * 1626 * <p> 1627 * Same as {@link #minItems(Long)} but takes in a string number. 1628 * 1629 * @param value 1630 * The new value for this property. 1631 * <br>Ignored if value is <jk>null</jk> or empty. 1632 * @return This object. 1633 */ 1634 public Builder minItems(String value) { 1635 minItems = resolve(value, minItems); 1636 return this; 1637 } 1638 1639 /** 1640 * Synonym for {@link #minLength(Long)}. 1641 * 1642 * @param value 1643 * The new value for this property. 1644 * @return This object. 1645 */ 1646 public Builder minl(Long value) { 1647 return minLength(value); 1648 } 1649 1650 /** 1651 * Synonym for {@link #minLength(String)}. 1652 * 1653 * @param value 1654 * The new value for this property. 1655 * @return This object. 1656 */ 1657 public Builder minl(String value) { 1658 return minLength(value); 1659 } 1660 1661 /** 1662 * <mk>minLength</mk> field. 1663 * 1664 * <p> 1665 * A string instance is valid against this keyword if its length is greater than, or equal to, the value of this keyword. 1666 * <br>The length of a string instance is defined as the number of its characters as defined by <a href='https://tools.ietf.org/html/rfc4627'>RFC 4627</a>. 1667 * 1668 * <p> 1669 * Only allowed for the following types: <js>"string"</js>. 1670 * 1671 * <p> 1672 * Applicable to the following Swagger schema objects: 1673 * <ul> 1674 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1675 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1676 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 1677 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1678 * </ul> 1679 * 1680 * @param value 1681 * The new value for this property. 1682 * <br>Ignored if value is <jk>null</jk> or <c>-1</c>. 1683 * @return This object. 1684 */ 1685 public Builder minLength(Long value) { 1686 minLength = resolve(value, minLength); 1687 return this; 1688 } 1689 1690 /** 1691 * <mk>minLength</mk> field. 1692 * 1693 * <p> 1694 * Same as {@link #minLength(Long)} but takes in a string number. 1695 * 1696 * @param value 1697 * The new value for this property. 1698 * <br>Ignored if value is <jk>null</jk> or empty. 1699 * @return This object. 1700 */ 1701 public Builder minLength(String value) { 1702 minLength = resolve(value, minLength); 1703 return this; 1704 } 1705 1706 /** 1707 * Synonym for {@link #minProperties(Long)}. 1708 * 1709 * @param value 1710 * The new value for this property. 1711 * @return This object. 1712 */ 1713 public Builder minp(Long value) { 1714 return minProperties(value); 1715 } 1716 1717 /** 1718 * Synonym for {@link #minProperties(String)}. 1719 * 1720 * @param value 1721 * The new value for this property. 1722 * @return This object. 1723 */ 1724 public Builder minp(String value) { 1725 return minProperties(value); 1726 } 1727 1728 /** 1729 * <mk>minProperties</mk> field. 1730 * 1731 * <p> 1732 * Applicable to the following Swagger schema objects: 1733 * <ul> 1734 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1735 * </ul> 1736 * 1737 * @param value 1738 * The new value for this property. 1739 * <br>Ignored if value is <jk>null</jk>. 1740 * @return This object. 1741 */ 1742 public Builder minProperties(Long value) { 1743 minProperties = resolve(value, minProperties); 1744 return this; 1745 } 1746 1747 /** 1748 * <mk>minProperties</mk> field. 1749 * 1750 * <p> 1751 * Same as {@link #minProperties(Long)} but takes in a string boolean. 1752 * 1753 * @param value 1754 * The new value for this property. 1755 * <br>Ignored if value is <jk>null</jk> or empty. 1756 * @return This object. 1757 */ 1758 public Builder minProperties(String value) { 1759 minProperties = resolve(value, minProperties); 1760 return this; 1761 } 1762 1763 /** 1764 * Synonym for {@link #multipleOf(Number)}. 1765 * 1766 * @param value 1767 * The new value for this property. 1768 * @return This object. 1769 */ 1770 public Builder mo(Number value) { 1771 return multipleOf(value); 1772 } 1773 1774 /** 1775 * <mk>multipleOf</mk> field. 1776 * 1777 * <p> 1778 * A numeric instance is valid if the result of the division of the instance by this keyword's value is an integer. 1779 * 1780 * <p> 1781 * Only allowed for the following types: <js>"integer"</js>, <js>"number"</js>. 1782 * 1783 * <p> 1784 * Applicable to the following Swagger schema objects: 1785 * <ul> 1786 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1787 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1788 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 1789 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1790 * </ul> 1791 * 1792 * @param value 1793 * The new value for this property. 1794 * <br>Ignored if value is <jk>null</jk>. 1795 * @return This object. 1796 */ 1797 public Builder multipleOf(Number value) { 1798 if (nn(value)) 1799 multipleOf = value; 1800 return this; 1801 } 1802 1803 /** 1804 * Synonym for {@link #name(String)}. 1805 * 1806 * @param value 1807 * The new value for this property. 1808 * @return This object. 1809 */ 1810 public Builder n(String value) { 1811 return name(value); 1812 } 1813 1814 /** 1815 * <mk>name</mk> field. 1816 * 1817 * <p> 1818 * Applicable to the following Swagger schema objects: 1819 * <ul> 1820 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1821 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1822 * </ul> 1823 * 1824 * @param value 1825 * The new value for this property. 1826 * @return This object. 1827 */ 1828 public Builder name(String value) { 1829 if (ine(value)) 1830 name = value; 1831 return this; 1832 } 1833 1834 /** 1835 * Disables Swagger schema usage validation checking. 1836 * 1837 * <p> 1838 * Shortcut for calling <code>noValidate(<jk>true</jk>);</code>. 1839 * 1840 * @return This object. 1841 */ 1842 public Builder noValidate() { 1843 return noValidate(true); 1844 } 1845 1846 /** 1847 * Disables Swagger schema usage validation checking. 1848 * 1849 * @param value Specify <jk>true</jk> to prevent {@link BasicRuntimeException} from being thrown if invalid Swagger usage was detected. 1850 * @return This object. 1851 */ 1852 public Builder noValidate(Boolean value) { 1853 if (nn(value)) 1854 noValidate = value; 1855 return this; 1856 } 1857 1858 /** 1859 * Synonym for {@link #pattern(String)}. 1860 * 1861 * @param value 1862 * The new value for this property. 1863 * @return This object. 1864 */ 1865 public Builder p(String value) { 1866 return pattern(value); 1867 } 1868 1869 /** 1870 * Shortcut for <c>property(key, value)</c>. 1871 * 1872 * <p> 1873 * Applicable to the following Swagger schema objects: 1874 * <ul> 1875 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1876 * </ul> 1877 * 1878 * @param key 1879 * The property name. 1880 * @param value 1881 * The new value for this property. 1882 * <br>Ignored if value is <jk>null</jk>. 1883 * @return This object. 1884 */ 1885 public Builder p(String key, Builder value) { 1886 return property(key, value); 1887 } 1888 1889 /** 1890 * Shortcut for <c>property(key, value)</c>. 1891 * 1892 * <p> 1893 * Applicable to the following Swagger schema objects: 1894 * <ul> 1895 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1896 * </ul> 1897 * 1898 * @param key 1899 * The property name. 1900 * @param value 1901 * The new value for this property. 1902 * <br>Ignored if value is <jk>null</jk>. 1903 * @return This object. 1904 */ 1905 public Builder p(String key, HttpPartSchema value) { 1906 return property(key, value); 1907 } 1908 1909 /** 1910 * Identifies the part parser to use for parsing this part. 1911 * 1912 * @param value 1913 * The new value for this property. 1914 * <br>Ignored if value is <jk>null</jk> or has the simple name "Void". 1915 * @return This object. 1916 */ 1917 public Builder parser(Class<?> value) { 1918 if (isNotVoid(value)) 1919 parser = value; 1920 return this; 1921 } 1922 1923 /** 1924 * <mk>pattern</mk> field. 1925 * 1926 * <p> 1927 * A string input is valid if it matches the specified regular expression pattern. 1928 * 1929 * <p> 1930 * Only allowed for the following types: <js>"string"</js>. 1931 * 1932 * <p> 1933 * Applicable to the following Swagger schema objects: 1934 * <ul> 1935 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 1936 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1937 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 1938 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 1939 * </ul> 1940 * 1941 * @param value 1942 * The new value for this property. 1943 * <br>Ignored if value is <jk>null</jk> or empty. 1944 * @return This object. 1945 */ 1946 public Builder pattern(String value) { 1947 try { 1948 if (ine(value)) 1949 this.pattern = Pattern.compile(value); 1950 } catch (Exception e) { 1951 throw new BasicRuntimeException(e, "Invalid value %s passed in as pattern value. Must be a valid regular expression.", value); 1952 } 1953 return this; 1954 } 1955 1956 /** 1957 * <mk>properties</mk> field. 1958 * 1959 * <p> 1960 * Applicable to the following Swagger schema objects: 1961 * <ul> 1962 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1963 * </ul> 1964 * 1965 * @param key 1966 * The property name. 1967 * @param value 1968 * The new value for this property. 1969 * <br>Ignored if value is <jk>null</jk>. 1970 * @return This object. 1971 */ 1972 public Builder property(String key, Builder value) { 1973 if (nn(key) && nn(value)) { 1974 if (properties == null) 1975 properties = map(); 1976 properties.put(key, value); 1977 } 1978 return this; 1979 } 1980 1981 /** 1982 * <mk>properties</mk> field. 1983 * 1984 * <p> 1985 * Applicable to the following Swagger schema objects: 1986 * <ul> 1987 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 1988 * </ul> 1989 * 1990 * @param key 1991 * The property name. 1992 * @param value 1993 * The new value for this property. 1994 * <br>Ignored if value is <jk>null</jk>. 1995 * @return This object. 1996 */ 1997 public Builder property(String key, HttpPartSchema value) { 1998 if (nn(key) && nn(value)) { 1999 if (properties == null) 2000 properties = map(); 2001 properties.put(key, value); 2002 } 2003 return this; 2004 } 2005 2006 /** 2007 * Synonym for {@link #required()}. 2008 * 2009 * @return This object. 2010 */ 2011 public Builder r() { 2012 return required(); 2013 } 2014 2015 /** 2016 * Synonym for {@link #required(Boolean)}. 2017 * 2018 * @param value 2019 * The new value for this property. 2020 * @return This object. 2021 */ 2022 public Builder r(Boolean value) { 2023 return required(value); 2024 } 2025 2026 /** 2027 * Synonym for {@link #required(String)}. 2028 * 2029 * @param value 2030 * The new value for this property. 2031 * @return This object. 2032 */ 2033 public Builder r(String value) { 2034 return required(value); 2035 } 2036 2037 /** 2038 * <mk>required</mk> field. 2039 * 2040 * <p> 2041 * Shortcut for calling <code>required(<jk>true</jk>);</code>. 2042 * 2043 * @return This object. 2044 */ 2045 public Builder required() { 2046 return required(true); 2047 } 2048 2049 /** 2050 * <mk>required</mk> field. 2051 * 2052 * <p> 2053 * Determines whether the parameter is mandatory. 2054 * 2055 * <p> 2056 * Applicable to the following Swagger schema objects: 2057 * <ul> 2058 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 2059 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 2060 * </ul> 2061 * 2062 * @param value 2063 * The new value for this property. 2064 * <br>Ignored if value is <jk>null</jk>. 2065 * @return This object. 2066 */ 2067 public Builder required(Boolean value) { 2068 required = resolve(value, required); 2069 return this; 2070 } 2071 2072 /** 2073 * <mk>required</mk> field. 2074 * 2075 * <p> 2076 * Determines whether the parameter is mandatory. 2077 * 2078 * <p> 2079 * Same as {@link #required(Boolean)} but takes in a boolean value as a string. 2080 * 2081 * @param value 2082 * The new value for this property. 2083 * <br>Ignored if value is <jk>null</jk> or empty. 2084 * @return This object. 2085 */ 2086 public Builder required(String value) { 2087 required = resolve(value, required); 2088 return this; 2089 } 2090 2091 /** 2092 * Identifies the part serializer to use for serializing this part. 2093 * 2094 * @param value 2095 * The new value for this property. 2096 * <br>Ignored if value is <jk>null</jk> or has the simple name "Void". 2097 * @return This object. 2098 */ 2099 public Builder serializer(Class<?> value) { 2100 if (isNotVoid(value)) 2101 serializer = value; 2102 return this; 2103 } 2104 2105 /** 2106 * Synonym for {@link #skipIfEmpty()}. 2107 * 2108 * @return This object. 2109 */ 2110 public Builder sie() { 2111 return skipIfEmpty(); 2112 } 2113 2114 /** 2115 * Synonym for {@link #skipIfEmpty(Boolean)}. 2116 * 2117 * @param value 2118 * The new value for this property. 2119 * @return This object. 2120 */ 2121 public Builder sie(Boolean value) { 2122 return skipIfEmpty(value); 2123 } 2124 2125 /** 2126 * Synonym for {@link #skipIfEmpty(String)}. 2127 * 2128 * @param value 2129 * The new value for this property. 2130 * @return This object. 2131 */ 2132 public Builder sie(String value) { 2133 return skipIfEmpty(value); 2134 } 2135 2136 /** 2137 * Identifies whether an item should be skipped if it's empty. 2138 * 2139 * <p> 2140 * Shortcut for calling <code>skipIfEmpty(<jk>true</jk>);</code>. 2141 * 2142 * @return This object. 2143 */ 2144 public Builder skipIfEmpty() { 2145 return skipIfEmpty(true); 2146 } 2147 2148 /** 2149 * <mk>skipIfEmpty</mk> field. 2150 * 2151 * <p> 2152 * Identifies whether an item should be skipped during serialization if it's empty. 2153 * 2154 * @param value 2155 * The new value for this property. 2156 * <br>Ignored if value is <jk>null</jk>. 2157 * @return This object. 2158 */ 2159 public Builder skipIfEmpty(Boolean value) { 2160 skipIfEmpty = resolve(value, skipIfEmpty); 2161 return this; 2162 } 2163 2164 /** 2165 * <mk>skipIfEmpty</mk> field. 2166 * 2167 * <p> 2168 * Same as {@link #skipIfEmpty(Boolean)} but takes in a string boolean. 2169 * 2170 * @param value 2171 * The new value for this property. 2172 * <br>Ignored if value is <jk>null</jk> or empty. 2173 * @return This object. 2174 */ 2175 public Builder skipIfEmpty(String value) { 2176 skipIfEmpty = resolve(value, skipIfEmpty); 2177 return this; 2178 } 2179 2180 /** 2181 * Synonym for {@link #type(HttpPartDataType)}. 2182 * 2183 * @param value 2184 * The new value for this property. 2185 * @return This object. 2186 */ 2187 public Builder t(HttpPartDataType value) { 2188 return type(value); 2189 } 2190 2191 /** 2192 * Synonym for {@link #type(String)}. 2193 * 2194 * @param value 2195 * The new value for this property. 2196 * @return This object. 2197 */ 2198 public Builder t(String value) { 2199 return type(value); 2200 } 2201 2202 /** 2203 * Shortcut for <c>type(HttpPartDataType.ARRAY)</c>. 2204 * 2205 * @return This object. 2206 */ 2207 public Builder tArray() { 2208 type = HttpPartDataType.ARRAY; 2209 return this; 2210 } 2211 2212 /** 2213 * Shortcut for <c>type(HttpPartDataType.BOOLEAN)</c>. 2214 * 2215 * @return This object. 2216 */ 2217 public Builder tBoolean() { 2218 type = HttpPartDataType.BOOLEAN; 2219 return this; 2220 } 2221 2222 /** 2223 * Shortcut for <c>type(HttpPartDataType.FILE)</c>. 2224 * 2225 * @return This object. 2226 */ 2227 public Builder tFile() { 2228 type = HttpPartDataType.FILE; 2229 return this; 2230 } 2231 2232 /** 2233 * Shortcut for <c>type(HttpPartDataType.INTEGER)</c>. 2234 * 2235 * @return This object. 2236 */ 2237 public Builder tInteger() { 2238 type = HttpPartDataType.INTEGER; 2239 return this; 2240 } 2241 2242 /** 2243 * Shortcut for <c>type(HttpPartDataType.NO_TYPE)</c>. 2244 * 2245 * @return This object. 2246 */ 2247 public Builder tNone() { 2248 type = HttpPartDataType.NO_TYPE; 2249 return this; 2250 } 2251 2252 /** 2253 * Shortcut for <c>type(HttpPartDataType.NUMBER)</c>. 2254 * 2255 * @return This object. 2256 */ 2257 public Builder tNumber() { 2258 type = HttpPartDataType.NUMBER; 2259 return this; 2260 } 2261 2262 /** 2263 * Shortcut for <c>type(HttpPartDataType.OBJECT)</c>. 2264 * 2265 * @return This object. 2266 */ 2267 public Builder tObject() { 2268 type = HttpPartDataType.OBJECT; 2269 return this; 2270 } 2271 2272 /** 2273 * Shortcut for <c>type(HttpPartDataType.STRING)</c>. 2274 * 2275 * @return This object. 2276 */ 2277 public Builder tString() { 2278 type = HttpPartDataType.STRING; 2279 return this; 2280 } 2281 2282 /** 2283 * <mk>type</mk> field. 2284 * 2285 * <p> 2286 * The type of the parameter. 2287 * 2288 * <p> 2289 * If the type is not specified, it will be auto-detected based on the parameter class type. 2290 * 2291 * <p> 2292 * Applicable to the following Swagger schema objects: 2293 * <ul> 2294 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 2295 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 2296 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 2297 * <li><a class="doclink" href="https://swagger.io/specification/v2#securitySchemeObject">SecurityScheme</a> 2298 * </ul> 2299 * 2300 * <ul class='values javatree'> 2301 * <li class='jc'>{@link HttpPartDataType} 2302 * <ul> 2303 * <li class='jf'> 2304 * {@link HttpPartDataType#STRING STRING} 2305 * <br>Parameter must be a string or a POJO convertible from a string. 2306 * <li> 2307 * {@link HttpPartDataType#NUMBER NUMBER} 2308 * <br>Parameter must be a number primitive or number object. 2309 * <br>If parameter is <c>Object</c>, creates either a <c>Float</c> or <c>Double</c> depending on the size of the number. 2310 * <li class='jf'> 2311 * {@link HttpPartDataType#INTEGER INTEGER} 2312 * <br>Parameter must be a integer/long primitive or integer/long object. 2313 * <br>If parameter is <c>Object</c>, creates either a <c>Short</c>, <c>Integer</c>, or <c>Long</c> depending on the size of the number. 2314 * <li class='jf'> 2315 * {@link HttpPartDataType#BOOLEAN BOOLEAN} 2316 * <br>Parameter must be a boolean primitive or object. 2317 * <li class='jf'> 2318 * {@link HttpPartDataType#ARRAY ARRAY} 2319 * <br>Parameter must be an array or collection. 2320 * <br>Elements must be strings or POJOs convertible from strings. 2321 * <br>If parameter is <c>Object</c>, creates an {@link JsonList}. 2322 * <li class='jf'> 2323 * {@link HttpPartDataType#OBJECT OBJECT} 2324 * <br>Parameter must be a map or bean. 2325 * <br>If parameter is <c>Object</c>, creates a map. 2326 * <br>Note that this is an extension of the OpenAPI schema as Juneau allows for arbitrarily-complex POJOs to be serialized as HTTP parts. 2327 * <li class='jf'> 2328 * {@link HttpPartDataType#FILE FILE} 2329 * <br>This type is currently not supported. 2330 * </ul> 2331 * </ul> 2332 * 2333 * <h5 class='section'>See Also:</h5><ul> 2334 * <li class='extlink'><a class="doclink" href="https://swagger.io/specification#dataTypes">Swagger Data Types</a> 2335 * </ul> 2336 * 2337 * @param value 2338 * The new value for this property. 2339 * @return This object. 2340 */ 2341 public Builder type(HttpPartDataType value) { 2342 // Guard null for parity with the String overload (which ine()-guards); a null must not overwrite the field. 2343 if (value != null) 2344 type = value; 2345 return this; 2346 } 2347 2348 /** 2349 * <mk>type</mk> field. 2350 * 2351 * <p> 2352 * The type of the parameter. 2353 * 2354 * <p> 2355 * If the type is not specified, it will be auto-detected based on the parameter class type. 2356 * 2357 * <p> 2358 * Applicable to the following Swagger schema objects: 2359 * <ul> 2360 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 2361 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 2362 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 2363 * <li><a class="doclink" href="https://swagger.io/specification/v2#securitySchemeObject">SecurityScheme</a> 2364 * </ul> 2365 * 2366 * <ul class='values'> 2367 * <li> 2368 * <js>"string"</js> 2369 * <br>Parameter must be a string or a POJO convertible from a string. 2370 * <li> 2371 * <js>"number"</js> 2372 * <br>Parameter must be a number primitive or number object. 2373 * <br>If parameter is <c>Object</c>, creates either a <c>Float</c> or <c>Double</c> depending on the size of the number. 2374 * <li> 2375 * <js>"integer"</js> 2376 * <br>Parameter must be a integer/long primitive or integer/long object. 2377 * <br>If parameter is <c>Object</c>, creates either a <c>Short</c>, <c>Integer</c>, or <c>Long</c> depending on the size of the number. 2378 * <li> 2379 * <js>"boolean"</js> 2380 * <br>Parameter must be a boolean primitive or object. 2381 * <li> 2382 * <js>"array"</js> 2383 * <br>Parameter must be an array or collection. 2384 * <br>Elements must be strings or POJOs convertible from strings. 2385 * <br>If parameter is <c>Object</c>, creates an {@link JsonList}. 2386 * <li> 2387 * <js>"object"</js> 2388 * <br>Parameter must be a map or bean. 2389 * <br>If parameter is <c>Object</c>, creates a map. 2390 * <br>Note that this is an extension of the OpenAPI schema as Juneau allows for arbitrarily-complex POJOs to be serialized as HTTP parts. 2391 * <li> 2392 * <js>"file"</js> 2393 * <br>This type is currently not supported. 2394 * </ul> 2395 * 2396 * <h5 class='section'>See Also:</h5><ul> 2397 * <li class='extlink'><a class="doclink" href="https://swagger.io/specification#dataTypes">Swagger Data Types</a> 2398 * </ul> 2399 * 2400 * @param value 2401 * The new value for this property. 2402 * <br>Ignored if value is <jk>null</jk> or empty. 2403 * @return This object. 2404 */ 2405 public Builder type(String value) { 2406 try { 2407 if (ine(value)) 2408 type = HttpPartDataType.fromString(value); 2409 } catch (Exception e) { 2410 throw new BasicRuntimeException(e, "Invalid value '%s' passed in as type value. Valid values: %s", value, HttpPartDataType.values()); 2411 } 2412 return this; 2413 } 2414 2415 /** 2416 * Synonym for {@link #uniqueItems()}. 2417 * 2418 * @return This object. 2419 */ 2420 public Builder ui() { 2421 return uniqueItems(); 2422 } 2423 2424 /** 2425 * Synonym for {@link #uniqueItems(Boolean)}. 2426 * 2427 * @param value 2428 * The new value for this property. 2429 * @return This object. 2430 */ 2431 public Builder ui(Boolean value) { 2432 return uniqueItems(value); 2433 } 2434 2435 /** 2436 * Synonym for {@link #uniqueItems(String)}. 2437 * 2438 * @param value 2439 * The new value for this property. 2440 * @return This object. 2441 */ 2442 public Builder ui(String value) { 2443 return uniqueItems(value); 2444 } 2445 2446 /** 2447 * <mk>uniqueItems</mk> field. 2448 * 2449 * <p> 2450 * Shortcut for calling <code>uniqueItems(<jk>true</jk>);</code>. 2451 * 2452 * @return This object. 2453 */ 2454 public Builder uniqueItems() { 2455 return uniqueItems(true); 2456 } 2457 2458 /** 2459 * <mk>uniqueItems</mk> field. 2460 * 2461 * <p> 2462 * If <jk>true</jk>, the input validates successfully if all of its elements are unique. 2463 * 2464 * <p> 2465 * <br>If the parameter type is a subclass of {@link Set}, this validation is skipped (since a set can only contain unique items anyway). 2466 * <br>Otherwise, the collection or array is checked for duplicate items. 2467 * 2468 * <p> 2469 * Only allowed for the following types: <js>"array"</js>. 2470 * 2471 * <p> 2472 * Applicable to the following Swagger schema objects: 2473 * <ul> 2474 * <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a> 2475 * <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a> 2476 * <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a> 2477 * <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a> 2478 * </ul> 2479 * 2480 * @param value 2481 * The new value for this property. 2482 * <br>Ignored if value is <jk>null</jk>. 2483 * @return This object. 2484 */ 2485 public Builder uniqueItems(Boolean value) { 2486 uniqueItems = resolve(value, uniqueItems); 2487 return this; 2488 } 2489 2490 /** 2491 * <mk>uniqueItems</mk> field. 2492 * 2493 * <p> 2494 * Same as {@link #uniqueItems(Boolean)} but takes in a string boolean. 2495 * 2496 * @param value 2497 * The new value for this property. 2498 * <br>Ignored if value is <jk>null</jk> or empty.. 2499 * @return This object. 2500 */ 2501 public Builder uniqueItems(String value) { 2502 uniqueItems = resolve(value, uniqueItems); 2503 return this; 2504 } 2505 2506 private static Long firstNmo(Long...l) { 2507 for (var ll : l) 2508 if (nn(ll) && ll != -1) 2509 return ll; 2510 return null; 2511 } 2512 2513 /** 2514 * Helper method to safely get annotation attribute values via reflection. 2515 * 2516 * <p> 2517 * This allows reading Jakarta Validation annotations without requiring them on the classpath. 2518 * 2519 * @param <T> The expected return type. 2520 * @param a The annotation to read from. 2521 * @param attributeName The attribute name to read. 2522 * @param type The expected type of the attribute value. 2523 * @return The attribute value, or <jk>null</jk> if not found or not of the expected type. 2524 */ 2525 private static <T> T getAnnotationValue(Annotation a, String attributeName, Class<T> type) { 2526 try { 2527 Method m = a.annotationType().getDeclaredMethod(attributeName); 2528 Object value = m.invoke(a); 2529 return type.isInstance(value) ? type.cast(value) : null; 2530 } catch (@SuppressWarnings("unused") Exception e) { 2531 return null; 2532 } 2533 } 2534 2535 private static String joinnlOrNull(String[]...s) { 2536 for (var ss : s) 2537 if (ss.length > 0) 2538 return StringUtils.joinnl(ss); 2539 return null; 2540 } 2541 2542 private static Boolean resolve(Boolean newValue, Boolean oldValue) { 2543 return newValue == null ? oldValue : newValue; 2544 } 2545 2546 private static Long resolve(Long newValue, Long oldValue) { 2547 return (newValue == null || newValue == -1) ? oldValue : newValue; 2548 } 2549 2550 private static Boolean resolve(String newValue, Boolean oldValue) { 2551 return isEmpty(newValue) ? oldValue : b(newValue); 2552 } 2553 2554 private static Long resolve(String newValue, Long oldValue) { 2555 return isEmpty(newValue) ? oldValue : Long.parseLong(newValue); 2556 } 2557 2558 private static Number toNumber(String...s) { 2559 return HttpPartSchema.toNumber(s); 2560 } 2561 2562 private static Set<String> toSet(String[]...s) { 2563 return HttpPartSchema.toSet(s); 2564 } 2565 2566 Builder apply(Class<? extends Annotation> c, Type t) { 2567 if (t instanceof Class<?> c2) { 2568 rstream(AP.find(c, info(c2))).forEach(x -> apply(x.inner())); 2569 } else if (Holder.isType(t)) { 2570 apply(c, Holder.getParameterType(t)); 2571 } 2572 return this; 2573 } 2574 2575 Builder apply(Class<? extends Annotation> c, Method m) { 2576 apply(c, m.getGenericReturnType()); 2577 Annotation a = m.getAnnotation(c); 2578 if (nn(a)) 2579 return apply(a); 2580 return this; 2581 } 2582 2583 Builder apply(Class<? extends Annotation> c, ParameterInfo mpi) { 2584 apply(c, mpi.getParameterType().innerType()); 2585 mpi.getAnnotations(c).forEach(x -> apply(x.inner())); 2586 return this; 2587 } 2588 2589 Builder apply(HttpPartMarshalling a) { 2590 parser(a.parser()); 2591 serializer(a.serializer()); 2592 return this; 2593 } 2594 2595 private Builder applyHttpAnnotation(Annotation a) { 2596 var annotationName = a.annotationType().getSimpleName(); 2597 2598 var schema = getAnnotationValue(a, "schema", Schema.class); 2599 if (schema != null && (eq(annotationName, "Response") || !SchemaAnnotation.empty(schema))) 2600 apply(schema); 2601 2602 var aName = getAnnotationString(a, "name"); 2603 var aValue = getAnnotationString(a, "value"); 2604 2605 switch (annotationName) { 2606 case "PathRemainder" -> { 2607 name("/*"); 2608 default_(getAnnotationString(a, "def")); 2609 allowEmptyValue(); 2610 required(false); 2611 } 2612 case "Path" -> { 2613 name(firstNonEmpty(aName, aValue)); 2614 var def = getAnnotationString(a, "def"); 2615 if (def != null && neq(NONE, def)) 2616 default_ = def; 2617 if (startsWith(name, '/')) { 2618 allowEmptyValue(); 2619 required(false); 2620 } else if (required == null) { 2621 required(def == null || eq(NONE, def)); 2622 } 2623 } 2624 case "Response" -> { 2625 allowEmptyValue(true); 2626 required(false); 2627 } 2628 case "StatusCode" -> codes(getAnnotationIntArray(a, "value")); 2629 default -> { 2630 name(firstNonEmpty(aName, aValue)); 2631 default_(getAnnotationString(a, "def")); 2632 } 2633 } 2634 return this; 2635 } 2636 2637 private static String getAnnotationString(Annotation a, String methodName) { 2638 try { 2639 var m = a.annotationType().getMethod(methodName); 2640 if (m.getReturnType() == String.class) 2641 return (String) m.invoke(a); 2642 } catch (@SuppressWarnings("unused") NoSuchMethodException e) { 2643 // Attribute doesn't exist on this annotation -- ignore 2644 } catch (Exception e) { 2645 throw rex(e); 2646 } 2647 return null; 2648 } 2649 2650 private static int[] getAnnotationIntArray(Annotation a, String methodName) { 2651 try { 2652 var m = a.annotationType().getMethod(methodName); 2653 if (m.getReturnType() == int[].class) { 2654 var arr = (int[]) m.invoke(a); 2655 return arr == null ? new int[0] : arr; 2656 } 2657 } catch (@SuppressWarnings("unused") NoSuchMethodException e) { 2658 // Attribute doesn't exist on this annotation -- ignore 2659 } catch (Exception e) { 2660 throw rex(e); 2661 } 2662 return new int[0]; 2663 } 2664 2665 Builder apply(Items a) { 2666 default_(joinnlOrNull(a.default_(), a.df())); 2667 enum_(toSet(a.enum_(), a.e())); 2668 collectionFormat(firstNonEmpty(a.collectionFormat(), a.cf())); 2669 exclusiveMaximum(a.exclusiveMaximum() || a.emax()); 2670 exclusiveMinimum(a.exclusiveMinimum() || a.emin()); 2671 format(firstNonEmpty(a.format(), a.f())); 2672 items(a.items()); 2673 maximum(toNumber(a.maximum(), a.max())); 2674 maxItems(firstNmo(a.maxItems(), a.maxi())); 2675 maxLength(firstNmo(a.maxLength(), a.maxl())); 2676 minimum(toNumber(a.minimum(), a.min())); 2677 minItems(firstNmo(a.minItems(), a.mini())); 2678 minLength(firstNmo(a.minLength(), a.minl())); 2679 multipleOf(toNumber(a.multipleOf(), a.mo())); 2680 pattern(firstNonEmpty(a.pattern(), a.p())); 2681 type(firstNonEmpty(a.type(), a.t())); 2682 uniqueItems(a.uniqueItems() || a.ui()); 2683 return this; 2684 } 2685 2686 // ----------------------------------------------------------------------------------------------------------------- 2687 // JSON Schema Draft 2020-12 property setters 2688 // ----------------------------------------------------------------------------------------------------------------- 2689 2690 // ----------------------------------------------------------------------------------------------------------------- 2691 // Other 2692 // ----------------------------------------------------------------------------------------------------------------- 2693 2694 Builder apply(Schema a) { 2695 default_(joinnlOrNull(a.default_(), a.df())); 2696 enum_(toSet(a.enum_(), a.e())); 2697 allowEmptyValue(a.allowEmptyValue() || a.aev()); 2698 collectionFormat(firstNonEmpty(a.collectionFormat(), a.cf())); 2699 2700 // Handle exclusiveMaximum with fallback from Draft 2020-12 to Draft 04 2701 String exMaxVal = a.exclusiveMaximumValue(); 2702 if (ine(exMaxVal)) { 2703 exclusiveMaximumValue(toNumber(exMaxVal)); 2704 } else if (a.emax()) { 2705 exclusiveMaximum(true); 2706 } 2707 2708 // Handle exclusiveMinimum with fallback from Draft 2020-12 to Draft 04 2709 String exMinVal = a.exclusiveMinimumValue(); 2710 if (ine(exMinVal)) { 2711 exclusiveMinimumValue(toNumber(exMinVal)); 2712 } else if (a.emin()) { 2713 exclusiveMinimum(true); 2714 } 2715 2716 format(firstNonEmpty(a.format(), a.f())); 2717 items(a.items()); 2718 maximum(toNumber(a.maximum(), a.max())); 2719 maxItems(firstNmo(a.maxItems(), a.maxi())); 2720 maxLength(firstNmo(a.maxLength(), a.maxl())); 2721 maxProperties(firstNmo(a.maxProperties(), a.maxp())); 2722 minimum(toNumber(a.minimum(), a.min())); 2723 minItems(firstNmo(a.minItems(), a.mini())); 2724 minLength(firstNmo(a.minLength(), a.minl())); 2725 minProperties(firstNmo(a.minProperties(), a.minp())); 2726 multipleOf(toNumber(a.multipleOf(), a.mo())); 2727 pattern(firstNonEmpty(a.pattern(), a.p())); 2728 required(a.required() || a.r()); 2729 skipIfEmpty(a.skipIfEmpty() || a.sie()); 2730 type(firstNonEmpty(a.type(), a.t())); 2731 uniqueItems(a.uniqueItems() || a.ui()); 2732 2733 // JSON Schema Draft 2020-12 properties 2734 const_(joinnlOrNull(a.const_())); 2735 examples(a.examples()); 2736 deprecated(a.deprecated_()); 2737 2738 return this; 2739 } 2740 2741 Builder apply(SubItems a) { 2742 default_(joinnlOrNull(a.default_(), a.df())); 2743 enum_(toSet(a.enum_(), a.e())); 2744 collectionFormat(firstNonEmpty(a.collectionFormat(), a.cf())); 2745 exclusiveMaximum(a.exclusiveMaximum() || a.emax()); 2746 exclusiveMinimum(a.exclusiveMinimum() || a.emin()); 2747 format(firstNonEmpty(a.format(), a.f())); 2748 maximum(toNumber(a.maximum(), a.max())); 2749 maxItems(firstNmo(a.maxItems(), a.maxi())); 2750 maxLength(firstNmo(a.maxLength(), a.maxl())); 2751 minimum(toNumber(a.minimum(), a.min())); 2752 minItems(firstNmo(a.minItems(), a.mini())); 2753 minLength(firstNmo(a.minLength(), a.minl())); 2754 multipleOf(toNumber(a.multipleOf(), a.mo())); 2755 pattern(firstNonEmpty(a.pattern(), a.p())); 2756 type(firstNonEmpty(a.type(), a.t())); 2757 uniqueItems(a.uniqueItems() || a.ui()); 2758 return this; 2759 } 2760 2761 public Builder applyAll(Class<? extends Annotation> c, Type t) { 2762 return apply(Schema.class, t).apply(c, t); 2763 } 2764 2765 public Builder applyAll(Class<? extends Annotation> c, Method m) { 2766 return apply(Schema.class, m).apply(c, m); 2767 } 2768 2769 public Builder applyAll(Class<? extends Annotation> c, ParameterInfo mpi) { 2770 return apply(Schema.class, mpi).apply(c, mpi); 2771 } 2772 2773 /** 2774 * Apply Jakarta Bean Validation constraints to this schema. 2775 * 2776 * <p> 2777 * This method uses pure reflection to read constraint annotations, so no jakarta.validation-api 2778 * dependency is required. The constraints are mapped to OpenAPI schema properties where applicable. 2779 * 2780 * <p> 2781 * Supported constraints: 2782 * <ul> 2783 * <li><c>@NotNull</c> → <c>required(true)</c> 2784 * <li><c>@Size</c> → <c>minLength/maxLength</c> or <c>minItems/maxItems</c> 2785 * <li><c>@Min</c> → <c>minimum</c> 2786 * <li><c>@Max</c> → <c>maximum</c> 2787 * <li><c>@DecimalMin</c> → <c>minimum + exclusiveMinimum</c> 2788 * <li><c>@DecimalMax</c> → <c>maximum + exclusiveMaximum</c> 2789 * <li><c>@Pattern</c> → <c>pattern</c> 2790 * <li><c>@Email</c> → <c>format("email")</c> 2791 * <li><c>@Positive</c> → <c>minimum(0) + exclusiveMinimum(true)</c> 2792 * <li><c>@PositiveOrZero</c> → <c>minimum(0)</c> 2793 * <li><c>@Negative</c> → <c>maximum(0) + exclusiveMaximum(true)</c> 2794 * <li><c>@NegativeOrZero</c> → <c>maximum(0)</c> 2795 * <li><c>@NotEmpty</c> → <c>required(true) + minLength(1)/minItems(1)</c> 2796 * <li><c>@NotBlank</c> → <c>required(true) + minLength(1) + pattern</c> 2797 * </ul> 2798 * 2799 * @param a The Jakarta Validation constraint annotation. 2800 * @return This object. 2801 * @since 9.2.0 2802 */ 2803 Builder applyJakartaValidation(Annotation a) { 2804 String simpleName = cns(a.annotationType()); 2805 2806 try { 2807 switch (simpleName) { 2808 case "NotNull": 2809 required(true); 2810 break; 2811 case "Size": 2812 Integer min = getAnnotationValue(a, "min", Integer.class); 2813 Integer max = getAnnotationValue(a, "max", Integer.class); 2814 if (nn(min) && min > 0) { 2815 minLength(min.longValue()); 2816 minItems(min.longValue()); 2817 } 2818 if (nn(max) && max < Integer.MAX_VALUE) { 2819 maxLength(max.longValue()); 2820 maxItems(max.longValue()); 2821 } 2822 break; 2823 case "Min": 2824 Long minValue = getAnnotationValue(a, "value", Long.class); 2825 if (nn(minValue)) 2826 minimum(minValue); 2827 break; 2828 case "Max": 2829 Long maxValue = getAnnotationValue(a, "value", Long.class); 2830 if (nn(maxValue)) 2831 maximum(maxValue); 2832 break; 2833 case "Pattern": 2834 String regexp = getAnnotationValue(a, "regexp", String.class); 2835 if (nn(regexp)) 2836 pattern(regexp); 2837 break; 2838 case "Email": 2839 format("email"); 2840 break; 2841 case "Positive": 2842 minimum(0); 2843 exclusiveMinimum(true); 2844 break; 2845 case "PositiveOrZero": 2846 minimum(0); 2847 break; 2848 case "Negative": 2849 maximum(0); 2850 exclusiveMaximum(true); 2851 break; 2852 case "NegativeOrZero": 2853 maximum(0); 2854 break; 2855 case "NotEmpty": 2856 required(true); 2857 minLength(1L); 2858 minItems(1L); 2859 break; 2860 case "NotBlank": 2861 required(true); 2862 minLength(1L); 2863 pattern(".*\\S.*"); // Contains at least one non-whitespace character 2864 break; 2865 case "DecimalMin": 2866 String minVal = getAnnotationValue(a, "value", String.class); 2867 Boolean minInclusive = getAnnotationValue(a, "inclusive", Boolean.class); 2868 if (nn(minVal)) { 2869 minimum(toNumber(minVal)); 2870 if (isFalse(minInclusive)) 2871 exclusiveMinimum(true); 2872 } 2873 break; 2874 case "DecimalMax": 2875 String maxVal = getAnnotationValue(a, "value", String.class); 2876 Boolean maxInclusive = getAnnotationValue(a, "inclusive", Boolean.class); 2877 if (nn(maxVal)) { 2878 maximum(toNumber(maxVal)); 2879 if (isFalse(maxInclusive)) 2880 exclusiveMaximum(true); 2881 } 2882 break; 2883 default: 2884 // Silently ignore other validation annotations we don't support yet 2885 } 2886 } catch (@SuppressWarnings("unused") Exception e) { 2887 // If reflection fails, just skip this annotation - it's optional 2888 } 2889 return this; 2890 } 2891 2892 Builder items(Items value) { 2893 if (! ItemsAnnotation.empty(value)) 2894 items = HttpPartSchema.create().apply(value); 2895 return this; 2896 } 2897 2898 Builder items(SubItems value) { 2899 if (! SubItemsAnnotation.empty(value)) 2900 items = HttpPartSchema.create().apply(value); 2901 return this; 2902 } 2903 } 2904 2905 /** Reusable instance of this object, all default settings. */ 2906 public static final HttpPartSchema DEFAULT = HttpPartSchema.create().allowEmptyValue(true).build(); 2907 2908 /** Boolean type */ 2909 public static final HttpPartSchema T_BOOLEAN = HttpPartSchema.tBoolean().build(); 2910 2911 /** File type */ 2912 public static final HttpPartSchema T_FILE = HttpPartSchema.tFile().build(); 2913 2914 /** Integer type */ 2915 public static final HttpPartSchema T_INTEGER = HttpPartSchema.tInteger().build(); 2916 2917 /** Int32 type */ 2918 public static final HttpPartSchema T_INT32 = HttpPartSchema.tInt32().build(); 2919 2920 /** Int64 type */ 2921 public static final HttpPartSchema T_INT64 = HttpPartSchema.tInt64().build(); 2922 2923 /** No type */ 2924 public static final HttpPartSchema T_NONE = HttpPartSchema.tNone().build(); 2925 2926 /** Number type */ 2927 public static final HttpPartSchema T_NUMBER = HttpPartSchema.tNumber().build(); 2928 2929 /** Float type */ 2930 public static final HttpPartSchema T_FLOAT = HttpPartSchema.tFloat().build(); 2931 2932 /** Double type */ 2933 public static final HttpPartSchema T_DOUBLE = HttpPartSchema.tDouble().build(); 2934 2935 /** String type */ 2936 public static final HttpPartSchema T_STRING = HttpPartSchema.tString().build(); 2937 2938 /** Byte type */ 2939 public static final HttpPartSchema T_BYTE = HttpPartSchema.tByte().build(); 2940 2941 /** Binary type */ 2942 public static final HttpPartSchema T_BINARY = HttpPartSchema.tBinary().build(); 2943 2944 /** Spaced binary type */ 2945 public static final HttpPartSchema T_BINARY_SPACED = HttpPartSchema.tBinarySpaced().build(); 2946 2947 /** Date type */ 2948 public static final HttpPartSchema T_DATE = HttpPartSchema.tDate().build(); 2949 2950 /** Date-time type */ 2951 public static final HttpPartSchema T_DATETIME = HttpPartSchema.tDateTime().build(); 2952 2953 /** UON-formated simple type */ 2954 public static final HttpPartSchema T_UON = HttpPartSchema.tUon().build(); 2955 2956 /** Array type */ 2957 public static final HttpPartSchema T_ARRAY = HttpPartSchema.tArray().build(); 2958 2959 /** Comma-delimited array type */ 2960 public static final HttpPartSchema T_ARRAY_CSV = HttpPartSchema.tArrayCsv().build(); 2961 2962 /** Pipe-delimited array type */ 2963 public static final HttpPartSchema T_ARRAY_PIPES = HttpPartSchema.tArrayPipes().build(); 2964 2965 /** Space-delimited array type */ 2966 public static final HttpPartSchema T_ARRAY_SSV = HttpPartSchema.tArraySsv().build(); 2967 2968 /** Tab-delimited array type */ 2969 public static final HttpPartSchema T_ARRAY_TSV = HttpPartSchema.tArrayTsv().build(); 2970 2971 /** UON-formatted array type */ 2972 public static final HttpPartSchema T_ARRAY_UON = HttpPartSchema.tArrayUon().build(); 2973 2974 /** Multi-part array type */ 2975 public static final HttpPartSchema T_ARRAY_MULTI = HttpPartSchema.tArrayMulti().build(); 2976 2977 /** Object type */ 2978 public static final HttpPartSchema T_OBJECT = HttpPartSchema.tObject().build(); 2979 2980 /** UON-formated object type */ 2981 public static final HttpPartSchema T_OBJECT_UON = HttpPartSchema.tObjectUon().build(); 2982 2983 /** 2984 * Instantiates a new builder for this object. 2985 * 2986 * @return A new builder for this object. 2987 */ 2988 public static Builder create() { 2989 return new Builder(); 2990 } 2991 2992 /** 2993 * Finds the schema information on the specified annotation. 2994 * 2995 * @param a 2996 * The annotation to find the schema information on.. 2997 * <br>Must not be <jk>null</jk>. 2998 * @return The schema information found on the annotation. Never <jk>null</jk>. 2999 */ 3000 public static HttpPartSchema create(Annotation a) { 3001 return create().apply(a).build(); 3002 } 3003 3004 /** 3005 * Finds the schema information on the specified annotation. 3006 * 3007 * @param a 3008 * The annotation to find the schema information on.. 3009 * <br>Must not be <jk>null</jk>. 3010 * @param defaultName The default part name if not specified on the annotation. Can be <jk>null</jk> (the name from the annotation, if any, is left unchanged). 3011 * @return The schema information found on the annotation. Never <jk>null</jk>. 3012 */ 3013 public static HttpPartSchema create(Annotation a, String defaultName) { 3014 return create().name(defaultName).apply(a).build(); 3015 } 3016 3017 /** 3018 * Finds the schema information for the specified class. 3019 * 3020 * <p> 3021 * This method will gather all the schema information from the annotations on the class and all parent classes/interfaces. 3022 * 3023 * @param c 3024 * The annotation to look for. 3025 * <br>Valid values: 3026 * <ul> 3027 * <li><ja>@Content</ja> 3028 * <li><ja>@Header</ja> 3029 * <li><ja>@Query</ja> 3030 * <li><ja>@FormData</ja> 3031 * <li><ja>@Path</ja> 3032 * <li><ja>@Response</ja> 3033 * <li><ja>@HasQuery</ja> 3034 * <li><ja>@HasFormData</ja> 3035 * </ul> 3036 * @param t 3037 * The class containing the parameter. 3038 * <br>Can be <jk>null</jk> (no type-level annotations are applied). 3039 * @return The schema information about the parameter. Never <jk>null</jk>. 3040 */ 3041 public static HttpPartSchema create(Class<? extends Annotation> c, Type t) { 3042 return create().applyAll(c, t).build(); 3043 } 3044 3045 /** 3046 * Finds the schema information for the specified method return. 3047 * 3048 * <p> 3049 * This method will gather all the schema information from the annotations at the following locations: 3050 * <ul> 3051 * <li>The method. 3052 * <li>The method return class. 3053 * <li>The method return parent classes and interfaces. 3054 * </ul> 3055 * 3056 * @param c 3057 * The annotation to look for. 3058 * <br>Valid values: 3059 * <ul> 3060 * <li><ja>@Content</ja> 3061 * <li><ja>@Header</ja> 3062 * <li><ja>@Query</ja> 3063 * <li><ja>@FormData</ja> 3064 * <li><ja>@Path</ja> 3065 * <li><ja>@Response</ja> 3066 * <li><ja>@HasQuery</ja> 3067 * <li><ja>@HasFormData</ja> 3068 * </ul> 3069 * @param m 3070 * The Java method with the return type being checked. 3071 * <br>Must not be <jk>null</jk>. 3072 * @return The schema information about the parameter. Never <jk>null</jk>. 3073 */ 3074 public static HttpPartSchema create(Class<? extends Annotation> c, Method m) { 3075 return create().applyAll(c, m).build(); 3076 } 3077 3078 /** 3079 * Finds the schema information for the specified method parameter. 3080 * 3081 * <p> 3082 * This method will gather all the schema information from the annotations at the following locations: 3083 * <ul> 3084 * <li>The method parameter. 3085 * <li>The method parameter class. 3086 * <li>The method parameter parent classes and interfaces. 3087 * </ul> 3088 * 3089 * @param c 3090 * The annotation to look for. 3091 * <br>Valid values: 3092 * <ul> 3093 * <li><ja>@Content</ja> 3094 * <li><ja>@Header</ja> 3095 * <li><ja>@Query</ja> 3096 * <li><ja>@FormData</ja> 3097 * <li><ja>@Path</ja> 3098 * <li><ja>@Response</ja> 3099 * <li><ja>@HasQuery</ja> 3100 * <li><ja>@HasFormData</ja> 3101 * </ul> 3102 * @param mpi The Java method parameter. Must not be <jk>null</jk>. 3103 * @return The schema information about the parameter. Never <jk>null</jk>. 3104 */ 3105 public static HttpPartSchema create(Class<? extends Annotation> c, ParameterInfo mpi) { 3106 return create().applyAll(c, mpi).build(); 3107 } 3108 3109 /** 3110 * Shortcut for calling <c>create().type(type);</c> 3111 * 3112 * @param type The schema type value. 3113 * @return A new builder. 3114 */ 3115 public static Builder create(String type) { 3116 return create().type(type); 3117 } 3118 3119 /** 3120 * Shortcut for calling <c>create().type(type).format(format);</c> 3121 * 3122 * @param type The schema type value. 3123 * @param format The schema format value. 3124 * @return A new builder. 3125 */ 3126 public static Builder create(String type, String format) { 3127 return create().type(type).format(format); 3128 } 3129 3130 /** 3131 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>)</c>. 3132 * 3133 * @return A new builder for this object. 3134 */ 3135 public static Builder tArray() { 3136 return create().tArray(); 3137 } 3138 3139 /** 3140 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).items(items)</c>. 3141 * 3142 * @param items The schema of the array items. 3143 * @return A new builder for this object. 3144 */ 3145 public static Builder tArray(Builder items) { 3146 return create().tArray().items(items); 3147 } 3148 3149 /** 3150 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>)</c>. 3151 * 3152 * @return A new builder for this object. 3153 */ 3154 public static Builder tArrayCsv() { 3155 return create().tArray().cfCsv(); 3156 } 3157 3158 /** 3159 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>).items(items)</c>. 3160 * 3161 * @param items The schema of the array items. 3162 * @return A new builder for this object. 3163 */ 3164 public static Builder tArrayCsv(Builder items) { 3165 return create().tArray().cfCsv().items(items); 3166 } 3167 3168 /** 3169 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>MULTI</jsf>)</c>. 3170 * 3171 * @return A new builder for this object. 3172 */ 3173 public static Builder tArrayMulti() { 3174 return create().tArray().cfMulti(); 3175 } 3176 3177 /** 3178 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>MULTI</jsf>).items(items)</c>. 3179 * 3180 * @param items The schema of the array items. 3181 * @return A new builder for this object. 3182 */ 3183 public static Builder tArrayMulti(Builder items) { 3184 return create().tArray().cfMulti().items(items); 3185 } 3186 3187 /** 3188 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>)</c>. 3189 * 3190 * @return A new builder for this object. 3191 */ 3192 public static Builder tArrayPipes() { 3193 return create().tArray().cfPipes(); 3194 } 3195 3196 /** 3197 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>).items(items)</c>. 3198 * 3199 * @param items The schema of the array items. 3200 * @return A new builder for this object. 3201 */ 3202 public static Builder tArrayPipes(Builder items) { 3203 return create().tArray().cfPipes().items(items); 3204 } 3205 3206 /** 3207 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>)</c>. 3208 * 3209 * @return A new builder for this object. 3210 */ 3211 public static Builder tArraySsv() { 3212 return create().tArray().cfSsv(); 3213 } 3214 3215 /** 3216 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>).items(items)</c>. 3217 * 3218 * @param items The schema of the array items. 3219 * @return A new builder for this object. 3220 */ 3221 public static Builder tArraySsv(Builder items) { 3222 return create().tArray().cfSsv().items(items); 3223 } 3224 3225 /** 3226 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>)</c>. 3227 * 3228 * @return A new builder for this object. 3229 */ 3230 public static Builder tArrayTsv() { 3231 return create().tArray().cfTsv(); 3232 } 3233 3234 /** 3235 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>).items(items)</c>. 3236 * 3237 * @param items The schema of the array items. 3238 * @return A new builder for this object. 3239 */ 3240 public static Builder tArrayTsv(Builder items) { 3241 return create().tArray().cfTsv().items(items); 3242 } 3243 3244 /** 3245 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UONC</jsf>)</c>. 3246 * 3247 * @return A new builder for this object. 3248 */ 3249 public static Builder tArrayUon() { 3250 return create().tArray().cfUon(); 3251 } 3252 3253 /** 3254 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UONC</jsf>).items(items)</c>. 3255 * 3256 * @param items The schema of the array items. 3257 * @return A new builder for this object. 3258 */ 3259 public static Builder tArrayUon(Builder items) { 3260 return create().tArray().cfUon().items(items); 3261 } 3262 3263 /** 3264 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BINARY</jsf>)</c>. 3265 * 3266 * @return A new builder for this object. 3267 */ 3268 public static Builder tBinary() { 3269 return create().tString().fBinary(); 3270 } 3271 3272 /** 3273 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BINARY_SPACED</jsf>)</c>. 3274 * 3275 * @return A new builder for this object. 3276 */ 3277 public static Builder tBinarySpaced() { 3278 return create().tString().fBinarySpaced(); 3279 } 3280 3281 /** 3282 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>BOOLEAN</jsf>)</c>. 3283 * 3284 * @return A new builder for this object. 3285 */ 3286 public static Builder tBoolean() { 3287 return create().tBoolean(); 3288 } 3289 3290 /** 3291 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BYTE</jsf>)</c>. 3292 * 3293 * @return A new builder for this object. 3294 */ 3295 public static Builder tByte() { 3296 return create().tString().fByte(); 3297 } 3298 3299 /** 3300 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>DATE</jsf>)</c>. 3301 * 3302 * @return A new builder for this object. 3303 */ 3304 public static Builder tDate() { 3305 return create().tString().fDate(); 3306 } 3307 3308 /** 3309 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>DATE_TIME</jsf>)</c>. 3310 * 3311 * @return A new builder for this object. 3312 */ 3313 public static Builder tDateTime() { 3314 return create().tString().fDateTime(); 3315 } 3316 3317 /** 3318 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>).format(HttpPartFormat.<jsf>DOUBLE</jsf>)</c>. 3319 * 3320 * @return A new builder for this object. 3321 */ 3322 public static Builder tDouble() { 3323 return create().tNumber().fDouble(); 3324 } 3325 3326 /** 3327 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>FILE</jsf>)</c>. 3328 * 3329 * @return A new builder for this object. 3330 */ 3331 public static Builder tFile() { 3332 return create().tFile(); 3333 } 3334 3335 /** 3336 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>).format(HttpPartFormat.<jsf>FLOAT</jsf>)</c>. 3337 * 3338 * @return A new builder for this object. 3339 */ 3340 public static Builder tFloat() { 3341 return create().tNumber().fFloat(); 3342 } 3343 3344 /** 3345 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>).format(HttpPartFormat.<jsf>INT32</jsf>)</c>. 3346 * 3347 * @return A new builder for this object. 3348 */ 3349 public static Builder tInt32() { 3350 return create().tInteger().fInt32(); 3351 } 3352 3353 /** 3354 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>).format(HttpPartFormat.<jsf>INT64</jsf>)</c>. 3355 * 3356 * @return A new builder for this object. 3357 */ 3358 public static Builder tInt64() { 3359 return create().tInteger().fInt64(); 3360 } 3361 3362 /** 3363 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>)</c>. 3364 * 3365 * @return A new builder for this object. 3366 */ 3367 public static Builder tInteger() { 3368 return create().tInteger(); 3369 } 3370 3371 /** 3372 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NONE</jsf>)</c>. 3373 * 3374 * @return A new builder for this object. 3375 */ 3376 public static Builder tNone() { 3377 return create().tNone(); 3378 } 3379 3380 /** 3381 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>)</c>. 3382 * 3383 * @return A new builder for this object. 3384 */ 3385 public static Builder tNumber() { 3386 return create().tNumber(); 3387 } 3388 3389 /** 3390 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>)</c>. 3391 * 3392 * @return A new builder for this object. 3393 */ 3394 public static Builder tObject() { 3395 return create().tObject(); 3396 } 3397 3398 /** 3399 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UON</jsf>)</c>. 3400 * 3401 * @return A new builder for this object. 3402 */ 3403 public static Builder tObjectUon() { 3404 return create().tObject().cfUon(); 3405 } 3406 3407 /** 3408 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>)</c>. 3409 * 3410 * @return A new builder for this object. 3411 */ 3412 public static Builder tString() { 3413 return create().tString(); 3414 } 3415 3416 /** 3417 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>UON</jsf>)</c>. 3418 * 3419 * @return A new builder for this object. 3420 */ 3421 public static Builder tUon() { 3422 return create().tString().fUon(); 3423 } 3424 3425 private static Map<String,HttpPartSchema> build(Map<String,Object> in, boolean noValidate) { 3426 if (in == null) 3427 return null; 3428 Map<String,HttpPartSchema> m = map(); 3429 in.forEach((k, v) -> m.put(k, build(v, noValidate))); 3430 return u(m); 3431 } 3432 3433 private static HttpPartSchema build(Object in, boolean noValidate) { 3434 if (in == null) 3435 return null; 3436 if (in instanceof HttpPartSchema in2) 3437 return in2; 3438 return ((Builder)in).noValidate(noValidate).build(); 3439 } 3440 3441 private static <T> Set<T> copy(Set<T> in) { 3442 return in == null ? emptySet() : u(cp(in)); 3443 } 3444 3445 static final Number toNumber(String...s) { 3446 try { 3447 for (var ss : s) 3448 if (ine(ss)) 3449 return parseNumber(ss, Number.class); 3450 return null; 3451 } catch (ParseException e) { 3452 throw toRex(e); 3453 } 3454 } 3455 3456 static final Set<String> toSet(String s) { 3457 if (isEmpty(s)) 3458 return null; 3459 Set<String> set = set(); 3460 StringUtils.split(s, set::add); 3461 return set; 3462 } 3463 3464 static final Set<String> toSet(String[]...s) { 3465 var isNotEmpty = false; 3466 for (var ss : s) 3467 isNotEmpty |= ss.length > 0; 3468 if (! isNotEmpty) 3469 return null; 3470 Set<String> set = set(); 3471 for (var ss : s) 3472 if (nn(ss)) 3473 for (var ss2 : ss) 3474 StringUtils.split(ss2, set::add); 3475 return set.isEmpty() ? null : set; 3476 } 3477 3478 final String name; 3479 final String default_; 3480 final Set<String> enum_; 3481 final Map<String,HttpPartSchema> properties; 3482 final boolean allowEmptyValue; 3483 final boolean exclusiveMaximum; 3484 final boolean exclusiveMinimum; 3485 final boolean required; 3486 final boolean uniqueItems; 3487 final boolean skipIfEmpty; 3488 final HttpPartCollectionFormat collectionFormat; 3489 final HttpPartDataType type; 3490 final HttpPartFormat format; 3491 final Pattern pattern; 3492 final HttpPartSchema items; 3493 final HttpPartSchema additionalProperties; 3494 final Number maximum; 3495 final Number minimum; 3496 final Number multipleOf; 3497 final Long maxLength; 3498 final Long minLength; 3499 final Long maxItems; 3500 final Long minItems; 3501 final Long maxProperties; 3502 final Long minProperties; 3503 3504 final Class<?> parser; 3505 3506 final Class<?> serializer; 3507 3508 final Class<?> parsedType; 3509 3510 // JSON Schema Draft 2020-12 fields 3511 final String const_; 3512 3513 final String[] examples; 3514 3515 final boolean deprecated; 3516 3517 final Number exclusiveMaximumValue; 3518 final Number exclusiveMinimumValue; 3519 3520 HttpPartSchema(Builder b) { 3521 name = b.name; 3522 default_ = b.default_; 3523 enum_ = copy(b.enum_); 3524 properties = build(b.properties, b.noValidate); 3525 allowEmptyValue = resolve(b.allowEmptyValue); 3526 exclusiveMaximum = resolve(b.exclusiveMaximum); 3527 exclusiveMinimum = resolve(b.exclusiveMinimum); 3528 required = resolve(b.required); 3529 uniqueItems = resolve(b.uniqueItems); 3530 skipIfEmpty = resolve(b.skipIfEmpty); 3531 collectionFormat = b.collectionFormat; 3532 type = b.type; 3533 format = b.format; 3534 pattern = b.pattern; 3535 items = build(b.items, b.noValidate); 3536 additionalProperties = build(b.additionalProperties, b.noValidate); 3537 maximum = b.maximum; 3538 minimum = b.minimum; 3539 multipleOf = b.multipleOf; 3540 maxItems = b.maxItems; 3541 maxLength = b.maxLength; 3542 maxProperties = b.maxProperties; 3543 minItems = b.minItems; 3544 minLength = b.minLength; 3545 minProperties = b.minProperties; 3546 parser = b.parser; 3547 serializer = b.serializer; 3548 // JSON Schema Draft 2020-12 fields 3549 const_ = b.const_; 3550 examples = b.examples; 3551 deprecated = resolve(b.deprecated); 3552 exclusiveMaximumValue = b.exclusiveMaximumValue; 3553 exclusiveMinimumValue = b.exclusiveMinimumValue; 3554 3555 // Calculate parse type 3556 Class<?> parsedType2 = Object.class; 3557 if (type == ARRAY) { 3558 if (nn(items) && nn(items.parsedType)) 3559 parsedType2 = Array.newInstance(items.parsedType, 0).getClass(); 3560 } else if (type == BOOLEAN) { 3561 parsedType2 = Boolean.class; 3562 } else if (type == INTEGER) { 3563 if (format == INT64) 3564 parsedType2 = Long.class; 3565 else 3566 parsedType2 = Integer.class; 3567 } else if (type == NUMBER) { 3568 if (format == DOUBLE) 3569 parsedType2 = Double.class; 3570 else 3571 parsedType2 = Float.class; 3572 } else if (type == STRING) { 3573 if (format == BYTE || format == BINARY || format == BINARY_SPACED) 3574 parsedType2 = byte[].class; 3575 else if (format == DATE || format == DATE_TIME) 3576 parsedType2 = Calendar.class; 3577 else 3578 parsedType2 = String.class; 3579 } 3580 this.parsedType = parsedType2; 3581 3582 if (b.noValidate) 3583 return; 3584 3585 // Validation. 3586 var errors = list(); 3587 var notAllowed = lb(String.class); 3588 var invalidFormat = false; 3589 3590 // @formatter:off 3591 switch (type) { 3592 case STRING: { 3593 notAllowed 3594 .addIf(nn(properties), "properties") 3595 .addIf(nn(additionalProperties), "additionalProperties") 3596 .addIf(exclusiveMaximum, "exclusiveMaximum") 3597 .addIf(exclusiveMinimum, "exclusiveMinimum") 3598 .addIf(uniqueItems, "uniqueItems") 3599 .addIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat") 3600 .addIf(nn(items), "items") 3601 .addIf(nn(maximum), "maximum") 3602 .addIf(nn(minimum), "minimum") 3603 .addIf(nn(multipleOf), "multipleOf") 3604 .addIf(nn(maxItems), "maxItems") 3605 .addIf(nn(minItems), "minItems") 3606 .addIf(nn(minProperties), "minProperties"); 3607 invalidFormat = ! format.isOneOf(HttpPartFormat.BYTE, HttpPartFormat.BINARY, HttpPartFormat.BINARY_SPACED, HttpPartFormat.DATE, HttpPartFormat.DATE_TIME, HttpPartFormat.PASSWORD, HttpPartFormat.UON, HttpPartFormat.NO_FORMAT); 3608 break; 3609 } 3610 case ARRAY: { 3611 notAllowed.addIf(nn(properties), "properties") 3612 .addIf(nn(additionalProperties), "additionalProperties") 3613 .addIf(exclusiveMaximum, "exclusiveMaximum") 3614 .addIf(exclusiveMinimum, "exclusiveMinimum") 3615 .addIf(nn(pattern), "pattern") 3616 .addIf(nn(maximum), "maximum") 3617 .addIf(nn(minimum), "minimum") 3618 .addIf(nn(multipleOf), "multipleOf") 3619 .addIf(nn(maxLength), "maxLength") 3620 .addIf(nn(minLength), "minLength") 3621 .addIf(nn(maxProperties), "maxProperties") 3622 .addIf(nn(minProperties), "minProperties"); 3623 invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON); 3624 break; 3625 } 3626 case BOOLEAN: { 3627 notAllowed.addIf(! enum_.isEmpty(), "enum") 3628 .addIf(nn(properties), "properties") 3629 .addIf(nn(additionalProperties), "additionalProperties") 3630 .addIf(exclusiveMaximum, "exclusiveMaximum") 3631 .addIf(exclusiveMinimum, "exclusiveMinimum") 3632 .addIf(uniqueItems, "uniqueItems") 3633 .addIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat") 3634 .addIf(nn(pattern), "pattern") 3635 .addIf(nn(items), "items") 3636 .addIf(nn(maximum), "maximum") 3637 .addIf(nn(minimum), "minimum") 3638 .addIf(nn(multipleOf), "multipleOf") 3639 .addIf(nn(maxItems), "maxItems") 3640 .addIf(nn(maxLength), "maxLength") 3641 .addIf(nn(maxProperties), "maxProperties") 3642 .addIf(nn(minItems), "minItems") 3643 .addIf(nn(minLength), "minLength") 3644 .addIf(nn(minProperties), "minProperties"); 3645 invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON); 3646 break; 3647 } 3648 case FILE: { 3649 break; 3650 } 3651 case INTEGER: { 3652 notAllowed.addIf(nn(properties), "properties") 3653 .addIf(nn(additionalProperties), "additionalProperties") 3654 .addIf(uniqueItems, "uniqueItems") 3655 .addIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat") 3656 .addIf(nn(pattern), "pattern") 3657 .addIf(nn(items), "items") 3658 .addIf(nn(maxItems), "maxItems") 3659 .addIf(nn(maxLength), "maxLength") 3660 .addIf(nn(maxProperties), "maxProperties") 3661 .addIf(nn(minItems), "minItems") 3662 .addIf(nn(minLength), "minLength") 3663 .addIf(nn(minProperties), "minProperties"); 3664 invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON, HttpPartFormat.INT32, HttpPartFormat.INT64); 3665 break; 3666 } 3667 case NUMBER: { 3668 notAllowed.addIf(nn(properties), "properties") 3669 .addIf(nn(additionalProperties), "additionalProperties") 3670 .addIf(uniqueItems, "uniqueItems") 3671 .addIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat") 3672 .addIf(nn(pattern), "pattern") 3673 .addIf(nn(items), "items") 3674 .addIf(nn(maxItems), "maxItems") 3675 .addIf(nn(maxLength), "maxLength") 3676 .addIf(nn(maxProperties), "maxProperties") 3677 .addIf(nn(minItems), "minItems") 3678 .addIf(nn(minLength), "minLength") 3679 .addIf(nn(minProperties), "minProperties"); 3680 invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON, HttpPartFormat.FLOAT, HttpPartFormat.DOUBLE); 3681 break; 3682 } 3683 case OBJECT: { 3684 notAllowed.addIf(exclusiveMaximum, "exclusiveMaximum") 3685 .addIf(exclusiveMinimum, "exclusiveMinimum") 3686 .addIf(uniqueItems, "uniqueItems") 3687 .addIf(nn(pattern), "pattern") 3688 .addIf(nn(items), "items") 3689 .addIf(nn(maximum), "maximum") 3690 .addIf(nn(minimum), "minimum") 3691 .addIf(nn(multipleOf), "multipleOf") 3692 .addIf(nn(maxItems), "maxItems") 3693 .addIf(nn(maxLength), "maxLength") 3694 .addIf(nn(minItems), "minItems") 3695 .addIf(nn(minLength), "minLength"); 3696 invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT); 3697 break; 3698 } 3699 default: 3700 break; 3701 } 3702 // @formatter:on 3703 3704 List<String> notAllowed2 = notAllowed.build(); 3705 if (! notAllowed2.isEmpty()) 3706 errors.add("Attributes not allow for type='" + type + "': " + StringUtils.join(notAllowed2, ",")); 3707 if (invalidFormat) 3708 errors.add("Invalid format for type='" + type + "': '" + format + "'"); 3709 if (exclusiveMaximum && maximum == null) 3710 errors.add("Cannot specify exclusiveMaximum with maximum."); 3711 if (exclusiveMinimum && minimum == null) 3712 errors.add("Cannot specify exclusiveMinimum with minimum."); 3713 if (required && nn(default_)) 3714 errors.add("Cannot specify a default value on a required value."); 3715 if (nn(minLength) && nn(maxLength) && maxLength < minLength) 3716 errors.add("maxLength cannot be less than minLength."); 3717 if (nn(minimum) && nn(maximum) && maximum.doubleValue() < minimum.doubleValue()) 3718 errors.add("maximum cannot be less than minimum."); 3719 if (nn(minItems) && nn(maxItems) && maxItems < minItems) 3720 errors.add("maxItems cannot be less than minItems."); 3721 if (nn(minProperties) && nn(maxProperties) && maxProperties < minProperties) 3722 errors.add("maxProperties cannot be less than minProperties."); 3723 if (nn(minLength) && minLength < 0) 3724 errors.add("minLength cannot be less than zero."); 3725 if (nn(maxLength) && maxLength < 0) 3726 errors.add("maxLength cannot be less than zero."); 3727 if (nn(minItems) && minItems < 0) 3728 errors.add("minItems cannot be less than zero."); 3729 if (nn(maxItems) && maxItems < 0) 3730 errors.add("maxItems cannot be less than zero."); 3731 if (nn(minProperties) && minProperties < 0) 3732 errors.add("minProperties cannot be less than zero."); 3733 if (nn(maxProperties) && maxProperties < 0) 3734 errors.add("maxProperties cannot be less than zero."); 3735 if (type == ARRAY && nn(items) && items.getType() == OBJECT && (format != UON && format != HttpPartFormat.NO_FORMAT)) 3736 errors.add("Cannot define an array of objects unless array format is 'uon'."); 3737 3738 if (! errors.isEmpty()) 3739 throw new BasicRuntimeException("Schema specification errors: \n\t" + StringUtils.join(errors, "\n\t")); 3740 } 3741 3742 /** 3743 * Returns the <c>collectionFormat</c> field of this schema. 3744 * 3745 * @return The <c>collectionFormat</c> field of this schema. Defaults to {@link HttpPartCollectionFormat#NO_COLLECTION_FORMAT} if not specified. 3746 * @see HttpPartSchema.Builder#collectionFormat(String) 3747 */ 3748 public HttpPartCollectionFormat getCollectionFormat() { return collectionFormat; } 3749 3750 /** 3751 * Returns the <c>default</c> field of this schema. 3752 * 3753 * @return The default value for this schema, or <jk>null</jk> if not specified. 3754 * @see HttpPartSchema.Builder#default_(String) 3755 */ 3756 public String getDefault() { return default_; } 3757 3758 /** 3759 * Returns the <c>enum</c> field of this schema. 3760 * 3761 * @return The <c>enum</c> field of this schema. Never <jk>null</jk> - returns an empty set if not specified. 3762 * @see HttpPartSchema.Builder#enum_(Set) 3763 */ 3764 public Set<String> getEnum() { return enum_; } 3765 3766 /** 3767 * Returns the <c>format</c> field of this schema. 3768 * 3769 * @see HttpPartSchema.Builder#format(String) 3770 * @return The <c>format</c> field of this schema. Defaults to {@link HttpPartFormat#NO_FORMAT} if not specified. 3771 */ 3772 public HttpPartFormat getFormat() { return format; } 3773 3774 /** 3775 * Returns the <c>format</c> field of this schema. 3776 * 3777 * @param cm 3778 * The class info of the object. 3779 * <br>Used to auto-detect the format if the format was not specified. 3780 * <br>Must not be <jk>null</jk>. 3781 * @return The <c>format</c> field of this schema, or the format auto-detected from the class info, or {@link HttpPartFormat#NO_FORMAT} if not specified and not auto-detectable. 3782 * @see HttpPartSchema.Builder#format(String) 3783 */ 3784 public HttpPartFormat getFormat(ClassInfo cm) { 3785 if (format != HttpPartFormat.NO_FORMAT) 3786 return format; 3787 if (cm.isNumber()) { 3788 if (cm.isDecimal()) { 3789 if (cm.isDouble()) 3790 return HttpPartFormat.DOUBLE; 3791 return HttpPartFormat.FLOAT; 3792 } 3793 if (cm.isLong()) 3794 return HttpPartFormat.INT64; 3795 return HttpPartFormat.INT32; 3796 } 3797 return format; 3798 } 3799 3800 /** 3801 * Returns the <c>items</c> field of this schema. 3802 * 3803 * @return The schema for child items of the object represented by this schema, or <jk>null</jk> if not defined. 3804 * @see HttpPartSchema.Builder#items(HttpPartSchema.Builder) 3805 */ 3806 public HttpPartSchema getItems() { return items; } 3807 3808 /** 3809 * Returns the <c>maximum</c> field of this schema. 3810 * 3811 * @return The <c>maximum</c> field of this schema, or <jk>null</jk> if not specified. 3812 * @see HttpPartSchema.Builder#maximum(Number) 3813 */ 3814 public Number getMaximum() { return maximum; } 3815 3816 /** 3817 * Returns the <c>maxItems</c> field of this schema. 3818 * 3819 * @return The <c>maxItems</c> field of this schema, or <jk>null</jk> if not specified. 3820 * @see HttpPartSchema.Builder#maxItems(Long) 3821 */ 3822 public Long getMaxItems() { return maxItems; } 3823 3824 /** 3825 * Returns the <c>maxLength</c> field of this schema. 3826 * 3827 * @return The <c>maxLength</c> field of this schema, or <jk>null</jk> if not specified. 3828 * @see HttpPartSchema.Builder#maxLength(Long) 3829 */ 3830 public Long getMaxLength() { return maxLength; } 3831 3832 /** 3833 * Returns the <c>maxProperties</c> field of this schema. 3834 * 3835 * @return The <c>maxProperties</c> field of this schema, or <jk>null</jk> if not specified. 3836 * @see HttpPartSchema.Builder#maxProperties(Long) 3837 */ 3838 public Long getMaxProperties() { return maxProperties; } 3839 3840 /** 3841 * Returns the <c>minimum</c> field of this schema. 3842 * 3843 * @return The <c>minimum</c> field of this schema, or <jk>null</jk> if not specified. 3844 * @see HttpPartSchema.Builder#minimum(Number) 3845 */ 3846 public Number getMinimum() { return minimum; } 3847 3848 /** 3849 * Returns the <c>minItems</c> field of this schema. 3850 * 3851 * @return The <c>minItems</c> field of this schema, or <jk>null</jk> if not specified. 3852 * @see HttpPartSchema.Builder#minItems(Long) 3853 */ 3854 public Long getMinItems() { return minItems; } 3855 3856 /** 3857 * Returns the <c>minLength</c> field of this schema. 3858 * 3859 * @return The <c>minLength</c> field of this schema, or <jk>null</jk> if not specified. 3860 * @see HttpPartSchema.Builder#minLength(Long) 3861 */ 3862 public Long getMinLength() { return minLength; } 3863 3864 /** 3865 * Returns the <c>minProperties</c> field of this schema. 3866 * 3867 * @return The <c>minProperties</c> field of this schema, or <jk>null</jk> if not specified. 3868 * @see HttpPartSchema.Builder#minProperties(Long) 3869 */ 3870 public Long getMinProperties() { return minProperties; } 3871 3872 /** 3873 * Returns the <c>multipleOf</c> field of this schema. 3874 * 3875 * @return The <c>multipleOf</c> field of this schema, or <jk>null</jk> if not specified. 3876 * @see HttpPartSchema.Builder#multipleOf(Number) 3877 */ 3878 public Number getMultipleOf() { return multipleOf; } 3879 3880 /** 3881 * Returns the name of the object described by this schema, for example the query or form parameter name. 3882 * 3883 * @return The name, or <jk>null</jk> if not specified. 3884 * @see HttpPartSchema.Builder#name(String) 3885 */ 3886 public String getName() { return name; } 3887 3888 /** 3889 * Returns the default parsed type for this schema. 3890 * 3891 * @return The default parsed type for this schema. Never <jk>null</jk>. 3892 */ 3893 public Class<?> getParsedType() { return parsedType; } 3894 3895 /** 3896 * Returns the <c>parser</c> field of this schema. 3897 * 3898 * @return The <c>parser</c> field of this schema, or <jk>null</jk> if not specified. 3899 * @see HttpPartSchema.Builder#parser(Class) 3900 */ 3901 public Class<?> getParser() { return parser; } 3902 3903 /** 3904 * Returns the <c>pattern</c> field of this schema. 3905 * 3906 * @return The <c>pattern</c> field of this schema, or <jk>null</jk> if not specified. 3907 * @see HttpPartSchema.Builder#pattern(String) 3908 */ 3909 public Pattern getPattern() { return pattern; } 3910 3911 /** 3912 * Returns the schema information for the specified property. 3913 * 3914 * @param name The property name. Can be <jk>null</jk> (treated as an unmatched name; falls back to the <c>additionalProperties</c> schema). 3915 * @return The schema information for the specified property, or the <c>additionalProperties</c> schema if no matching property is defined, or <jk>null</jk> if neither is defined. 3916 */ 3917 public HttpPartSchema getProperty(String name) { 3918 if (nn(properties)) { 3919 HttpPartSchema schema = properties.get(name); 3920 if (nn(schema)) 3921 return schema; 3922 } 3923 return additionalProperties; 3924 } 3925 3926 /** 3927 * Returns the <c>serializer</c> field of this schema. 3928 * 3929 * @return The <c>serializer</c> field of this schema, or <jk>null</jk> if not specified. 3930 * @see HttpPartSchema.Builder#serializer(Class) 3931 */ 3932 public Class<?> getSerializer() { return serializer; } 3933 3934 /** 3935 * Returns the <c>type</c> field of this schema. 3936 * 3937 * @return The <c>type</c> field of this schema. Defaults to {@link HttpPartDataType#NO_TYPE} if not specified. 3938 * @see HttpPartSchema.Builder#type(String) 3939 */ 3940 public HttpPartDataType getType() { return type; } 3941 3942 /** 3943 * Returns the <c>type</c> field of this schema. 3944 * 3945 * @param cm 3946 * The class info of the object. 3947 * <br>Used to auto-detect the type if the type was not specified. 3948 * <br>Must not be <jk>null</jk>. 3949 * @return The <c>type</c> field of this schema, or the type auto-detected from the class info if not specified. 3950 * @see HttpPartSchema.Builder#type(String) 3951 */ 3952 public HttpPartDataType getType(ClassInfo cm) { 3953 if (type != HttpPartDataType.NO_TYPE) 3954 return type; 3955 if (cm.isTemporal() || cm.isDateOrCalendar()) 3956 return HttpPartDataType.STRING; 3957 if (cm.isNumber()) { 3958 if (cm.isDecimal()) 3959 return HttpPartDataType.NUMBER; 3960 return HttpPartDataType.INTEGER; 3961 } 3962 if (cm.isBoolean()) 3963 return HttpPartDataType.BOOLEAN; 3964 if (cm.isMap() || isObjectType(cm)) 3965 return HttpPartDataType.OBJECT; 3966 if (cm.isCollectionOrArray()) 3967 return HttpPartDataType.ARRAY; 3968 return HttpPartDataType.STRING; 3969 } 3970 3971 private static boolean isObjectType(ClassInfo cm) { 3972 if (cm instanceof ClassMeta<?> cm2) 3973 return cm2.isBean(); 3974 return false; 3975 } 3976 3977 /** 3978 * Returns <jk>true</jk> if this schema has properties associated with it. 3979 * 3980 * @return <jk>true</jk> if this schema has properties associated with it. 3981 */ 3982 public boolean hasProperties() { 3983 return nn(properties) || nn(additionalProperties); 3984 } 3985 3986 /** 3987 * Returns the <c>allowEmptyValue</c> field of this schema. 3988 * 3989 * @return The <c>allowEmptyValue</c> field of this schema. 3990 * @see HttpPartSchema.Builder#allowEmptyValue(Boolean) 3991 */ 3992 public boolean isAllowEmptyValue() { return allowEmptyValue; } 3993 3994 /** 3995 * Returns the <c>exclusiveMaximum</c> field of this schema. 3996 * 3997 * @return The <c>exclusiveMaximum</c> field of this schema. 3998 * @see HttpPartSchema.Builder#exclusiveMaximum(Boolean) 3999 */ 4000 public boolean isExclusiveMaximum() { return exclusiveMaximum; } 4001 4002 /** 4003 * Returns the <c>exclusiveMinimum</c> field of this schema. 4004 * 4005 * @return The <c>exclusiveMinimum</c> field of this schema. 4006 * @see HttpPartSchema.Builder#exclusiveMinimum(Boolean) 4007 */ 4008 public boolean isExclusiveMinimum() { return exclusiveMinimum; } 4009 4010 /** 4011 * Returns the <c>required</c> field of this schema. 4012 * 4013 * @return The <c>required</c> field of this schema. 4014 * @see HttpPartSchema.Builder#required(Boolean) 4015 */ 4016 public boolean isRequired() { return required; } 4017 4018 /** 4019 * Returns the <c>skipIfEmpty</c> field of this schema. 4020 * 4021 * @return The <c>skipIfEmpty</c> field of this schema. 4022 * @see HttpPartSchema.Builder#skipIfEmpty(Boolean) 4023 */ 4024 public boolean isSkipIfEmpty() { return skipIfEmpty; } 4025 4026 /** 4027 * Returns the <c>uniqueItems</c> field of this schema. 4028 * 4029 * @return The <c>uniqueItems</c> field of this schema. 4030 * @see HttpPartSchema.Builder#uniqueItems(Boolean) 4031 */ 4032 public boolean isUniqueItems() { return uniqueItems; } 4033 4034 protected FluentMap<String,Object> properties() { 4035 // @formatter:off 4036 Predicate<Object> ne = x -> ine(s(x)); 4037 Predicate<Object> nf = x -> x instanceof Boolean x2 && x2; 4038 Predicate<Object> nm1 = x -> x instanceof Number x2 && x2.intValue() != -1; 4039 Predicate<Object> nn = Shorts::nn; 4040 return mapb_so().sorted().buildFluent() 4041 .ai(ne, "name", name) 4042 .ai(ne, "type", type) 4043 .ai(ne, "format", format) 4044 .ai(ne, "default", default_) 4045 .ai(ne, "enum", enum_) 4046 .ai(ne, "properties", properties) 4047 .ai(nf, "allowEmptyValue", allowEmptyValue) 4048 .ai(nf, "exclusiveMaximum", exclusiveMaximum) 4049 .ai(nf, "exclusiveMinimum", exclusiveMinimum) 4050 .ai(nf, "required", required) 4051 .ai(nf, "uniqueItems", uniqueItems) 4052 .ai(nf, "skipIfEmpty", skipIfEmpty) 4053 .ai(x -> x != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat", collectionFormat) 4054 .ai(ne, "pattern", pattern) 4055 .ai(nn, "items", items) 4056 .ai(nn, "additionalProperties", additionalProperties) 4057 .ai(nm1, "maximum", maximum) 4058 .ai(nm1, "minimum", minimum) 4059 .ai(nm1, "multipleOf", multipleOf) 4060 .ai(nm1, "maxLength", maxLength) 4061 .ai(nm1, "minLength", minLength) 4062 .ai(nm1, "maxItems", maxItems) 4063 .ai(nm1, "minItems", minItems) 4064 .ai(nm1, "maxProperties", maxProperties) 4065 .ai(nm1, "minProperties", minProperties) 4066 .a("parsedType", parsedType); 4067 // @formatter:on 4068 } 4069 4070 @Override /* Overridden from Object */ 4071 public String toString() { 4072 return r(properties()); 4073 } 4074 4075 /** 4076 * Throws a {@link SchemaValidationException} if the specified pre-parsed input does not validate against this schema. 4077 * 4078 * @param in The input. Can be <jk>null</jk> (only the <c>required</c> check is performed; format/pattern/length checks are skipped). 4079 * @return The same object passed in. 4080 * @throws SchemaValidationException if the specified pre-parsed input does not validate against this schema. 4081 */ 4082 public String validateInput(String in) throws SchemaValidationException { 4083 if (! isValidRequired(in)) 4084 throw new SchemaValidationException("No value specified."); 4085 if (nn(in)) { 4086 if (! isValidAllowEmpty(in)) 4087 throw new SchemaValidationException("Empty value not allowed."); 4088 if (! isValidConst(in)) 4089 throw new SchemaValidationException("Value does not match constant. Must be: %s", const_); 4090 if (! isValidPattern(in)) 4091 throw new SchemaValidationException("Value does not match expected pattern. Must match pattern: %s", pattern.pattern()); 4092 if (! isValidEnum(in)) 4093 throw new SchemaValidationException("Value does not match one of the expected values. Must be one of the following: %s", toCdl(enum_)); 4094 if (! isValidMaxLength(in)) 4095 throw new SchemaValidationException("Maximum length of value exceeded."); 4096 if (! isValidMinLength(in)) 4097 throw new SchemaValidationException("Minimum length of value not met."); 4098 if (! isValidFormat(in)) 4099 throw new SchemaValidationException("Value does not match expected format: %s", format); 4100 } 4101 return in; 4102 } 4103 4104 /** 4105 * Validates the specified parsed output against this schema. 4106 * 4107 * @param <T> The return type. 4108 * @param o The parsed output. Can be <jk>null</jk> (only the <c>required</c> check is performed; type-specific validation is skipped). 4109 * @return The same object passed in. 4110 * @throws SchemaValidationException if the specified parsed output does not validate against this schema. 4111 */ 4112 public <T> T validateOutput(T o) throws SchemaValidationException { 4113 if (o == null) { 4114 if (! isValidRequired(o)) 4115 throw new SchemaValidationException("Required value not provided."); 4116 return o; 4117 } 4118 var ci = ClassInfo.of(o); 4119 switch (getType(ci)) { 4120 case ARRAY: { 4121 if (ci.isArray()) { 4122 if (! isValidMinItems(o)) 4123 throw new SchemaValidationException("Minimum number of items not met."); 4124 if (! isValidMaxItems(o)) 4125 throw new SchemaValidationException("Maximum number of items exceeded."); 4126 if (! isValidUniqueItems(o)) 4127 throw new SchemaValidationException("Duplicate items not allowed."); 4128 HttpPartSchema items2 = getItems(); 4129 if (nn(items2)) 4130 for (var i = 0; i < Array.getLength(o); i++) 4131 items2.validateOutput(Array.get(o, i)); 4132 } else if (ci.isCollection()) { 4133 Collection<?> c = (Collection<?>)o; 4134 if (! isValidMinItems(c)) 4135 throw new SchemaValidationException("Minimum number of items not met."); 4136 if (! isValidMaxItems(c)) 4137 throw new SchemaValidationException("Maximum number of items exceeded."); 4138 if (! isValidUniqueItems(c)) 4139 throw new SchemaValidationException("Duplicate items not allowed."); 4140 HttpPartSchema items2 = getItems(); 4141 if (nn(items2)) 4142 c.forEach(items2::validateOutput); 4143 } 4144 break; 4145 } 4146 case INTEGER: { 4147 if (ci.isNumber()) { 4148 Number n = (Number)o; 4149 if (! isValidMinimum(n)) 4150 throw new SchemaValidationException("Minimum value not met."); 4151 if (! isValidMaximum(n)) 4152 throw new SchemaValidationException("Maximum value exceeded."); 4153 if (! isValidMultipleOf(n)) 4154 throw new SchemaValidationException("Multiple-of not met."); 4155 } 4156 break; 4157 } 4158 case NUMBER: { 4159 if (ci.isNumber()) { 4160 Number n = (Number)o; 4161 if (! isValidMinimum(n)) 4162 throw new SchemaValidationException("Minimum value not met."); 4163 if (! isValidMaximum(n)) 4164 throw new SchemaValidationException("Maximum value exceeded."); 4165 if (! isValidMultipleOf(n)) 4166 throw new SchemaValidationException("Multiple-of not met."); 4167 } 4168 break; 4169 } 4170 case OBJECT: { 4171 if (ci.isMap()) { 4172 Map<?,?> m = (Map<?,?>)o; 4173 if (! isValidMinProperties(m)) 4174 throw new SchemaValidationException("Minimum number of properties not met."); 4175 if (! isValidMaxProperties(m)) 4176 throw new SchemaValidationException("Maximum number of properties exceeded."); 4177 m.forEach((k, v) -> { 4178 var key = k.toString(); 4179 HttpPartSchema s2 = getProperty(key); 4180 if (nn(s2)) 4181 s2.validateOutput(v); 4182 }); 4183 } 4184 break; 4185 } 4186 case STRING: { 4187 if (ci.isCharSequence()) { 4188 var s = o.toString(); 4189 if (! isValidMinLength(s)) 4190 throw new SchemaValidationException("Minimum length of value not met."); 4191 if (! isValidMaxLength(s)) 4192 throw new SchemaValidationException("Maximum length of value exceeded."); 4193 if (! isValidPattern(s)) 4194 throw new SchemaValidationException("Value does not match expected pattern. Must match pattern: %s", pattern.pattern()); 4195 if (! isValidFormat(s)) 4196 throw new SchemaValidationException("Value does not match expected format: %s", format); 4197 } 4198 break; 4199 } 4200 case BOOLEAN, FILE, NO_TYPE: 4201 break; 4202 default: 4203 break; 4204 } 4205 return o; 4206 } 4207 4208 private boolean isValidAllowEmpty(String x) { 4209 return allowEmptyValue || ine(x); 4210 } 4211 4212 private boolean isValidConst(String x) { 4213 return const_ == null || const_.equals(x); 4214 } 4215 4216 private static boolean isValidDate(String x) { 4217 // RFC 3339 full-date: YYYY-MM-DD (relaxed to allow various date formats) (programmatic to avoid regex recompilation per HTTP-part value). 4218 // Mirrors prefix match ^\d{4}[-/]\d{1,2}[-/]\d{1,2}.* (\d = ASCII [0-9]; trailing .* is a non-line-terminator prefix check, not a full match). 4219 if (x.length() < 4) 4220 return false; 4221 for (var i = 0; i < 4; i++) 4222 if (! isAsciiDigit(x.charAt(i))) 4223 return false; 4224 var i = scanSepAnd1or2Digits(x, 4); 4225 if (i < 0) 4226 return false; 4227 i = scanSepAnd1or2Digits(x, i); 4228 if (i < 0) 4229 return false; 4230 return hasNoLineTerminator(x, i); 4231 } 4232 4233 private static boolean isValidDateTime(String x) { 4234 // RFC 3339 date-time (relaxed to allow various datetime formats) (programmatic to avoid regex recompilation per HTTP-part value). 4235 // Mirrors prefix match ^\d{4}[-/]\d{1,2}[-/]\d{1,2}[T\s]\d{1,2}:\d{1,2}.* (\s = ASCII whitespace; trailing .* is a non-line-terminator prefix check). 4236 if (x.length() < 4) 4237 return false; 4238 for (var i = 0; i < 4; i++) 4239 if (! isAsciiDigit(x.charAt(i))) 4240 return false; 4241 var i = scanSepAnd1or2Digits(x, 4); 4242 if (i < 0) 4243 return false; 4244 i = scanSepAnd1or2Digits(x, i); 4245 if (i < 0) 4246 return false; 4247 if (i >= x.length() || ! (x.charAt(i) == 'T' || isRegexWhitespace(x.charAt(i)))) 4248 return false; 4249 i = scan1or2Digits(x, i + 1); 4250 if (i < 0) 4251 return false; 4252 if (! charIs(x, i, ':')) 4253 return false; 4254 i = scan1or2Digits(x, i + 1); 4255 if (i < 0) 4256 return false; 4257 return hasNoLineTerminator(x, i); 4258 } 4259 4260 private static boolean isValidDateTimeZone(String x) { 4261 // RFC 3339 date-time with time zone (programmatic to avoid regex recompilation per HTTP-part value). 4262 // Mirrors full match ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?[+-]\d{2}:\d{2}$ (\d = ASCII [0-9]). 4263 var len = x.length(); 4264 var i = scanDateTimeCore(x); 4265 if (i < 0) 4266 return false; 4267 i = scanOptionalFractionalSeconds(x, i, len); 4268 if (i < 0) 4269 return false; 4270 return scanTimezoneOffset(x, i, len) == len; 4271 } 4272 4273 // Mirrors \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2} - the date/time core consumed by isValidDateTimeZone. Returns 4274 // the index after the seconds field, or -1 if any component or separator is missing/malformed. 4275 private static int scanDateTimeCore(String x) { 4276 var i = scanDigits(x, 0, 4); 4277 if (i < 0 || ! charIs(x, i, '-')) 4278 return -1; 4279 i = scanDigits(x, i + 1, 2); 4280 if (i < 0 || ! charIs(x, i, '-')) 4281 return -1; 4282 i = scanDigits(x, i + 1, 2); 4283 if (i < 0 || ! charIs(x, i, 'T')) 4284 return -1; 4285 i = scanDigits(x, i + 1, 2); 4286 if (i < 0 || ! charIs(x, i, ':')) 4287 return -1; 4288 i = scanDigits(x, i + 1, 2); 4289 if (i < 0 || ! charIs(x, i, ':')) 4290 return -1; 4291 return scanDigits(x, i + 1, 2); 4292 } 4293 4294 // Mirrors the optional group (\.\d+)? shared by isValidDateTimeZone and isValidTime: consumes '.' followed 4295 // by one-or-more ASCII digits. Returns i unchanged if no '.' is present at i, the index after the digits 4296 // if one is, or -1 if '.' is present with no following digit. 4297 private static int scanOptionalFractionalSeconds(String x, int i, int len) { 4298 if (i < len && x.charAt(i) == '.') { 4299 var f = i + 1; 4300 var j = f; 4301 while (j < len && isAsciiDigit(x.charAt(j))) 4302 j++; 4303 if (j == f) 4304 return -1; 4305 return j; 4306 } 4307 return i; 4308 } 4309 4310 // Mirrors [+-]\d{2}:\d{2}, the mandatory zone-offset suffix of isValidDateTimeZone and the offset branch of 4311 // isValidTime's optional zone group. Returns the index after the offset, or -1 if the sign, digits, or 4312 // separator are missing/malformed. 4313 private static int scanTimezoneOffset(String x, int i, int len) { 4314 if (i >= len || (x.charAt(i) != '+' && x.charAt(i) != '-')) 4315 return -1; 4316 i = scanDigits(x, i + 1, 2); 4317 if (i < 0 || ! charIs(x, i, ':')) 4318 return -1; 4319 return scanDigits(x, i + 1, 2); 4320 } 4321 4322 private static boolean isValidDuration(String x) { 4323 // RFC 3339 Appendix A duration (ISO 8601) (programmatic to avoid regex recompilation per HTTP-part value). 4324 // Mirrors full match ^P(?:\d+Y)?(?:\d+M)?(?:\d+D)?(?:T(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$ (\d = ASCII [0-9]). 4325 var len = x.length(); 4326 if (len == 0 || x.charAt(0) != 'P') 4327 return false; 4328 var i = scanDurationUnit(x, 1, 'Y'); 4329 i = scanDurationUnit(x, i, 'M'); 4330 i = scanDurationUnit(x, i, 'D'); 4331 if (i < len && x.charAt(i) == 'T') { 4332 i = scanDurationUnit(x, i + 1, 'H'); 4333 i = scanDurationUnit(x, i, 'M'); 4334 i = scanDurationSeconds(x, i); 4335 } 4336 return i == len; 4337 } 4338 4339 private static boolean isValidEmail(String x) { 4340 // RFC 5321 simplified email validation (programmatic to avoid regex super-linear backtracking on large inputs). 4341 // Mirrors ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ 4342 var at = x.indexOf('@'); 4343 if (at < 1 || at != x.lastIndexOf('@')) 4344 return false; 4345 for (var i = 0; i < at; i++) 4346 if (! isEmailLocalChar(x.charAt(i))) 4347 return false; 4348 var dot = x.lastIndexOf('.'); 4349 if (dot < at + 2) // Need at least one domain-body char between '@' and the final '.'. 4350 return false; 4351 for (var i = at + 1; i < dot; i++) 4352 if (! isEmailDomainChar(x.charAt(i))) 4353 return false; 4354 if (x.length() - dot - 1 < 2) // TLD must be 2+ characters. 4355 return false; 4356 for (var i = dot + 1; i < x.length(); i++) 4357 if (! isAsciiLetter(x.charAt(i))) 4358 return false; 4359 return true; 4360 } 4361 4362 private static boolean isEmailLocalChar(char c) { 4363 return isAsciiAlphanumeric(c) || c == '.' || c == '_' || c == '%' || c == '+' || c == '-'; 4364 } 4365 4366 private static boolean isEmailDomainChar(char c) { 4367 return isAsciiAlphanumeric(c) || c == '.' || c == '-'; 4368 } 4369 4370 private static boolean isAsciiLetter(char c) { 4371 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); 4372 } 4373 4374 private boolean isValidEnum(String x) { 4375 return enum_.isEmpty() || enum_.contains(x); 4376 } 4377 4378 private boolean isValidFormat(String x) { 4379 if (format == null || format == HttpPartFormat.NO_FORMAT) 4380 return true; 4381 4382 // Skip validation for literal "null" string 4383 if (eq(x, "null")) 4384 return true; 4385 4386 try { 4387 return switch (format) { 4388 case EMAIL -> isValidEmail(x); 4389 case IDN_EMAIL -> isValidIdnEmail(x); 4390 case HOSTNAME -> isValidHostname(x); 4391 case IDN_HOSTNAME -> isValidIdnHostname(x); 4392 case IPV4 -> isValidIpv4(x); 4393 case IPV6 -> isValidIpv6(x); 4394 case URI -> isValidUri(x); 4395 case URI_REFERENCE -> isValidUriReference(x); 4396 case IRI -> isValidIri(x); 4397 case IRI_REFERENCE -> isValidIriReference(x); 4398 case UUID -> isValidUuid(x); 4399 case URI_TEMPLATE -> isValidUriTemplate(x); 4400 case JSON_POINTER -> isValidJsonPointer(x); 4401 case RELATIVE_JSON_POINTER -> isValidRelativeJsonPointer(x); 4402 case REGEX -> isValidRegex(x); 4403 case DATE -> isValidDate(x); 4404 case DATE_TIME -> isValidDateTime(x); 4405 case DATE_TIME_ZONE -> isValidDateTimeZone(x); 4406 case TIME -> isValidTime(x); 4407 case DURATION -> isValidDuration(x); 4408 case BYTE, BINARY, BINARY_SPACED -> true; // These are transformation formats, not validation formats 4409 case PASSWORD -> true; // Password format is just a UI hint 4410 case INT32, INT64, FLOAT, DOUBLE -> true; // Numeric formats are validated during parsing 4411 case UON -> true; // UON format is validated during parsing 4412 default -> true; 4413 }; 4414 } catch (@SuppressWarnings("unused") Exception e) { 4415 return false; 4416 } 4417 } 4418 4419 private static boolean isValidHostname(String x) { 4420 // RFC 1123 hostname validation (programmatic to avoid regex stack overflow on large inputs) 4421 if (x.isEmpty()) 4422 return false; 4423 int labelStart = 0; 4424 for (int i = 0; i <= x.length(); i++) { 4425 if (i == x.length() || x.charAt(i) == '.') { 4426 if (! isValidHostnameLabel(x, labelStart, i)) 4427 return false; 4428 labelStart = i + 1; 4429 } 4430 } 4431 return true; 4432 } 4433 4434 private static boolean isValidHostnameLabel(String x, int start, int end) { 4435 int len = end - start; 4436 if (len == 0 || len > 63) 4437 return false; 4438 if (! isAsciiAlphanumeric(x.charAt(start)) || ! isAsciiAlphanumeric(x.charAt(end - 1))) 4439 return false; 4440 for (int i = start + 1; i < end - 1; i++) { 4441 char c = x.charAt(i); 4442 if (! isAsciiAlphanumeric(c) && c != '-') 4443 return false; 4444 } 4445 return true; 4446 } 4447 4448 private static boolean isAsciiAlphanumeric(char c) { 4449 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); 4450 } 4451 4452 private static boolean isAsciiDigit(char c) { 4453 // Java regex \d (ASCII [0-9] only). 4454 return c >= '0' && c <= '9'; 4455 } 4456 4457 private static boolean isAsciiHexDigit(char c) { 4458 return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); 4459 } 4460 4461 private static boolean isUriSchemeChar(char c) { 4462 // Matches the regex class [a-zA-Z0-9+.-]. 4463 return isAsciiAlphanumeric(c) || c == '+' || c == '.' || c == '-'; 4464 } 4465 4466 private static boolean isRegexLineTerminator(char c) { 4467 // Chars NOT matched by regex '.' in default (non-DOTALL) mode. 4468 return c == '\n' || c == '\r' || c == '\u0085' || c == '\u2028' || c == '\u2029'; 4469 } 4470 4471 private static boolean charIs(String x, int i, char c) { 4472 return i < x.length() && x.charAt(i) == c; 4473 } 4474 4475 // Returns the index after consuming exactly n ASCII digits starting at i, or -1 if fewer than n digits are present. 4476 private static int scanDigits(String x, int i, int n) { 4477 if (i < 0 || i + n > x.length()) 4478 return -1; 4479 for (var k = 0; k < n; k++) 4480 if (! isAsciiDigit(x.charAt(i + k))) 4481 return -1; 4482 return i + n; 4483 } 4484 4485 // Mirrors \d{1,2} - consumes 1 mandatory then 1 optional ASCII digit (greedy). Returns new index or -1. 4486 private static int scan1or2Digits(String x, int i) { 4487 var len = x.length(); 4488 if (i < 0 || i >= len || ! isAsciiDigit(x.charAt(i))) 4489 return -1; 4490 i++; 4491 if (i < len && isAsciiDigit(x.charAt(i))) 4492 i++; 4493 return i; 4494 } 4495 4496 // Mirrors [-/]\d{1,2}. Returns new index or -1. 4497 private static int scanSepAnd1or2Digits(String x, int i) { 4498 if (i < 0 || i >= x.length() || (x.charAt(i) != '-' && x.charAt(i) != '/')) 4499 return -1; 4500 return scan1or2Digits(x, i + 1); 4501 } 4502 4503 // Mirrors '.*' / '.+' tail consumption: returns true iff every char from i onward is a non-line-terminator (i.e. matchable by regex '.'). 4504 private static boolean hasNoLineTerminator(String x, int i) { 4505 for (var j = i; j < x.length(); j++) 4506 if (isRegexLineTerminator(x.charAt(j))) 4507 return false; 4508 return true; 4509 } 4510 4511 // Mirrors the optional group (?:\d+<unit>)?: consumes a run of >=1 ASCII digits followed by the unit char if present; otherwise leaves i unchanged. 4512 private static int scanDurationUnit(String x, int i, char unit) { 4513 var len = x.length(); 4514 var j = i; 4515 while (j < len && isAsciiDigit(x.charAt(j))) 4516 j++; 4517 if (j > i && j < len && x.charAt(j) == unit) 4518 return j + 1; 4519 return i; 4520 } 4521 4522 // Mirrors the optional group (?:\d+(?:\.\d+)?S)?: consumes seconds (with optional fraction) followed by 'S' if present; otherwise leaves i unchanged. 4523 private static int scanDurationSeconds(String x, int i) { 4524 var len = x.length(); 4525 var j = i; 4526 while (j < len && isAsciiDigit(x.charAt(j))) 4527 j++; 4528 if (j == i) 4529 return i; // \d+ requires at least one digit 4530 if (j < len && x.charAt(j) == '.') { 4531 var f = j + 1; 4532 var k = f; 4533 while (k < len && isAsciiDigit(x.charAt(k))) 4534 k++; 4535 if (k == f) 4536 return i; // '.' with no following digit -> group cannot match -> consume nothing 4537 j = k; 4538 } 4539 if (j < len && x.charAt(j) == 'S') 4540 return j + 1; 4541 return i; // no 'S' -> group matches empty 4542 } 4543 4544 // Mirrors ^[a-zA-Z][a-zA-Z0-9+.-]*:<rest> where <rest> is .+ (nonEmptyRest=true) or .* (nonEmptyRest=false), all non-line-terminators. 4545 private static boolean isValidScheme(String x, boolean nonEmptyRest) { 4546 var len = x.length(); 4547 if (len == 0 || ! isAsciiLetter(x.charAt(0))) 4548 return false; 4549 var i = 1; 4550 while (i < len && isUriSchemeChar(x.charAt(i))) 4551 i++; 4552 if (i >= len || x.charAt(i) != ':') 4553 return false; 4554 i++; 4555 if (nonEmptyRest && i >= len) 4556 return false; 4557 return hasNoLineTerminator(x, i); 4558 } 4559 4560 // Consumes up to 4 ASCII hex digits starting at i (bounded by end). Mirrors [0-9a-fA-F]{0,4}. 4561 private static int scanHex4(String x, int i, int end) { 4562 var n = 0; 4563 while (i < end && n < 4 && isAsciiHexDigit(x.charAt(i))) { 4564 i++; 4565 n++; 4566 } 4567 return i; 4568 } 4569 4570 // Mirrors ^([0-9a-fA-F]{0,4}+:){7}[0-9a-fA-F]{0,4}$ - exactly 8 hextet groups separated by 7 colons. 4571 private static boolean isIpv6Full(String x) { 4572 var len = x.length(); 4573 var i = 0; 4574 for (var g = 0; g < 7; g++) { 4575 i = scanHex4(x, i, len); 4576 if (i >= len || x.charAt(i) != ':') 4577 return false; 4578 i++; 4579 } 4580 i = scanHex4(x, i, len); 4581 return i == len; 4582 } 4583 4584 // Mirrors ^::([0-9a-fA-F]{0,4}+:){0,6}[0-9a-fA-F]{0,4}$. 4585 private static boolean isIpv6LeadingDoubleColon(String x) { 4586 var len = x.length(); 4587 if (len < 2 || x.charAt(0) != ':' || x.charAt(1) != ':') 4588 return false; 4589 var i = 2; 4590 var groups = 0; 4591 while (groups < 6) { 4592 var j = scanHex4(x, i, len); 4593 if (j < len && x.charAt(j) == ':') { 4594 i = j + 1; 4595 groups++; 4596 } else { 4597 break; 4598 } 4599 } 4600 i = scanHex4(x, i, len); 4601 return i == len; 4602 } 4603 4604 // Mirrors ^([0-9a-fA-F]{0,4}+:){1,7}:$ - every such match ends in ':', and the body (minus the final ':') is 1..7 hextet+colon groups. 4605 private static boolean isIpv6TrailingColon(String x) { 4606 var len = x.length(); 4607 if (len == 0 || x.charAt(len - 1) != ':') 4608 return false; 4609 var body = len - 1; 4610 var i = 0; 4611 var groups = 0; 4612 while (i < body) { 4613 var j = scanHex4(x, i, body); 4614 if (j < body && x.charAt(j) == ':') { 4615 i = j + 1; 4616 groups++; 4617 } else { 4618 return false; 4619 } 4620 } 4621 return groups >= 1 && groups <= 7 && i == body; 4622 } 4623 4624 private static boolean isValidIdnEmail(String x) { 4625 // RFC 6531 - allows international characters (programmatic to avoid regex super-linear backtracking on large inputs). 4626 // Mirrors ^[^@\s]+@[^@\s]+\.[^@\s]+$ 4627 var at = x.indexOf('@'); 4628 if (at < 1 || at != x.lastIndexOf('@')) 4629 return false; 4630 for (var i = 0; i < at; i++) 4631 if (isRegexWhitespace(x.charAt(i))) 4632 return false; 4633 var len = x.length(); 4634 var interiorDot = false; 4635 for (var i = at + 1; i < len; i++) { 4636 var c = x.charAt(i); 4637 if (isRegexWhitespace(c)) 4638 return false; 4639 if (c == '.' && i > at + 1 && i < len - 1) 4640 interiorDot = true; 4641 } 4642 return interiorDot; 4643 } 4644 4645 private static boolean isRegexWhitespace(char c) { 4646 // Matches Java regex \s (ASCII whitespace only). 4647 return c == ' ' || c == '\t' || c == '\n' || c == '\u000B' || c == '\f' || c == '\r'; 4648 } 4649 4650 private static boolean isValidIdnHostname(String x) { 4651 // RFC 5890 - allows international characters (programmatic to avoid regex stack overflow on large inputs) 4652 if (x.isEmpty()) 4653 return false; 4654 for (int i = 0; i < x.length(); i++) 4655 if (Character.isWhitespace(x.charAt(i))) 4656 return false; 4657 return true; 4658 } 4659 4660 private static boolean isValidIpv4(String x) { 4661 // RFC 2673 IPv4 validation 4662 var parts = x.split("\\."); 4663 if (parts.length != 4) 4664 return false; 4665 for (var part : parts) { 4666 try { 4667 int val = Integer.parseInt(part); 4668 if (val < 0 || val > 255) 4669 return false; 4670 } catch (@SuppressWarnings("unused") NumberFormatException e) { 4671 return false; 4672 } 4673 } 4674 return true; 4675 } 4676 4677 private static boolean isValidIpv6(String x) { 4678 // RFC 4291 IPv6 validation (simplified) (programmatic to avoid regex recompilation per HTTP-part value). 4679 // Mirrors the three full-match alternatives: 4680 // ^([0-9a-fA-F]{0,4}+:){7}[0-9a-fA-F]{0,4}$ | ^::([0-9a-fA-F]{0,4}+:){0,6}[0-9a-fA-F]{0,4}$ | ^([0-9a-fA-F]{0,4}+:){1,7}:$ 4681 return isIpv6Full(x) || isIpv6LeadingDoubleColon(x) || isIpv6TrailingColon(x); 4682 } 4683 4684 private static boolean isValidIri(String x) { 4685 // RFC 3987 IRI validation (allows international characters) (programmatic to avoid regex recompilation per HTTP-part value). 4686 // Mirrors prefix match ^[a-zA-Z][a-zA-Z0-9+.-]*:.+ (.+ requires at least one trailing non-line-terminator char). 4687 return isValidScheme(x, true); 4688 } 4689 4690 private static boolean isValidIriReference(String x) { 4691 // RFC 3987 IRI reference (allows international characters) 4692 return !x.isEmpty(); 4693 } 4694 4695 private static boolean isValidJsonPointer(String x) { 4696 // RFC 6901 JSON Pointer validation 4697 return x.isEmpty() || x.charAt(0) == '/'; 4698 } 4699 4700 private boolean isValidMaximum(Number x) { 4701 // Check Draft 2020-12 exclusiveMaximumValue first (takes precedence) 4702 if (nn(exclusiveMaximumValue)) { 4703 if (x instanceof Integer || x instanceof AtomicInteger) 4704 return x.intValue() < exclusiveMaximumValue.intValue(); 4705 if (x instanceof Short || x instanceof Byte) 4706 return x.shortValue() < exclusiveMaximumValue.shortValue(); 4707 if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger) 4708 return x.longValue() < exclusiveMaximumValue.longValue(); 4709 if (x instanceof Float) 4710 return x.floatValue() < exclusiveMaximumValue.floatValue(); 4711 if (x instanceof Double || x instanceof BigDecimal) 4712 return x.doubleValue() < exclusiveMaximumValue.doubleValue(); 4713 } 4714 // Fall back to Draft 04 boolean exclusiveMaximum with maximum 4715 if (x instanceof Integer || x instanceof AtomicInteger) 4716 return maximum == null || x.intValue() < maximum.intValue() || (x.intValue() == maximum.intValue() && (! exclusiveMaximum)); 4717 if (x instanceof Short || x instanceof Byte) 4718 return maximum == null || x.shortValue() < maximum.shortValue() || (x.intValue() == maximum.shortValue() && (! exclusiveMaximum)); 4719 if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger) 4720 return maximum == null || x.longValue() < maximum.longValue() || (x.intValue() == maximum.longValue() && (! exclusiveMaximum)); 4721 if (x instanceof Float) 4722 return maximum == null || x.floatValue() < maximum.floatValue() || (x.floatValue() == maximum.floatValue() && (! exclusiveMaximum)); 4723 if (x instanceof Double || x instanceof BigDecimal) 4724 return maximum == null || x.doubleValue() < maximum.doubleValue() || (x.doubleValue() == maximum.doubleValue() && (! exclusiveMaximum)); 4725 return true; 4726 } 4727 4728 private boolean isValidMaxItems(Collection<?> x) { 4729 return maxItems == null || x.size() <= maxItems; 4730 } 4731 4732 private boolean isValidMaxItems(Object x) { 4733 return maxItems == null || Array.getLength(x) <= maxItems; 4734 } 4735 4736 private boolean isValidMaxLength(String x) { 4737 return maxLength == null || x.length() <= maxLength; 4738 } 4739 4740 private boolean isValidMaxProperties(Map<?,?> x) { 4741 return maxProperties == null || x.size() <= maxProperties; 4742 } 4743 4744 private boolean isValidMinimum(Number x) { 4745 // Check Draft 2020-12 exclusiveMinimumValue first (takes precedence) 4746 if (nn(exclusiveMinimumValue)) { 4747 if (x instanceof Integer || x instanceof AtomicInteger) 4748 return x.intValue() > exclusiveMinimumValue.intValue(); 4749 if (x instanceof Short || x instanceof Byte) 4750 return x.shortValue() > exclusiveMinimumValue.shortValue(); 4751 if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger) 4752 return x.longValue() > exclusiveMinimumValue.longValue(); 4753 if (x instanceof Float) 4754 return x.floatValue() > exclusiveMinimumValue.floatValue(); 4755 if (x instanceof Double || x instanceof BigDecimal) 4756 return x.doubleValue() > exclusiveMinimumValue.doubleValue(); 4757 } 4758 // Fall back to Draft 04 boolean exclusiveMinimum with minimum 4759 if (x instanceof Integer || x instanceof AtomicInteger) 4760 return minimum == null || x.intValue() > minimum.intValue() || (x.intValue() == minimum.intValue() && (! exclusiveMinimum)); 4761 if (x instanceof Short || x instanceof Byte) 4762 return minimum == null || x.shortValue() > minimum.shortValue() || (x.intValue() == minimum.shortValue() && (! exclusiveMinimum)); 4763 if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger) 4764 return minimum == null || x.longValue() > minimum.longValue() || (x.intValue() == minimum.longValue() && (! exclusiveMinimum)); 4765 if (x instanceof Float) 4766 return minimum == null || x.floatValue() > minimum.floatValue() || (x.floatValue() == minimum.floatValue() && (! exclusiveMinimum)); 4767 if (x instanceof Double || x instanceof BigDecimal) 4768 return minimum == null || x.doubleValue() > minimum.doubleValue() || (x.doubleValue() == minimum.doubleValue() && (! exclusiveMinimum)); 4769 return true; 4770 } 4771 4772 private boolean isValidMinItems(Collection<?> x) { 4773 return minItems == null || x.size() >= minItems; 4774 } 4775 4776 private boolean isValidMinItems(Object x) { 4777 return minItems == null || Array.getLength(x) >= minItems; 4778 } 4779 4780 private boolean isValidMinLength(String x) { 4781 return minLength == null || x.length() >= minLength; 4782 } 4783 4784 private boolean isValidMinProperties(Map<?,?> x) { 4785 return minProperties == null || x.size() >= minProperties; 4786 } 4787 4788 private boolean isValidMultipleOf(Number x) { 4789 if (x instanceof Integer || x instanceof AtomicInteger) 4790 return multipleOf == null || x.intValue() % multipleOf.intValue() == 0; 4791 if (x instanceof Short || x instanceof Byte) 4792 return multipleOf == null || x.shortValue() % multipleOf.shortValue() == 0; 4793 if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger) 4794 return multipleOf == null || x.longValue() % multipleOf.longValue() == 0; 4795 if (x instanceof Float) 4796 return multipleOf == null || x.floatValue() % multipleOf.floatValue() == 0; 4797 if (x instanceof Double || x instanceof BigDecimal) 4798 return multipleOf == null || x.doubleValue() % multipleOf.doubleValue() == 0; 4799 return true; 4800 } 4801 4802 private boolean isValidPattern(String x) { 4803 return pattern == null || pattern.matcher(x).matches(); 4804 } 4805 4806 // Upper bound on the length of a value validated as a regular expression. The value here is intentionally 4807 // a caller-authored regex (JSON-Schema "format":"regex"), so it must be compiled rather than quoted; the 4808 // compiled pattern is immediately discarded and never matched against anything. This cap bounds the cost 4809 // of that throwaway compilation so an over-long or deeply-nested pattern can't be used as a DoS vector. 4810 private static final int MAX_REGEX_VALIDATION_LENGTH = 1000; 4811 4812 private static boolean isValidRegex(String x) { 4813 // ECMA-262 regex validation. Reject over-long values before compiling (the compiled Pattern is 4814 // discarded and never used for matching, so bounding length is sufficient to cap the work done here). 4815 if (x.length() > MAX_REGEX_VALIDATION_LENGTH) 4816 return false; 4817 try { 4818 Pattern.compile(x); 4819 return true; 4820 } catch (@SuppressWarnings("unused") Exception e) { 4821 return false; 4822 } 4823 } 4824 4825 private static boolean isValidRelativeJsonPointer(String x) { 4826 // Relative JSON Pointer validation (programmatic to avoid regex stack overflow on large inputs) 4827 int i = 0; 4828 int len = x.length(); 4829 if (i >= len) 4830 return false; 4831 if (x.charAt(i) == '0') { 4832 i++; 4833 } else if (x.charAt(i) >= '1' && x.charAt(i) <= '9') { 4834 i++; 4835 while (i < len && x.charAt(i) >= '0' && x.charAt(i) <= '9') 4836 i++; 4837 } else { 4838 return false; 4839 } 4840 if (i >= len) 4841 return true; 4842 if (x.charAt(i) == '#') 4843 return i + 1 == len; 4844 return x.charAt(i) == '/'; 4845 } 4846 4847 private boolean isValidRequired(Object x) { 4848 return nn(x) || ! required; 4849 } 4850 4851 private static boolean isValidTime(String x) { 4852 // RFC 3339 time (programmatic to avoid regex recompilation per HTTP-part value). 4853 // Mirrors full match ^\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$ (\d = ASCII [0-9]). 4854 var len = x.length(); 4855 var i = scanTimeCore(x); 4856 if (i < 0) 4857 return false; 4858 i = scanOptionalFractionalSeconds(x, i, len); 4859 if (i < 0) 4860 return false; 4861 if (i == len) 4862 return true; // (Z|[+-]\d{2}:\d{2})? matched empty 4863 if (x.charAt(i) == 'Z') 4864 return i + 1 == len; 4865 return scanTimezoneOffset(x, i, len) == len; 4866 } 4867 4868 // Mirrors \d{2}:\d{2}:\d{2} - the time core consumed by isValidTime. Returns the index after the seconds 4869 // field, or -1 if any component or separator is missing/malformed. 4870 private static int scanTimeCore(String x) { 4871 var i = scanDigits(x, 0, 2); 4872 if (i < 0 || ! charIs(x, i, ':')) 4873 return -1; 4874 i = scanDigits(x, i + 1, 2); 4875 if (i < 0 || ! charIs(x, i, ':')) 4876 return -1; 4877 return scanDigits(x, i + 1, 2); 4878 } 4879 4880 private boolean isValidUniqueItems(Collection<?> x) { 4881 if (uniqueItems && ! (x instanceof Set)) { 4882 var s = new HashSet<>(); 4883 for (var o : x) 4884 if (! s.add(o)) 4885 return false; 4886 } 4887 return true; 4888 } 4889 4890 private boolean isValidUniqueItems(Object x) { 4891 if (uniqueItems) { 4892 var s = new HashSet<>(); 4893 for (var i = 0; i < Array.getLength(x); i++) { 4894 var o = Array.get(x, i); 4895 if (! s.add(o)) 4896 return false; 4897 } 4898 } 4899 return true; 4900 } 4901 4902 private static boolean isValidUri(String x) { 4903 // RFC 3986 URI validation 4904 try { 4905 new java.net.URI(x); 4906 // Mirrors prefix match ^[a-zA-Z][a-zA-Z0-9+.-]*:.* (.* permits an empty trailing, non-line-terminator part). 4907 return isValidScheme(x, false); 4908 } catch (Exception e) { 4909 return false; 4910 } 4911 } 4912 4913 private static boolean isValidUriReference(String x) { 4914 // RFC 3986 URI reference (can be relative) 4915 try { 4916 new java.net.URI(x); 4917 return true; 4918 } catch (Exception e) { 4919 return false; 4920 } 4921 } 4922 4923 private static boolean isValidUriTemplate(String x) { 4924 // RFC 6570 URI Template validation (simplified) (programmatic to avoid regex recompilation per HTTP-part value). 4925 // Mirrors full match ^[^\s]*$ - every char must be a non-whitespace char (\s = ASCII whitespace). 4926 for (var i = 0; i < x.length(); i++) 4927 if (isRegexWhitespace(x.charAt(i))) 4928 return false; 4929 return true; 4930 } 4931 4932 private static boolean isValidUuid(String x) { 4933 // RFC 4122 UUID validation (programmatic to avoid regex recompilation per HTTP-part value). 4934 // Mirrors full match ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$. 4935 if (x.length() != 36) 4936 return false; 4937 for (var i = 0; i < 36; i++) { 4938 var c = x.charAt(i); 4939 if (i == 8 || i == 13 || i == 18 || i == 23) { 4940 if (c != '-') 4941 return false; 4942 } else if (! isAsciiHexDigit(c)) { 4943 return false; 4944 } 4945 } 4946 return true; 4947 } 4948 4949 private static boolean resolve(Boolean b) { 4950 return b != null && b; 4951 } 4952}