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.swagger; 014 015import static org.apache.juneau.internal.StringUtils.*; 016import static org.apache.juneau.internal.CollectionUtils.*; 017import static org.apache.juneau.internal.ObjectUtils.*; 018 019import java.util.*; 020 021import org.apache.juneau.annotation.*; 022import org.apache.juneau.internal.*; 023import org.apache.juneau.utils.*; 024 025/** 026 * Describes a single response from an API Operation. 027 * 028 * <h5 class='section'>Example:</h5> 029 * <p class='bcode w800'> 030 * <jc>// Construct using SwaggerBuilder.</jc> 031 * ResponseInfo x = <jsm>responseInfo</jsm>(<js>"A complex object array response"</js>) 032 * .schema( 033 * <jsm>schemaInfo</jsm> 034 * .type(<js>"array"</js>) 035 * .items( 036 * <jsm>items</jsm>() 037 * .set(<js>"$ref"</js>, <js>"#/definitions/VeryComplexType"</js>) 038 * ) 039 * ); 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 w800'> 048 * <jc>// Output</jc> 049 * { 050 * <js>"description"</js>: <js>"A complex object array response"</js>, 051 * <js>"schema"</js>: { 052 * <js>"type"</js>: <js>"array"</js>, 053 * <js>"items"</js>: { 054 * <js>"$ref"</js>: <js>"#/definitions/VeryComplexType"</js> 055 * } 056 * } 057 * } 058 * </p> 059 * 060 * <ul class='seealso'> 061 * <li class='link'>{@doc juneau-dto.Swagger} 062 * </ul> 063 */ 064@Bean(bpi="description,schema,headers,x-example,examples,*") 065public class ResponseInfo extends SwaggerElement { 066 067 private String description; 068 private SchemaInfo schema; 069 private Map<String,HeaderInfo> headers; 070 private Object example; 071 private Map<String,Object> examples; 072 073 /** 074 * Default constructor. 075 */ 076 public ResponseInfo() {} 077 078 /** 079 * Copy constructor. 080 * 081 * @param copyFrom The object to copy. 082 */ 083 public ResponseInfo(ResponseInfo copyFrom) { 084 super(copyFrom); 085 086 this.description = copyFrom.description; 087 this.schema = copyFrom.schema == null ? null : copyFrom.schema.copy(); 088 089 if (copyFrom.headers == null) { 090 this.headers = null; 091 } else { 092 this.headers = new LinkedHashMap<>(); 093 for (Map.Entry<String,HeaderInfo> e : copyFrom.headers.entrySet()) 094 this.headers.put(e.getKey(), e.getValue().copy()); 095 } 096 097 this.example = copyFrom.example; 098 099 if (copyFrom.examples == null) 100 this.examples = null; 101 else 102 this.examples = new LinkedHashMap<>(copyFrom.examples); 103 } 104 105 /** 106 * Make a deep copy of this object. 107 * 108 * @return A deep copy of this object. 109 */ 110 public ResponseInfo copy() { 111 return new ResponseInfo(this); 112 } 113 114 /** 115 * Copies any non-null fields from the specified object to this object. 116 * 117 * @param r 118 * The object to copy fields from. 119 * <br>Can be <jk>null</jk>. 120 * @return This object (for method chaining). 121 */ 122 public ResponseInfo copyFrom(ResponseInfo r) { 123 if (r != null) { 124 if (r.description != null) 125 description = r.description; 126 if (r.schema != null) 127 schema = r.schema; 128 if (r.headers != null) 129 headers = r.headers; 130 if (r.example != null) 131 example = r.example; 132 if (r.examples != null) 133 examples = r.examples; 134 } 135 return this; 136 } 137 138 /** 139 * Bean property getter: <property>description</property>. 140 * 141 * <p> 142 * A short description of the response. 143 * 144 * @return The property value, or <jk>null</jk> if it is not set. 145 */ 146 public String getDescription() { 147 return description; 148 } 149 150 /** 151 * Bean property setter: <property>description</property>. 152 * 153 * <p> 154 * A short description of the response. 155 * 156 * @param value 157 * The new value for this property. 158 * <br>{@doc GFM} can be used for rich text representation. 159 * <br>Property value is required. 160 * @return This object (for method chaining). 161 */ 162 public ResponseInfo setDescription(String value) { 163 description = value; 164 return this; 165 } 166 167 /** 168 * Same as {@link #setDescription(String)}. 169 * 170 * @param value 171 * The new value for this property. 172 * <br>Non-String values will be converted to String using <c>toString()</c>. 173 * <br>Can be <jk>null</jk> to unset the property. 174 * @return This object (for method chaining). 175 */ 176 public ResponseInfo description(Object value) { 177 return setDescription(stringify(value)); 178 } 179 180 /** 181 * Bean property getter: <property>schema</property>. 182 * 183 * <p> 184 * A definition of the response structure. 185 * 186 * <ul class='notes'> 187 * <li> 188 * If this field does not exist, it means no content is returned as part of the response. 189 * <li> 190 * As an extension to the {@doc SwaggerSchemaObject Schema Object}, 191 * its root type value may also be <js>"file"</js>. 192 * <li> 193 * This SHOULD be accompanied by a relevant produces mime-type. 194 * </ul> 195 * 196 * @return The property value, or <jk>null</jk> if it is not set. 197 */ 198 public SchemaInfo getSchema() { 199 return schema; 200 } 201 202 /** 203 * Bean property setter: <property>schema</property>. 204 * 205 * <p> 206 * A definition of the response structure. 207 * 208 * <ul class='notes'> 209 * <li> 210 * If this field does not exist, it means no content is returned as part of the response. 211 * <li> 212 * As an extension to the {@doc SwaggerSchemaObject Schema Object}, 213 * its root type value may also be <js>"file"</js>. 214 * <li> 215 * This SHOULD be accompanied by a relevant produces mime-type. 216 * </ul> 217 * 218 * @param value 219 * The new value for this property. 220 * <br>It can be a primitive, an array or an object. 221 * <br>Can be <jk>null</jk> to unset the property. 222 * @return This object (for method chaining). 223 */ 224 public ResponseInfo setSchema(SchemaInfo value) { 225 schema = value; 226 return this; 227 } 228 229 /** 230 * Same as {@link #setSchema(SchemaInfo)}. 231 * 232 * @param value 233 * The new value for this property. 234 * <br>Valid types: 235 * <ul> 236 * <li>{@link SchemaInfo} 237 * <li><c>String</c> - JSON object representation of {@link SchemaInfo} 238 * <p class='bcode w800'> 239 * <jc>// Example </jc> 240 * schema(<js>"{type:'type',description:'description',...}"</js>); 241 * </p> 242 * </ul> 243 * <br>Can be <jk>null</jk> to unset the property. 244 * @return This object (for method chaining). 245 */ 246 public ResponseInfo schema(Object value) { 247 return setSchema(toType(value, SchemaInfo.class)); 248 } 249 250 /** 251 * Bean property getter: <property>headers</property>. 252 * 253 * <p> 254 * A list of headers that are sent with the response. 255 * 256 * @return The property value, or <jk>null</jk> if it is not set. 257 */ 258 public Map<String,HeaderInfo> getHeaders() { 259 return headers; 260 } 261 262 /** 263 * Bean property setter: <property>headers</property>. 264 * 265 * <p> 266 * A list of headers that are sent with the response. 267 * 268 * @param value 269 * The new value for this property. 270 * <br>Can be <jk>null</jk> to unset the property. 271 * @return This object (for method chaining). 272 */ 273 public ResponseInfo setHeaders(Map<String,HeaderInfo> value) { 274 headers = newMap(value); 275 return this; 276 } 277 278 /** 279 * Adds one or more values to the <property>headers</property> property. 280 * 281 * @param values 282 * The values to add to this property. 283 * <br>Ignored if <jk>null</jk>. 284 * @return This object (for method chaining). 285 */ 286 public ResponseInfo addHeaders(Map<String,HeaderInfo> values) { 287 headers = addToMap(headers, values); 288 return this; 289 } 290 291 /** 292 * Adds a single value to the <property>headers</property> property. 293 * 294 * @param name The header name. 295 * @param header The header descriptions 296 * @return This object (for method chaining). 297 */ 298 public ResponseInfo header(String name, HeaderInfo header) { 299 addHeaders(Collections.singletonMap(name, header)); 300 return this; 301 } 302 303 /** 304 * Adds one or more values to the <property>headers</property> property. 305 * 306 * @param values 307 * The values to add to this property. 308 * <br>Valid types: 309 * <ul> 310 * <li><c>Map<String,{@link HeaderInfo}|String></c> 311 * <li><c>String</c> - JSON object representation of <c>Map<String,{@link HeaderInfo}></c> 312 * <p class='bcode w800'> 313 * <jc>// Example </jc> 314 * headers(<js>"{headerName:{description:'description',...}}"</js>); 315 * </p> 316 * </ul> 317 * <br>Ignored if <jk>null</jk>. 318 * @return This object (for method chaining). 319 */ 320 public ResponseInfo headers(Object...values) { 321 headers = addToMap(headers, values, String.class, HeaderInfo.class); 322 return this; 323 } 324 325 /** 326 * Returns the header information with the specified name. 327 * 328 * @param name The header name. 329 * @return The header info, or <jk>null</jk> if not found. 330 */ 331 public HeaderInfo getHeader(String name) { 332 return getHeaders().get(name); 333 } 334 335 /** 336 * Bean property getter: <property>x-example</property>. 337 * 338 * @return The property value, or <jk>null</jk> if it is not set. 339 */ 340 @Beanp("x-example") 341 public Object getExample() { 342 return example; 343 } 344 345 /** 346 * Bean property setter: <property>x-example</property>. 347 * 348 * @param value 349 * The new value for this property. 350 * <br>Can be <jk>null</jk> to unset the property. 351 * @return This object (for method chaining). 352 */ 353 @Beanp("x-example") 354 public ResponseInfo setExample(Object value) { 355 example = value; 356 return this; 357 } 358 359 /** 360 * Bean property setter: <property>x-example</property>. 361 * 362 * @param value The property value. 363 * @return This object (for method chaining). 364 */ 365 public ResponseInfo example(Object value) { 366 example = value; 367 return this; 368 } 369 370 /** 371 * Bean property getter: <property>examples</property>. 372 * 373 * <p> 374 * An example of the response message. 375 * 376 * @return The property value, or <jk>null</jk> if it is not set. 377 */ 378 public Map<String,Object> getExamples() { 379 return examples; 380 } 381 382 /** 383 * Bean property setter: <property>examples</property>. 384 * 385 * <p> 386 * An example of the response message. 387 * 388 * @param value 389 * The new value for this property. 390 * <br>Keys must be MIME-type strings. 391 * <br>Can be <jk>null</jk> to unset the property. 392 * @return This object (for method chaining). 393 */ 394 public ResponseInfo setExamples(Map<String,Object> value) { 395 examples = newMap(value); 396 return this; 397 } 398 399 /** 400 * Adds one or more values to the <property>examples</property> property. 401 * 402 * @param values 403 * The values to add to this property. 404 * <br>Ignored if <jk>null</jk>. 405 * @return This object (for method chaining). 406 */ 407 public ResponseInfo addExamples(Map<String,Object> values) { 408 examples = addToMap(examples, values); 409 return this; 410 } 411 412 /** 413 * Adds a single value to the <property>examples</property> property. 414 * 415 * @param mimeType The mime-type string. 416 * @param example The example. 417 * @return This object (for method chaining). 418 */ 419 public ResponseInfo example(String mimeType, Object example) { 420 examples = addToMap(examples, mimeType, example); 421 return this; 422 } 423 424 /** 425 * Adds one or more values to the <property>examples</property> property. 426 * 427 * @param values 428 * The values to add to this property. 429 * <br>Valid types: 430 * <ul> 431 * <li><c>Map<String,Object></c> 432 * <li><c>String</c> - JSON object representation of <c>Map<String,Object></c> 433 * <p class='bcode w800'> 434 * <jc>// Example </jc> 435 * examples(<js>"{'text/json':{foo:'bar'}}"</js>); 436 * </p> 437 * </ul> 438 * <br>Ignored if <jk>null</jk>. 439 * @return This object (for method chaining). 440 */ 441 public ResponseInfo examples(Object...values) { 442 examples = addToMap(examples, values, String.class, Object.class); 443 return this; 444 } 445 446 @Override /* SwaggerElement */ 447 public <T> T get(String property, Class<T> type) { 448 if (property == null) 449 return null; 450 switch (property) { 451 case "description": return toType(getDescription(), type); 452 case "schema": return toType(getSchema(), type); 453 case "headers": return toType(getHeaders(), type); 454 case "example": return toType(getExample(), type); 455 case "examples": return toType(getExamples(), type); 456 default: return super.get(property, type); 457 } 458 } 459 460 @Override /* SwaggerElement */ 461 public ResponseInfo set(String property, Object value) { 462 if (property == null) 463 return this; 464 switch (property) { 465 case "description": return description(value); 466 case "schema": return schema(value); 467 case "headers": return setHeaders(null).headers(value); 468 case "example": return setExample(value); 469 case "examples": return setExamples(null).examples(value); 470 default: 471 super.set(property, value); 472 return this; 473 } 474 } 475 476 @Override /* SwaggerElement */ 477 public Set<String> keySet() { 478 ASet<String> s = new ASet<String>() 479 .appendIf(description != null, "description") 480 .appendIf(schema != null, "schema") 481 .appendIf(headers != null, "headers") 482 .appendIf(example != null, "example") 483 .appendIf(examples != null, "examples"); 484 return new MultiSet<>(s, super.keySet()); 485 } 486 487 /** 488 * Returns <jk>true</jk> if this response info has headers associated with it. 489 * 490 * @return <jk>true</jk> if this response info has headers associated with it. 491 */ 492 public boolean hasHeaders() { 493 return headers != null && ! headers.isEmpty(); 494 } 495 496 /** 497 * Resolves any <js>"$ref"</js> attributes in this element. 498 * 499 * @param swagger The swagger document containing the definitions. 500 * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops. 501 * @param maxDepth 502 * The maximum depth to resolve references. 503 * <br>After that level is reached, <c>$ref</c> references will be left alone. 504 * <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex. 505 * @return 506 * This object with references resolved. 507 * <br>May or may not be the same object. 508 */ 509 public ResponseInfo resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) { 510 511 if (schema != null) 512 schema = schema.resolveRefs(swagger, refStack, maxDepth); 513 514 if (headers != null) 515 for (Map.Entry<String,HeaderInfo> e : headers.entrySet()) 516 e.setValue(e.getValue().resolveRefs(swagger, refStack, maxDepth)); 517 518 return this; 519 } 520}