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.CollectionUtils.*; 020import static org.apache.juneau.bean.swagger.SwaggerCopyUtils.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022import static org.apache.juneau.marshall.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 */ 083@SuppressWarnings({ 084 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 085}) 086public class ResponseInfo extends SwaggerElement { 087 088 private String description; 089 private SchemaInfo schema; 090 private Map<String,HeaderInfo> headers = map(); 091 private Map<String,Object> examples = map(); 092 093 /** 094 * Default constructor. 095 */ 096 public ResponseInfo() {} 097 098 /** 099 * Copy constructor. 100 * 101 * @param copyFrom The object to copy. 102 */ 103 public ResponseInfo(ResponseInfo copyFrom) { 104 super(copyFrom); 105 106 this.description = copyFrom.description; 107 this.schema = copyOf(copyFrom.schema); 108 examples.putAll(copyOf(copyFrom.examples)); 109 headers.putAll(copyOf(copyFrom.headers, HeaderInfo::copy)); 110 } 111 112 /** 113 * Bean property appender: <property>examples</property>. 114 * 115 * <p> 116 * Adds a single value to the <property>examples</property> property. 117 * 118 * @param mimeType The mime-type string. Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown. 119 * @param example The example. Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown. 120 * @return This object. 121 */ 122 public ResponseInfo addExample(String mimeType, Object example) { 123 reqnn("mimeType", mimeType); 124 reqnn("example", example); 125 examples.put(mimeType, example); 126 return this; 127 } 128 129 /** 130 * Bean property appender: <property>headers</property>. 131 * 132 * @param name The header name. Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown. 133 * @param header The header descriptions Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown. 134 * @return This object. 135 */ 136 public ResponseInfo addHeader(String name, HeaderInfo header) { 137 reqnn("name", name); 138 reqnn("header", header); 139 headers.put(name, header); 140 return this; 141 } 142 143 /** 144 * Make a deep copy of this object. 145 * 146 * @return A deep copy of this object. 147 */ 148 public ResponseInfo copy() { 149 return new ResponseInfo(this); 150 } 151 152 /** 153 * Copies any non-null fields from the specified object to this object. 154 * 155 * @param r 156 * The object to copy fields from. 157 * <br>Can be <jk>null</jk> (no-op). 158 * @return This object. 159 */ 160 public ResponseInfo copyFrom(ResponseInfo r) { 161 if (nn(r)) { 162 if (nn(r.description)) 163 description = r.description; 164 if (nn(r.schema)) 165 schema = r.schema; 166 headers = r.headers; 167 examples = r.examples; 168 } 169 return this; 170 } 171 172 @Override /* Overridden from SwaggerElement */ 173 public <T> T get(String property, Class<T> type) { 174 reqnn("property", property); 175 return switch (property) { 176 case "description" -> toType(getDescription(), type); 177 case "examples" -> toType(getExamples(), type); 178 case "headers" -> toType(getHeaders(), type); 179 case "schema" -> toType(getSchema(), type); 180 default -> super.get(property, type); 181 }; 182 } 183 184 /** 185 * Bean property getter: <property>description</property>. 186 * 187 * <p> 188 * A short description of the response. 189 * 190 * @return The property value, or <jk>null</jk> if it is not set. 191 */ 192 public String getDescription() { return description; } 193 194 /** 195 * Bean property getter: <property>examples</property>. 196 * 197 * <p> 198 * An example of the response message. 199 * 200 * @return The property value, or <jk>null</jk> if it is not set. 201 */ 202 public Map<String,Object> getExamples() { return u(nie(examples)); } 203 204 /** 205 * Returns the header information with the specified name. 206 * 207 * @param name The header name. Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown. 208 * @return The header info, or <jk>null</jk> if not found. 209 */ 210 public HeaderInfo getHeader(String name) { 211 reqnn("name", name); 212 return headers.get(name); 213 } 214 215 /** 216 * Bean property getter: <property>headers</property>. 217 * 218 * <p> 219 * A list of headers that are sent with the response. 220 * 221 * @return The property value, or <jk>null</jk> if it is not set. 222 */ 223 public Map<String,HeaderInfo> getHeaders() { return u(nie(headers)); } 224 225 /** 226 * Bean property getter: <property>schema</property>. 227 * 228 * <p> 229 * A definition of the response structure. 230 * 231 * @return The property value, or <jk>null</jk> if it is not set. 232 */ 233 public SchemaInfo getSchema() { return schema; } 234 235 @Override /* Overridden from SwaggerElement */ 236 public Set<String> keySet() { 237 // @formatter:off 238 var s = stb(String.class) 239 .addIf(nn(description), "description") 240 .addIf(ine(examples), "examples") 241 .addIf(ine(headers), "headers") 242 .addIf(nn(schema), "schema") 243 .build(); 244 // @formatter:on 245 return new MultiSet<>(s, super.keySet()); 246 } 247 248 /** 249 * Resolves any <js>"$ref"</js> attributes in this element. 250 * 251 * @param swagger The swagger document containing the definitions. 252 * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops. 253 * @param maxDepth 254 * The maximum depth to resolve references. 255 * <br>After that level is reached, <c>$ref</c> references will be left alone. 256 * <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex. 257 * @return 258 * This object with references resolved. 259 * <br>May or may not be the same object. 260 */ 261 public ResponseInfo resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) { 262 263 if (nn(schema)) 264 schema = schema.resolveRefs(swagger, refStack, maxDepth); 265 266 headers.entrySet().forEach(x -> x.setValue(x.getValue().resolveRefs(swagger, refStack, maxDepth))); 267 268 return this; 269 } 270 271 @Override /* Overridden from SwaggerElement */ 272 public ResponseInfo set(String property, Object value) { 273 reqnn("property", property); 274 return switch (property) { 275 case "description" -> setDescription(s(value)); 276 case "examples" -> setExamples(toMapBuilder(value, String.class, Object.class).sparse().build()); 277 case "headers" -> setHeaders(toMapBuilder(value, String.class, HeaderInfo.class).sparse().build()); 278 case "schema" -> setSchema(toType(value, SchemaInfo.class)); 279 default -> { 280 super.set(property, value); 281 yield this; 282 } 283 }; 284 } 285 286 /** 287 * Bean property setter: <property>description</property>. 288 * 289 * <p> 290 * A short description of the response. 291 * 292 * @param value 293 * The new value for this property. 294 * <br><a class="doclink" href="https://help.github.com/articles/github-flavored-markdown">GFM syntax</a> can be used for rich text representation. 295 * <br>Property value is required. 296 * <br>Can be <jk>null</jk> to unset the property. 297 * @return This object. 298 */ 299 public ResponseInfo setDescription(String value) { 300 description = value; 301 return this; 302 } 303 304 /** 305 * Bean property setter: <property>examples</property>. 306 * 307 * <p> 308 * An example of the response message. 309 * 310 * @param value 311 * The new value for this property. 312 * <br>Keys must be MIME-type strings. 313 * <br>Can be <jk>null</jk> to unset the property. 314 * @return This object. 315 */ 316 public ResponseInfo setExamples(Map<String,Object> value) { 317 examples.clear(); 318 if (nn(value)) 319 examples.putAll(value); 320 return this; 321 } 322 323 /** 324 * Bean property setter: <property>headers</property>. 325 * 326 * <p> 327 * A list of headers that are sent with the response. 328 * 329 * @param value 330 * The new value for this property. 331 * <br>Can be <jk>null</jk> to unset the property. 332 * @return This object. 333 */ 334 public ResponseInfo setHeaders(Map<String,HeaderInfo> value) { 335 headers.clear(); 336 if (nn(value)) 337 headers.putAll(value); 338 return this; 339 } 340 341 /** 342 * Bean property setter: <property>schema</property>. 343 * 344 * <p> 345 * A definition of the response structure. 346 * 347 * @param value 348 * The new value for this property. 349 * <br>It can be a primitive, an array or an object. 350 * <br>Can be <jk>null</jk> to unset the property. 351 * @return This object. 352 */ 353 public ResponseInfo setSchema(SchemaInfo value) { 354 schema = value; 355 return this; 356 } 357 358 /** 359 * Sets strict mode on this bean. 360 * 361 * @return This object. 362 */ 363 @Override 364 public ResponseInfo strict() { 365 super.strict(); 366 return this; 367 } 368 369 /** 370 * Sets strict mode on this bean. 371 * 372 * @param value 373 * The new value for this property. 374 * <br>Non-boolean values will be converted to boolean using <code>Boolean.<jsm>valueOf</jsm>(value.toString())</code>. 375 * <br>Can be <jk>null</jk> (interpreted as <jk>false</jk>). 376 * @return This object. 377 */ 378 @Override 379 public ResponseInfo strict(Object value) { 380 super.strict(value); 381 return this; 382 } 383}