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.bean.swagger; 018 019import static org.apache.juneau.commons.utils.AssertionUtils.*; 020import static org.apache.juneau.commons.utils.CollectionUtils.*; 021import static org.apache.juneau.commons.utils.Utils.*; 022import static org.apache.juneau.internal.ConverterUtils.*; 023 024import java.util.*; 025 026import org.apache.juneau.commons.collections.*; 027 028/** 029 * Describes a single response from an API operation. 030 * 031 * <p> 032 * The Response Object describes a single response from a Swagger 2.0 API operation, including a description, 033 * schema, headers, and examples. Responses are associated with HTTP status codes (e.g., 200, 404, 500). 034 * 035 * <h5 class='section'>Swagger Specification:</h5> 036 * <p> 037 * The Response Object is composed of the following fields: 038 * <ul class='spaced-list'> 039 * <li><c>description</c> (string, REQUIRED) - A short description of the response 040 * <li><c>schema</c> ({@link SchemaInfo}) - A definition of the response structure 041 * <li><c>headers</c> (map of {@link HeaderInfo}) - Maps a header name to its definition 042 * <li><c>examples</c> (map of any) - An example of the response message (keys are media types) 043 * </ul> 044 * 045 * <h5 class='section'>Example:</h5> 046 * <p class='bjava'> 047 * <jc>// Construct using SwaggerBuilder.</jc> 048 * ResponseInfo <jv>info</jv> = <jsm>responseInfo</jsm>(<js>"A complex object array response"</js>) 049 * .schema( 050 * <jsm>schemaInfo</jsm> 051 * .type(<js>"array"</js>) 052 * .items( 053 * <jsm>items</jsm>() 054 * .set(<js>"$ref"</js>, <js>"#/definitions/VeryComplexType"</js>) 055 * ) 056 * ); 057 * 058 * <jc>// Serialize using JsonSerializer.</jc> 059 * String <jv>json</jv> = Json.<jsm>from</jsm>(<jv>info</jv>); 060 * 061 * <jc>// Or just use toString() which does the same as above.</jc> 062 * <jv>json</jv> = <jv>info</jv>.toString(); 063 * </p> 064 * <p class='bjson'> 065 * <jc>// Output</jc> 066 * { 067 * <js>"description"</js>: <js>"A complex object array response"</js>, 068 * <js>"schema"</js>: { 069 * <js>"type"</js>: <js>"array"</js>, 070 * <js>"items"</js>: { 071 * <js>"$ref"</js>: <js>"#/definitions/VeryComplexType"</js> 072 * } 073 * } 074 * } 075 * </p> 076 * 077 * <h5 class='section'>See Also:</h5><ul> 078 * <li class='link'><a class="doclink" href="https://swagger.io/specification/v2/#response-object">Swagger 2.0 Specification > Response Object</a> 079 * <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/2-0/describing-responses/">Swagger Describing Responses</a> 080 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanSwagger2">juneau-bean-swagger-v2</a> 081 * </ul> 082 */ 083public class ResponseInfo extends SwaggerElement { 084 085 private String description; 086 private SchemaInfo schema; 087 private Map<String,HeaderInfo> headers = map(); 088 private Map<String,Object> examples = map(); 089 090 /** 091 * Default constructor. 092 */ 093 public ResponseInfo() {} 094 095 /** 096 * Copy constructor. 097 * 098 * @param copyFrom The object to copy. 099 */ 100 public ResponseInfo(ResponseInfo copyFrom) { 101 super(copyFrom); 102 103 this.description = copyFrom.description; 104 this.schema = copyFrom.schema == null ? null : copyFrom.schema.copy(); 105 if (nn(copyFrom.examples)) 106 examples.putAll(copyOf(copyFrom.examples)); 107 if (nn(copyFrom.headers)) 108 headers.putAll(copyOf(copyFrom.headers, HeaderInfo::copy)); 109 } 110 111 /** 112 * Bean property appender: <property>examples</property>. 113 * 114 * <p> 115 * Adds a single value to the <property>examples</property> property. 116 * 117 * @param mimeType The mime-type string. Must not be <jk>null</jk>. 118 * @param example The example. Must not be <jk>null</jk>. 119 * @return This object. 120 */ 121 public ResponseInfo addExample(String mimeType, Object example) { 122 assertArgNotNull("mimeType", mimeType); 123 assertArgNotNull("example", example); 124 examples.put(mimeType, example); 125 return this; 126 } 127 128 /** 129 * Bean property appender: <property>headers</property>. 130 * 131 * @param name The header name. Must not be <jk>null</jk>. 132 * @param header The header descriptions Must not be <jk>null</jk>. 133 * @return This object. 134 */ 135 public ResponseInfo addHeader(String name, HeaderInfo header) { 136 assertArgNotNull("name", name); 137 assertArgNotNull("header", header); 138 headers.put(name, header); 139 return this; 140 } 141 142 /** 143 * Make a deep copy of this object. 144 * 145 * @return A deep copy of this object. 146 */ 147 public ResponseInfo copy() { 148 return new ResponseInfo(this); 149 } 150 151 /** 152 * Copies any non-null fields from the specified object to this object. 153 * 154 * @param r 155 * The object to copy fields from. 156 * <br>Can be <jk>null</jk>. 157 * @return This object. 158 */ 159 public ResponseInfo copyFrom(ResponseInfo r) { 160 if (nn(r)) { 161 if (nn(r.description)) 162 description = r.description; 163 if (nn(r.schema)) 164 schema = r.schema; 165 if (nn(r.headers)) 166 headers = r.headers; 167 if (nn(r.examples)) 168 examples = r.examples; 169 } 170 return this; 171 } 172 173 @Override /* Overridden from SwaggerElement */ 174 public <T> T get(String property, Class<T> type) { 175 assertArgNotNull("property", property); 176 return switch (property) { 177 case "description" -> toType(getDescription(), type); 178 case "examples" -> toType(getExamples(), type); 179 case "headers" -> toType(getHeaders(), type); 180 case "schema" -> toType(getSchema(), type); 181 default -> super.get(property, type); 182 }; 183 } 184 185 /** 186 * Bean property getter: <property>description</property>. 187 * 188 * <p> 189 * A short description of the response. 190 * 191 * @return The property value, or <jk>null</jk> if it is not set. 192 */ 193 public String getDescription() { return description; } 194 195 /** 196 * Bean property getter: <property>examples</property>. 197 * 198 * <p> 199 * An example of the response message. 200 * 201 * @return The property value, or <jk>null</jk> if it is not set. 202 */ 203 public Map<String,Object> getExamples() { return nullIfEmpty(examples); } 204 205 /** 206 * Returns the header information with the specified name. 207 * 208 * @param name The header name. Must not be <jk>null</jk>. 209 * @return The header info, or <jk>null</jk> if not found. 210 */ 211 public HeaderInfo getHeader(String name) { 212 assertArgNotNull("name", name); 213 return headers.get(name); 214 } 215 216 /** 217 * Bean property getter: <property>headers</property>. 218 * 219 * <p> 220 * A list of headers that are sent with the response. 221 * 222 * @return The property value, or <jk>null</jk> if it is not set. 223 */ 224 public Map<String,HeaderInfo> getHeaders() { return nullIfEmpty(headers); } 225 226 /** 227 * Bean property getter: <property>schema</property>. 228 * 229 * <p> 230 * A definition of the response structure. 231 * 232 * @return The property value, or <jk>null</jk> if it is not set. 233 */ 234 public SchemaInfo getSchema() { return schema; } 235 236 @Override /* Overridden from SwaggerElement */ 237 public Set<String> keySet() { 238 // @formatter:off 239 var s = setb(String.class) 240 .addIf(nn(description), "description") 241 .addIf(ne(examples), "examples") 242 .addIf(ne(headers), "headers") 243 .addIf(nn(schema), "schema") 244 .build(); 245 // @formatter:on 246 return new MultiSet<>(s, super.keySet()); 247 } 248 249 /** 250 * Resolves any <js>"$ref"</js> attributes in this element. 251 * 252 * @param swagger The swagger document containing the definitions. 253 * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops. 254 * @param maxDepth 255 * The maximum depth to resolve references. 256 * <br>After that level is reached, <c>$ref</c> references will be left alone. 257 * <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex. 258 * @return 259 * This object with references resolved. 260 * <br>May or may not be the same object. 261 */ 262 public ResponseInfo resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) { 263 264 if (nn(schema)) 265 schema = schema.resolveRefs(swagger, refStack, maxDepth); 266 267 headers.entrySet().forEach(x -> x.setValue(x.getValue().resolveRefs(swagger, refStack, maxDepth))); 268 269 return this; 270 } 271 272 @Override /* Overridden from SwaggerElement */ 273 public ResponseInfo set(String property, Object value) { 274 assertArgNotNull("property", property); 275 return switch (property) { 276 case "description" -> setDescription(s(value)); 277 case "examples" -> setExamples(toMapBuilder(value, String.class, Object.class).sparse().build()); 278 case "headers" -> setHeaders(toMapBuilder(value, String.class, HeaderInfo.class).sparse().build()); 279 case "schema" -> setSchema(toType(value, SchemaInfo.class)); 280 default -> { 281 super.set(property, value); 282 yield this; 283 } 284 }; 285 } 286 287 /** 288 * Bean property setter: <property>description</property>. 289 * 290 * <p> 291 * A short description of the response. 292 * 293 * @param value 294 * The new value for this property. 295 * <br><a class="doclink" href="https://help.github.com/articles/github-flavored-markdown">GFM syntax</a> can be used for rich text representation. 296 * <br>Property value is required. 297 * <br>Can be <jk>null</jk> to unset the property. 298 * @return This object. 299 */ 300 public ResponseInfo setDescription(String value) { 301 description = value; 302 return this; 303 } 304 305 /** 306 * Bean property setter: <property>examples</property>. 307 * 308 * <p> 309 * An example of the response message. 310 * 311 * @param value 312 * The new value for this property. 313 * <br>Keys must be MIME-type strings. 314 * <br>Can be <jk>null</jk> to unset the property. 315 * @return This object. 316 */ 317 public ResponseInfo setExamples(Map<String,Object> value) { 318 examples.clear(); 319 if (nn(value)) 320 examples.putAll(value); 321 return this; 322 } 323 324 /** 325 * Bean property setter: <property>headers</property>. 326 * 327 * <p> 328 * A list of headers that are sent with the response. 329 * 330 * @param value 331 * The new value for this property. 332 * <br>Can be <jk>null</jk> to unset the property. 333 * @return This object. 334 */ 335 public ResponseInfo setHeaders(Map<String,HeaderInfo> value) { 336 headers.clear(); 337 if (nn(value)) 338 headers.putAll(value); 339 return this; 340 } 341 342 /** 343 * Bean property setter: <property>schema</property>. 344 * 345 * <p> 346 * A definition of the response structure. 347 * 348 * @param value 349 * The new value for this property. 350 * <br>It can be a primitive, an array or an object. 351 * <br>Can be <jk>null</jk> to unset the property. 352 * @return This object. 353 */ 354 public ResponseInfo setSchema(SchemaInfo value) { 355 schema = value; 356 return this; 357 } 358 359 /** 360 * Sets strict mode on this bean. 361 * 362 * @return This object. 363 */ 364 @Override 365 public ResponseInfo strict() { 366 super.strict(); 367 return this; 368 } 369 370 /** 371 * Sets strict mode on this bean. 372 * 373 * @param value 374 * The new value for this property. 375 * <br>Non-boolean values will be converted to boolean using <code>Boolean.<jsm>valueOf</jsm>(value.toString())</code>. 376 * <br>Can be <jk>null</jk> (interpreted as <jk>false</jk>). 377 * @return This object. 378 */ 379 @Override 380 public ResponseInfo strict(Object value) { 381 super.strict(value); 382 return this; 383 } 384}