001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.dto.openapi3; 014 015import static org.apache.juneau.internal.ConverterUtils.*; 016 017import org.apache.juneau.*; 018import org.apache.juneau.annotation.*; 019import org.apache.juneau.collections.*; 020import org.apache.juneau.dto.swagger.Swagger; 021import org.apache.juneau.internal.*; 022import org.apache.juneau.json.Json5Serializer; 023 024import java.util.*; 025 026import static org.apache.juneau.common.internal.StringUtils.*; 027import static org.apache.juneau.internal.ArrayUtils.contains; 028import static org.apache.juneau.internal.CollectionUtils.*; 029 030/** 031 * A limited subset of JSON-Schema's items object. 032 * 033 * <p> 034 * It is used by parameter definitions that are not located in "body". 035 * 036 * <h5 class='section'>Example:</h5> 037 * <p class='bcode'> 038 * <jc>// Construct using SwaggerBuilder.</jc> 039 * Items x = <jsm>items</jsm>(<js>"string"</js>).minLength(2); 040 * 041 * <jc>// Serialize using JsonSerializer.</jc> 042 * String json = JsonSerializer.<jsf>DEFAULT</jsf>.toString(x); 043 * 044 * <jc>// Or just use toString() which does the same as above.</jc> 045 * String json = x.toString(); 046 * </p> 047 * <p class='bcode'> 048 * <jc>// Output</jc> 049 * { 050 * <js>"type"</js>: <js>"string"</js>, 051 * <js>"minLength"</js>: 2 052 * } 053 * </p> 054 */ 055@Bean(properties="type,format,items,collectionFormat,default,maximum,exclusiveMaximum,minimum,exclusiveMinimum,maxLength,minLength,pattern,maxItems,minItems,uniqueItems,enum,multipleOf,$ref,*") 056@FluentSetters 057public class Items extends OpenApiElement { 058 059 private static final String[] VALID_TYPES = {"string", "number", "integer", "boolean", "array"}; 060 private static final String[] VALID_COLLECTION_FORMATS = {"csv","ssv","tsv","pipes","multi"}; 061 062 private String 063 type, 064 format, 065 collectionFormat, 066 pattern, 067 ref; 068 private Number 069 maximum, 070 minimum, 071 multipleOf; 072 private Integer 073 maxLength, 074 minLength, 075 maxItems, 076 minItems; 077 private Boolean 078 exclusiveMaximum, 079 exclusiveMinimum, 080 uniqueItems; 081 private Items items; 082 private Object _default; 083 private List<Object> _enum; 084 085 /** 086 * Default constructor. 087 */ 088 public Items() {} 089 090 /** 091 * Copy constructor. 092 * 093 * @param copyFrom The object to copy. 094 */ 095 public Items(Items copyFrom) { 096 super(copyFrom); 097 098 this.type = copyFrom.type; 099 this.format = copyFrom.format; 100 this.collectionFormat = copyFrom.collectionFormat; 101 this.pattern = copyFrom.pattern; 102 this.maximum = copyFrom.maximum; 103 this.minimum = copyFrom.minimum; 104 this.multipleOf = copyFrom.multipleOf; 105 this.maxLength = copyFrom.maxLength; 106 this.minLength = copyFrom.minLength; 107 this.maxItems = copyFrom.maxItems; 108 this.minItems = copyFrom.minItems; 109 this.exclusiveMaximum = copyFrom.exclusiveMaximum; 110 this.exclusiveMinimum = copyFrom.exclusiveMinimum; 111 this.uniqueItems = copyFrom.uniqueItems; 112 this.items = copyFrom.items == null ? null : copyFrom.items.copy(); 113 this._default = copyFrom._default; 114 this._enum = copyOf(copyFrom._enum); 115 this.ref = copyFrom.ref; 116 } 117 118 /** 119 * Make a deep copy of this object. 120 * 121 * @return A deep copy of this object. 122 */ 123 public Items copy() { 124 return new Items(this); 125 } 126 127 128 @Override /* SwaggerElement */ 129 protected Items strict() { 130 super.strict(); 131 return this; 132 } 133 134 /** 135 * Bean property getter: <property>type</property>. 136 * 137 * <p> 138 * The internal type of the array. 139 * 140 * @return The property value, or <jk>null</jk> if it is not set. 141 */ 142 public String getType() { 143 return type; 144 } 145 146 /** 147 * Bean property setter: <property>type</property>. 148 * 149 * <p> 150 * The internal type of the array. 151 * 152 * @param value 153 * The new value for this property. 154 * <br>Valid values: 155 * <ul> 156 * <li><js>"string"</js> 157 * <li><js>"number"</js> 158 * <li><js>"integer"</js> 159 * <li><js>"boolean"</js> 160 * <li><js>"array"</js> 161 * </ul> 162 * <br>Property value is required. 163 * @return This object 164 */ 165 public Items setType(String value) { 166 if (isStrict() && ! contains(value, VALID_TYPES)) 167 throw new IllegalArgumentException( 168 "Invalid value passed in to setType(String). Value='"+value+"', valid values=" 169 + Json5Serializer.DEFAULT.toString(VALID_TYPES)); 170 type = value; 171 return this; 172 } 173 174 /** 175 * Bean property getter: <property>format</property>. 176 * 177 * <p> 178 * The extending format for the previously mentioned <code>type</code>. 179 * 180 * @return The property value, or <jk>null</jk> if it is not set. 181 */ 182 public String getFormat() { 183 return format; 184 } 185 186 /** 187 * Bean property setter: <property>format</property>. 188 * 189 * <p> 190 * The extending format for the previously mentioned <code>type</code>. 191 * 192 * @param value 193 * The new value for this property. 194 * <br>Can be <jk>null</jk> to unset the property. 195 * @return This object 196 */ 197 public Items setFormat(String value) { 198 format = value; 199 return this; 200 } 201 202 /** 203 * Bean property getter: <property>items</property>. 204 * 205 * <p> 206 * Describes the type of items in the array. 207 * 208 * @return The property value, or <jk>null</jk> if it is not set. 209 */ 210 public Items getItems() { 211 return items; 212 } 213 214 /** 215 * Bean property setter: <property>items</property>. 216 * 217 * <p> 218 * Describes the type of items in the array. 219 * 220 * @param value 221 * The new value for this property. 222 * <br>Property value is required if <code>type</code> is <js>"array"</js>. 223 * <br>Can be <jk>null</jk> to unset the property. 224 * @return This object 225 */ 226 public Items setItems(Items value) { 227 items = value; 228 return this; 229 } 230 231 /** 232 * Bean property getter: <property>collectionFormat</property>. 233 * 234 * <p> 235 * Determines the format of the array if type array is used. 236 * 237 * @return The property value, or <jk>null</jk> if it is not set. 238 */ 239 public String getCollectionFormat() { 240 return collectionFormat; 241 } 242 243 /** 244 * Bean property setter: <property>collectionFormat</property>. 245 * 246 * <p> 247 * Determines the format of the array if type array is used. 248 * 249 * @param value 250 * The new value for this property. 251 * <br>Valid values: 252 * <ul> 253 * <li><js>"csv"</js> (default) - comma separated values <code>foo,bar</code>. 254 * <li><js>"ssv"</js> - space separated values <code>foo bar</code>. 255 * <li><js>"tsv"</js> - tab separated values <code>foo\tbar</code>. 256 * <li><js>"pipes"</js> - pipe separated values <code>foo|bar</code>. 257 * </ul> 258 * <br>Can be <jk>null</jk> to unset the property. 259 * @return This object 260 */ 261 public Items setCollectionFormat(String value) { 262 if (isStrict() && ! contains(value, VALID_COLLECTION_FORMATS)) 263 throw new BasicRuntimeException( 264 "Invalid value passed in to setCollectionFormat(String). Value=''{0}'', valid values={1}", 265 value, VALID_COLLECTION_FORMATS 266 ); 267 collectionFormat = value; 268 return this; 269 } 270 271 /** 272 * Bean property getter: <property>default</property>. 273 * 274 * <p> 275 * Declares the value of the item that the server will use if none is provided. 276 * 277 * <h5 class='section'>Notes:</h5> 278 * <ul class='spaced-list'> 279 * <li> 280 * <js>"default"</js> has no meaning for required items. 281 * <li> 282 * Unlike JSON Schema this value MUST conform to the defined <code>type</code> for the data type. 283 * </ul> 284 * 285 * @return The property value, or <jk>null</jk> if it is not set. 286 */ 287 public Object getDefault() { 288 return _default; 289 } 290 291 /** 292 * Bean property setter: <property>default</property>. 293 * 294 * <p> 295 * Declares the value of the item that the server will use if none is provided. 296 * 297 * <h5 class='section'>Notes:</h5> 298 * <ul class='spaced-list'> 299 * <li> 300 * <js>"default"</js> has no meaning for required items. 301 * <li> 302 * Unlike JSON Schema this value MUST conform to the defined <code>type</code> for the data type. 303 * </ul> 304 * 305 * @param value 306 * The new value for this property. 307 * <br>Can be <jk>null</jk> to unset the property. 308 * @return This object 309 */ 310 public Items setDefault(Object value) { 311 _default = value; 312 return this; 313 } 314 315 /** 316 * Bean property getter: <property>maximum</property>. 317 * 318 * @return The property value, or <jk>null</jk> if it is not set. 319 */ 320 public Number getMaximum() { 321 return maximum; 322 } 323 324 /** 325 * Bean property setter: <property>maximum</property>. 326 * 327 * @param value 328 * The new value for this property. 329 * <br>Can be <jk>null</jk> to unset the property. 330 * @return This object 331 */ 332 public Items setMaximum(Number value) { 333 maximum = value; 334 return this; 335 } 336 337 /** 338 * Bean property getter: <property>exclusiveMaximum</property>. 339 * 340 * @return The property value, or <jk>null</jk> if it is not set. 341 */ 342 public Boolean getExclusiveMaximum() { 343 return exclusiveMaximum; 344 } 345 346 /** 347 * Bean property setter: <property>exclusiveMaximum</property>. 348 * 349 * @param value 350 * The new value for this property. 351 * <br>Can be <jk>null</jk> to unset the property. 352 * @return This object 353 */ 354 public Items setExclusiveMaximum(Boolean value) { 355 exclusiveMaximum = value; 356 return this; 357 } 358 359 /** 360 * Bean property getter: <property>minimum</property>. 361 * 362 * @return The property value, or <jk>null</jk> if it is not set. 363 */ 364 public Number getMinimum() { 365 return minimum; 366 } 367 368 /** 369 * Bean property setter: <property>minimum</property>. 370 * 371 * @param value 372 * The new value for this property. 373 * <br>Can be <jk>null</jk> to unset the property. 374 * @return This object 375 */ 376 public Items setMinimum(Number value) { 377 minimum = value; 378 return this; 379 } 380 381 /** 382 * Bean property getter: <property>exclusiveMinimum</property>. 383 * 384 * @return The property value, or <jk>null</jk> if it is not set. 385 */ 386 public Boolean getExclusiveMinimum() { 387 return exclusiveMinimum; 388 } 389 390 /** 391 * Bean property setter: <property>exclusiveMinimum</property>. 392 * 393 * @param value 394 * The new value for this property. 395 * <br>Can be <jk>null</jk> to unset the property. 396 * @return This object 397 */ 398 public Items setExclusiveMinimum(Boolean value) { 399 exclusiveMinimum = value; 400 return this; 401 } 402 403 /** 404 * Bean property getter: <property>maxLength</property>. 405 * 406 * @return The property value, or <jk>null</jk> if it is not set. 407 */ 408 public Integer getMaxLength() { 409 return maxLength; 410 } 411 412 /** 413 * Bean property setter: <property>maxLength</property>. 414 * 415 * @param value 416 * The new value for this property. 417 * <br>Can be <jk>null</jk> to unset the property. 418 * @return This object 419 */ 420 public Items setMaxLength(Integer value) { 421 maxLength = value; 422 return this; 423 } 424 425 /** 426 * Bean property getter: <property>minLength</property>. 427 * 428 * @return The property value, or <jk>null</jk> if it is not set. 429 */ 430 public Integer getMinLength() { 431 return minLength; 432 } 433 434 /** 435 * Bean property setter: <property>minLength</property>. 436 * 437 * @param value 438 * The new value for this property. 439 * <br>Can be <jk>null</jk> to unset the property. 440 * @return This object 441 */ 442 public Items setMinLength(Integer value) { 443 minLength = value; 444 return this; 445 } 446 447 /** 448 * Bean property getter: <property>pattern</property>. 449 * 450 * @return The property value, or <jk>null</jk> if it is not set. 451 */ 452 public String getPattern() { 453 return pattern; 454 } 455 456 /** 457 * Bean property setter: <property>pattern</property>. 458 * 459 * <p> 460 * This string SHOULD be a valid regular expression. 461 * 462 * @param value 463 * The new value for this property. 464 * <br>Can be <jk>null</jk> to unset the property. 465 * @return This object 466 */ 467 public Items setPattern(String value) { 468 pattern = value; 469 return this; 470 } 471 472 /** 473 * Bean property getter: <property>maxItems</property>. 474 * 475 * @return The property value, or <jk>null</jk> if it is not set. 476 */ 477 public Integer getMaxItems() { 478 return maxItems; 479 } 480 481 /** 482 * Bean property setter: <property>maxItems</property>. 483 * 484 * @param value 485 * The new value for this property. 486 * <br>Can be <jk>null</jk> to unset the property. 487 * @return This object 488 */ 489 public Items setMaxItems(Integer value) { 490 maxItems = value; 491 return this; 492 } 493 494 /** 495 * Bean property getter: <property>minItems</property>. 496 * 497 * @return The property value, or <jk>null</jk> if it is not set. 498 */ 499 public Integer getMinItems() { 500 return minItems; 501 } 502 503 /** 504 * Bean property setter: <property>minItems</property>. 505 * 506 * @param value 507 * The new value for this property. 508 * <br>Can be <jk>null</jk> to unset the property. 509 * @return This object 510 */ 511 public Items setMinItems(Integer value) { 512 minItems = value; 513 return this; 514 } 515 516 /** 517 * Bean property getter: <property>uniqueItems</property>. 518 * 519 * @return The property value, or <jk>null</jk> if it is not set. 520 */ 521 public Boolean getUniqueItems() { 522 return uniqueItems; 523 } 524 525 /** 526 * Bean property setter: <property>uniqueItems</property>. 527 * 528 * @param value 529 * The new value for this property. 530 * <br>Can be <jk>null</jk> to unset the property. 531 * @return This object 532 */ 533 public Items setUniqueItems(Boolean value) { 534 uniqueItems = value; 535 return this; 536 } 537 538 /** 539 * Bean property getter: <property>enum</property>. 540 * 541 * @return The property value, or <jk>null</jk> if it is not set. 542 */ 543 public List<Object> getEnum() { 544 return _enum; 545 } 546 547 /** 548 * Bean property setter: <property>enum</property>. 549 * 550 * @param value 551 * The new value for this property. 552 * <br>Can be <jk>null</jk> to unset the property. 553 * @return This object 554 */ 555 public Items setEnum(Collection<Object> value) { 556 _enum = listFrom(value); 557 return this; 558 } 559 560 /** 561 * Adds one or more values to the <property>enum</property> property. 562 * 563 * @param values 564 * The values to add to this property. 565 * <br>Ignored if <jk>null</jk>. 566 * @return This object 567 */ 568 public Items addEnum(Object...values) { 569 _enum = listBuilder(_enum).sparse().addAny(values).build(); 570 return this; 571 } 572 573 /** 574 * Adds one or more values to the <property>enum</property> property. 575 * 576 * @param values 577 * The values to add to this property. 578 * <br>Valid types: 579 * <ul> 580 * <li><code>Object</code> 581 * <li><code>Collection<Object></code> 582 * <li><code>String</code> - JSON array representation of <code>Collection<Object></code> 583 * <h5 class='figure'>Example:</h5> 584 * <p class='bcode'> 585 * _enum(<js>"['foo','bar']"</js>); 586 * </p> 587 * <li><code>String</code> - Individual values 588 * <h5 class='figure'>Example:</h5> 589 * <p class='bcode'> 590 * _enum(<js>"foo"</js>, <js>"bar"</js>); 591 * </p> 592 * </ul> 593 * <br>Ignored if <jk>null</jk>. 594 * @return This object 595 */ 596 public Items setEnum(Object...values) { 597 _enum = listBuilder(_enum).sparse().addAny(values).build(); 598 return this; 599 } 600 601 /** 602 * Bean property getter: <property>multipleOf</property>. 603 * 604 * @return The property value, or <jk>null</jk> if it is not set. 605 */ 606 public Number getMultipleOf() { 607 return multipleOf; 608 } 609 610 /** 611 * Bean property setter: <property>multipleOf</property>. 612 * 613 * @param value 614 * The new value for this property. 615 * <br>Can be <jk>null</jk> to unset the property. 616 * @return This object 617 */ 618 public Items setMultipleOf(Number value) { 619 multipleOf = value; 620 return this; 621 } 622 623 /** 624 * Bean property getter: <property>$ref</property>. 625 * 626 * @return The property value, or <jk>null</jk> if it is not set. 627 */ 628 @Beanp("$ref") 629 public String getRef() { 630 return ref; 631 } 632 633 /** 634 * Bean property setter: <property>$ref</property>. 635 * 636 * @param value 637 * The new value for this property. 638 * <br>Can be <jk>null</jk> to unset the property. 639 * @return This object 640 */ 641 @Beanp("$ref") 642 public Items setRef(Object value) { 643 ref = stringify(value); 644 return this; 645 } 646 647 /** 648 * Same as {@link #setRef(Object)}. 649 * 650 * @param value 651 * The new value for this property. 652 * <br>Can be <jk>null</jk> to unset the property. 653 * @return This object 654 */ 655 public Items ref(Object value) { 656 return setRef(value); 657 } 658 659 // <FluentSetters> 660 661 // </FluentSetters> 662 663 @Override /* SwaggerElement */ 664 public <T> T get(String property, Class<T> type) { 665 if (property == null) 666 return null; 667 switch (property) { 668 case "type": return toType(getType(), type); 669 case "format": return toType(getFormat(), type); 670 case "items": return toType(getItems(), type); 671 case "collectionFormat": return toType(getCollectionFormat(), type); 672 case "default": return toType(getDefault(), type); 673 case "maximum": return toType(getMaximum(), type); 674 case "exclusiveMaximum": return toType(getExclusiveMaximum(), type); 675 case "minimum": return toType(getMinimum(), type); 676 case "exclusiveMinimum": return toType(getExclusiveMinimum(), type); 677 case "maxLength": return toType(getMaxLength(), type); 678 case "minLength": return toType(getMinLength(), type); 679 case "pattern": return toType(getPattern(), type); 680 case "maxItems": return toType(getMaxItems(), type); 681 case "minItems": return toType(getMinItems(), type); 682 case "uniqueItems": return toType(getUniqueItems(), type); 683 case "enum": return toType(getEnum(), type); 684 case "multipleOf": return toType(getMultipleOf(), type); 685 case "$ref": return toType(getRef(), type); 686 default: return super.get(property, type); 687 } 688 } 689 690 @Override /* SwaggerElement */ 691 public Items set(String property, Object value) { 692 if (property == null) 693 return this; 694 switch (property) { 695 case "type": return setType(stringify(value)); 696 case "format": return setFormat(stringify(value)); 697 case "items": return setItems(toType(value,Items.class)); 698 case "collectionFormat": return setCollectionFormat(stringify(value)); 699 case "default": return setDefault(value); 700 case "maximum": return setMaximum(toNumber(value)); 701 case "exclusiveMaximum": return setExclusiveMaximum(toBoolean(value)); 702 case "minimum": return setMinimum(toNumber(value)); 703 case "exclusiveMinimum": return setExclusiveMinimum(toBoolean(value)); 704 case "maxLength": return setMaxLength(toInteger(value)); 705 case "minLength": return setMinLength(toInteger(value)); 706 case "pattern": return setPattern(stringify(value)); 707 case "maxItems": return setMaxItems(toInteger(value)); 708 case "minItems": return setMinItems(toInteger(value)); 709 case "uniqueItems": return setUniqueItems(toBoolean(value)); 710 case "enum": return setEnum(value); 711 case "multipleOf": return setMultipleOf(toNumber(value)); 712 case "$ref": return ref(value); 713 default: 714 super.set(property, value); 715 return this; 716 } 717 } 718 719 @Override /* SwaggerElement */ 720 public Set<String> keySet() { 721 Set<String> s = setBuilder(String.class) 722 .addIf(type != null, "type") 723 .addIf(format != null, "format") 724 .addIf(items != null, "items") 725 .addIf(collectionFormat != null, "collectionFormat") 726 .addIf(_default != null, "default") 727 .addIf(maximum != null, "maximum") 728 .addIf(exclusiveMaximum != null, "exclusiveMaximum") 729 .addIf(minimum != null, "minimum") 730 .addIf(exclusiveMinimum != null, "exclusiveMinimum") 731 .addIf(maxLength != null, "maxLength") 732 .addIf(minLength != null, "minLength") 733 .addIf(pattern != null, "pattern") 734 .addIf(maxItems != null, "maxItems") 735 .addIf(minItems != null, "minItems") 736 .addIf(uniqueItems != null, "uniqueItems") 737 .addIf(_enum != null, "enum") 738 .addIf(multipleOf != null, "multipleOf") 739 .addIf(ref != null, "$ref") 740 .build(); 741 return new MultiSet<>(s, super.keySet()); 742 } 743 744 /** 745 * Resolves any <js>"$ref"</js> attributes in this element. 746 * 747 * @param swagger The swagger document containing the definitions. 748 * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops. 749 * @param maxDepth 750 * The maximum depth to resolve references. 751 * <br>After that level is reached, <code>$ref</code> references will be left alone. 752 * <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex. 753 * @return 754 * This object with references resolved. 755 * <br>May or may not be the same object. 756 */ 757 public Items resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) { 758 759 if (ref != null) { 760 if (refStack.contains(ref) || refStack.size() >= maxDepth) 761 return this; 762 refStack.addLast(ref); 763 Items r = swagger.findRef(ref, Items.class).resolveRefs(swagger, refStack, maxDepth); 764 refStack.removeLast(); 765 return r; 766 } 767 768 set("properties", resolveRefs(get("properties"), swagger, refStack, maxDepth)); 769 770 if (items != null) 771 items = items.resolveRefs(swagger, refStack, maxDepth); 772 773 set("example", null); 774 775 return this; 776 } 777 778 /* Resolve references in extra attributes */ 779 private Object resolveRefs(Object o, Swagger swagger, Deque<String> refStack, int maxDepth) { 780 if (o instanceof JsonMap) { 781 JsonMap om = (JsonMap)o; 782 Object ref = om.get("$ref"); 783 if (ref instanceof CharSequence) { 784 String sref = ref.toString(); 785 if (refStack.contains(sref) || refStack.size() >= maxDepth) 786 return o; 787 refStack.addLast(sref); 788 Object o2 = swagger.findRef(sref, Object.class); 789 o2 = resolveRefs(o2, swagger, refStack, maxDepth); 790 refStack.removeLast(); 791 return o2; 792 } 793 for (Map.Entry<String,Object> e : om.entrySet()) 794 e.setValue(resolveRefs(e.getValue(), swagger, refStack, maxDepth)); 795 } 796 if (o instanceof JsonList) 797 for (ListIterator<Object> li = ((JsonList)o).listIterator(); li.hasNext();) 798 li.set(resolveRefs(li.next(), swagger, refStack, maxDepth)); 799 return o; 800 } 801}