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.openapi3; 018 019import static org.apache.juneau.common.utils.Utils.*; 020import static org.apache.juneau.internal.CollectionUtils.*; 021import static org.apache.juneau.internal.ConverterUtils.*; 022 023import java.net.*; 024import java.util.*; 025 026import org.apache.juneau.*; 027import org.apache.juneau.common.utils.*; 028import org.apache.juneau.internal.*; 029 030/** 031 * Describes a single response from an API operation. 032 * 033 * <p> 034 * The Response Object describes a single response from an API operation, including a description, headers, content, and links. 035 * Responses are returned based on the HTTP status code, with the most common being success responses (2xx), redirects (3xx), 036 * client errors (4xx), and server errors (5xx). 037 * 038 * <h5 class='section'>OpenAPI Specification:</h5> 039 * <p> 040 * The Response Object is composed of the following fields: 041 * <ul class='spaced-list'> 042 * <li><c>description</c> (string, REQUIRED) - A short description of the response (CommonMark syntax may be used) 043 * <li><c>headers</c> (map of {@link HeaderInfo}) - Maps a header name to its definition 044 * <li><c>content</c> (map of {@link MediaType}) - A map containing descriptions of potential response payloads (keys are media types) 045 * <li><c>links</c> (map of {@link Link}) - A map of operations links that can be followed from the response 046 * </ul> 047 * 048 * <h5 class='section'>Example:</h5> 049 * <p class='bjava'> 050 * <jc>// Create a successful response with JSON content</jc> 051 * Response <jv>response</jv> = <jk>new</jk> Response() 052 * .setDescription(<js>"A list of pets"</js>) 053 * .setContent( 054 * JsonMap.<jsm>of</jsm>( 055 * <js>"application/json"</js>, <jk>new</jk> MediaType() 056 * .setSchema( 057 * <jk>new</jk> SchemaInfo() 058 * .setType(<js>"array"</js>) 059 * .setItems(<jk>new</jk> Items().setRef(<js>"#/components/schemas/Pet"</js>)) 060 * ) 061 * ) 062 * ) 063 * .setHeaders( 064 * JsonMap.<jsm>of</jsm>( 065 * <js>"X-Rate-Limit"</js>, <jk>new</jk> HeaderInfo() 066 * .setDescription(<js>"Requests per hour allowed by the user"</js>) 067 * .setSchema(<jk>new</jk> SchemaInfo().setType(<js>"integer"</js>)) 068 * ) 069 * ); 070 * </p> 071 * 072 * <h5 class='section'>See Also:</h5><ul> 073 * <li class='link'><a class="doclink" href="https://spec.openapis.org/oas/v3.0.0#response-object">OpenAPI Specification > Response Object</a> 074 * <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/describing-responses/">OpenAPI Describing Responses</a> 075 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a> 076 * </ul> 077 */ 078public class Response extends OpenApiElement{ 079 080 private String description; 081 private Map<String,HeaderInfo> headers; 082 private Map<String,MediaType> content; 083 private Map<String,Link> links; 084 085 /** 086 * Default constructor. 087 */ 088 public Response() { } 089 090 /** 091 * Copy constructor. 092 * 093 * @param copyFrom The object to copy. 094 */ 095 public Response(Response copyFrom) { 096 super(copyFrom); 097 098 this.description = copyFrom.description; 099 this.headers = copyOf(copyFrom.headers, HeaderInfo::copy); 100 this.content = copyOf(copyFrom.content, MediaType::copy); 101 this.links = copyOf(copyFrom.links, Link::copy); 102 } 103 104 /** 105 * Make a deep copy of this object. 106 * 107 * @return A deep copy of this object. 108 */ 109 public Response copy() { 110 return new Response(this); 111 } 112 113 @Override /* Overridden from OpenApiElement */ 114 protected Response strict() { 115 super.strict(); 116 return this; 117 } 118 119 @Override /* Overridden from OpenApiElement */ 120 public Response strict(Object value) { 121 super.strict(value); 122 return this; 123 } 124 125 /** 126 * Bean property getter: <property>Description</property>. 127 * 128 * <p> 129 * The URL pointing to the contact information. 130 * 131 * @return The property value, or <jk>null</jk> if it is not set. 132 */ 133 public String getDescription() { 134 return description; 135 } 136 137 /** 138 * Bean property setter: <property>Description</property>. 139 * 140 * <p> 141 * The value can be of any of the following types: {@link URI}, {@link URL}, {@link String}. 142 * <br>Strings must be valid URIs. 143 * 144 * <p> 145 * URIs defined by {@link UriResolver} can be used for values. 146 * 147 * @param value 148 * The new value for this property. 149 * <br>Can be <jk>null</jk> to unset the property. 150 * @return This object 151 */ 152 public Response setDescription(String value) { 153 description = value; 154 return this; 155 } 156 157 /** 158 * Bean property getter: <property>headers</property>. 159 * 160 * @return The property value, or <jk>null</jk> if it is not set. 161 */ 162 public Map<String, HeaderInfo> getHeaders() { 163 return headers; 164 } 165 166 /** 167 * Returns the header with the specified name. 168 * 169 * @param name The header name. Must not be <jk>null</jk>. 170 * @return The header info, or <jk>null</jk> if not found. 171 */ 172 public HeaderInfo getHeader(String name) { 173 assertArgNotNull("name", name); 174 return headers == null ? null : headers.get(name); 175 } 176 177 /** 178 * Bean property setter: <property>headers</property>. 179 * 180 * @param value 181 * The new value for this property. 182 * <br>Can be <jk>null</jk> to unset the property. 183 * @return This object 184 */ 185 public Response setHeaders(Map<String, HeaderInfo> value) { 186 headers = copyOf(value); 187 return this; 188 } 189 190 /** 191 * Adds one or more values to the <property>headers</property> property. 192 * 193 * @param key The mapping key. Must not be <jk>null</jk>. 194 * @param value The values to add to this property. Must not be <jk>null</jk>. 195 * @return This object 196 */ 197 public Response addHeader(String key, HeaderInfo value) { 198 assertArgNotNull("key", key); 199 assertArgNotNull("value", value); 200 headers = mapBuilder(headers).sparse().add(key, value).build(); 201 return this; 202 } 203 204 /** 205 * Bean property getter: <property>content</property>. 206 * 207 * @return The property value, or <jk>null</jk> if it is not set. 208 */ 209 public Map<String, MediaType> getContent() { 210 return content; 211 } 212 213 /** 214 * Returns the content with the specified media type. 215 * 216 * @param mediaType The media type. Must not be <jk>null</jk>. 217 * @return The media type info, or <jk>null</jk> if not found. 218 */ 219 public MediaType getContent(String mediaType) { 220 assertArgNotNull("mediaType", mediaType); 221 return content == null ? null : content.get(mediaType); 222 } 223 224 /** 225 * Bean property setter: <property>content</property>. 226 * 227 * @param value 228 * The new value for this property. 229 * <br>Can be <jk>null</jk> to unset the property. 230 * @return This object 231 */ 232 public Response setContent(Map<String, MediaType> value) { 233 content = copyOf(value); 234 return this; 235 } 236 237 /** 238 * Adds one or more values to the <property>content</property> property. 239 * 240 * @param key The mapping key. Must not be <jk>null</jk>. 241 * @param value The values to add to this property. Must not be <jk>null</jk>. 242 * @return This object 243 */ 244 public Response addContent(String key, MediaType value) { 245 assertArgNotNull("key", key); 246 assertArgNotNull("value", value); 247 content = mapBuilder(content).sparse().add(key, value).build(); 248 return this; 249 } 250 251 /** 252 * Bean property getter: <property>links</property>. 253 * 254 * @return The property value, or <jk>null</jk> if it is not set. 255 */ 256 public Map<String, Link> getLinks() { 257 return links; 258 } 259 260 /** 261 * Returns the link with the specified name. 262 * 263 * @param name The link name. Must not be <jk>null</jk>. 264 * @return The link info, or <jk>null</jk> if not found. 265 */ 266 public Link getLink(String name) { 267 assertArgNotNull("name", name); 268 return links == null ? null : links.get(name); 269 } 270 271 /** 272 * Bean property setter: <property>links</property>. 273 * 274 * @param value 275 * The new value for this property. 276 * <br>Can be <jk>null</jk> to unset the property. 277 * @return This object 278 */ 279 public Response setLinks(Map<String, Link> value) { 280 links = copyOf(value); 281 return this; 282 } 283 284 /** 285 * Adds one or more values to the <property>links</property> property. 286 * 287 * @param key The mapping key. Must not be <jk>null</jk>. 288 * @param value The values to add to this property. Must not be <jk>null</jk>. 289 * @return This object 290 */ 291 public Response addLink(String key, Link value) { 292 assertArgNotNull("key", key); 293 assertArgNotNull("value", value); 294 links = mapBuilder(links).sparse().add(key, value).build(); 295 return this; 296 } 297 298 @Override /* Overridden from OpenApiElement */ 299 public <T> T get(String property, Class<T> type) { 300 assertArgNotNull("property", property); 301 return switch (property) { 302 case "description" -> toType(getDescription(), type); 303 case "content" -> toType(getContent(), type); 304 case "headers" -> toType(getHeaders(), type); 305 case "links" -> toType(getLinks(), type); 306 default -> super.get(property, type); 307 }; 308 } 309 310 @Override /* Overridden from OpenApiElement */ 311 public Response set(String property, Object value) { 312 assertArgNotNull("property", property); 313 return switch (property) { 314 case "content" -> setContent(mapBuilder(String.class,MediaType.class).sparse().addAny(value).build()); 315 case "description" -> setDescription(Utils.s(value)); 316 case "headers" -> setHeaders(mapBuilder(String.class,HeaderInfo.class).sparse().addAny(value).build()); 317 case "links" -> setLinks(mapBuilder(String.class,Link.class).sparse().addAny(value).build()); 318 default -> { 319 super.set(property, value); 320 yield this; 321 } 322 }; 323 } 324 325 @Override /* Overridden from OpenApiElement */ 326 public Set<String> keySet() { 327 var s = setBuilder(String.class) 328 .addIf(content != null, "content") 329 .addIf(description != null, "description") 330 .addIf(headers != null, "headers") 331 .addIf(links != null, "links") 332 .build(); 333 return new MultiSet<>(s, super.keySet()); 334 } 335}