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.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.net.*; 025import java.util.*; 026 027import org.apache.juneau.*; 028import org.apache.juneau.commons.collections.*; 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 = map(); 082 private Map<String,MediaType> content = map(); 083 private Map<String,Link> links = map(); 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 if (nn(copyFrom.headers)) 100 headers.putAll(copyOf(copyFrom.headers, HeaderInfo::copy)); 101 if (nn(copyFrom.content)) 102 content.putAll(copyOf(copyFrom.content, MediaType::copy)); 103 if (nn(copyFrom.links)) 104 links.putAll(copyOf(copyFrom.links, Link::copy)); 105 } 106 107 /** 108 * Adds one or more values to the <property>content</property> property. 109 * 110 * @param key The mapping key. Must not be <jk>null</jk>. 111 * @param value The values to add to this property. Must not be <jk>null</jk>. 112 * @return This object 113 */ 114 public Response addContent(String key, MediaType value) { 115 assertArgNotNull("key", key); 116 assertArgNotNull("value", value); 117 content.put(key, value); 118 return this; 119 } 120 121 /** 122 * Adds one or more values to the <property>headers</property> property. 123 * 124 * @param key The mapping key. Must not be <jk>null</jk>. 125 * @param value The values to add to this property. Must not be <jk>null</jk>. 126 * @return This object 127 */ 128 public Response addHeader(String key, HeaderInfo value) { 129 assertArgNotNull("key", key); 130 assertArgNotNull("value", value); 131 headers.put(key, value); 132 return this; 133 } 134 135 /** 136 * Adds one or more values to the <property>links</property> property. 137 * 138 * @param key The mapping key. Must not be <jk>null</jk>. 139 * @param value The values to add to this property. Must not be <jk>null</jk>. 140 * @return This object 141 */ 142 public Response addLink(String key, Link value) { 143 assertArgNotNull("key", key); 144 assertArgNotNull("value", value); 145 links.put(key, value); 146 return this; 147 } 148 149 /** 150 * Make a deep copy of this object. 151 * 152 * @return A deep copy of this object. 153 */ 154 public Response copy() { 155 return new Response(this); 156 } 157 158 @Override /* Overridden from OpenApiElement */ 159 public <T> T get(String property, Class<T> type) { 160 assertArgNotNull("property", property); 161 return switch (property) { 162 case "description" -> toType(getDescription(), type); 163 case "content" -> toType(getContent(), type); 164 case "headers" -> toType(getHeaders(), type); 165 case "links" -> toType(getLinks(), type); 166 default -> super.get(property, type); 167 }; 168 } 169 170 /** 171 * Bean property getter: <property>content</property>. 172 * 173 * @return The property value, or <jk>null</jk> if it is not set. 174 */ 175 public Map<String,MediaType> getContent() { return nullIfEmpty(content); } 176 177 /** 178 * Returns the content with the specified media type. 179 * 180 * @param mediaType The media type. Must not be <jk>null</jk>. 181 * @return The media type info, or <jk>null</jk> if not found. 182 */ 183 public MediaType getContent(String mediaType) { 184 assertArgNotNull("mediaType", mediaType); 185 return content.get(mediaType); 186 } 187 188 /** 189 * Bean property getter: <property>Description</property>. 190 * 191 * <p> 192 * The URL pointing to the contact information. 193 * 194 * @return The property value, or <jk>null</jk> if it is not set. 195 */ 196 public String getDescription() { return description; } 197 198 /** 199 * Returns the header with the specified name. 200 * 201 * @param name The header name. Must not be <jk>null</jk>. 202 * @return The header info, or <jk>null</jk> if not found. 203 */ 204 public HeaderInfo getHeader(String name) { 205 assertArgNotNull("name", name); 206 return headers.get(name); 207 } 208 209 /** 210 * Bean property getter: <property>headers</property>. 211 * 212 * @return The property value, or <jk>null</jk> if it is not set. 213 */ 214 public Map<String,HeaderInfo> getHeaders() { return nullIfEmpty(headers); } 215 216 /** 217 * Returns the link with the specified name. 218 * 219 * @param name The link name. Must not be <jk>null</jk>. 220 * @return The link info, or <jk>null</jk> if not found. 221 */ 222 public Link getLink(String name) { 223 assertArgNotNull("name", name); 224 return links.get(name); 225 } 226 227 /** 228 * Bean property getter: <property>links</property>. 229 * 230 * @return The property value, or <jk>null</jk> if it is not set. 231 */ 232 public Map<String,Link> getLinks() { return nullIfEmpty(links); } 233 234 @Override /* Overridden from OpenApiElement */ 235 public Set<String> keySet() { 236 // @formatter:off 237 var s = setb(String.class) 238 .addIf(ne(content), "content") 239 .addIf(nn(description), "description") 240 .addIf(ne(headers), "headers") 241 .addIf(ne(links), "links") 242 .build(); 243 // @formatter:on 244 return new MultiSet<>(s, super.keySet()); 245 } 246 247 @Override /* Overridden from OpenApiElement */ 248 public Response set(String property, Object value) { 249 assertArgNotNull("property", property); 250 return switch (property) { 251 case "content" -> setContent(toMapBuilder(value, String.class, MediaType.class).sparse().build()); 252 case "description" -> setDescription(s(value)); 253 case "headers" -> setHeaders(toMapBuilder(value, String.class, HeaderInfo.class).sparse().build()); 254 case "links" -> setLinks(toMapBuilder(value, String.class, Link.class).sparse().build()); 255 default -> { 256 super.set(property, value); 257 yield this; 258 } 259 }; 260 } 261 262 /** 263 * Bean property setter: <property>content</property>. 264 * 265 * @param value 266 * The new value for this property. 267 * <br>Can be <jk>null</jk> to unset the property. 268 * @return This object 269 */ 270 public Response setContent(Map<String,MediaType> value) { 271 content.clear(); 272 if (nn(value)) 273 content.putAll(value); 274 return this; 275 } 276 277 /** 278 * Bean property setter: <property>Description</property>. 279 * 280 * <p> 281 * The value can be of any of the following types: {@link URI}, {@link URL}, {@link String}. 282 * <br>Strings must be valid URIs. 283 * 284 * <p> 285 * URIs defined by {@link UriResolver} can be used for values. 286 * 287 * @param value 288 * The new value for this property. 289 * <br>Can be <jk>null</jk> to unset the property. 290 * @return This object 291 */ 292 public Response setDescription(String value) { 293 description = value; 294 return this; 295 } 296 297 /** 298 * Bean property setter: <property>headers</property>. 299 * 300 * @param value 301 * The new value for this property. 302 * <br>Can be <jk>null</jk> to unset the property. 303 * @return This object 304 */ 305 public Response setHeaders(Map<String,HeaderInfo> value) { 306 headers.clear(); 307 if (nn(value)) 308 headers.putAll(value); 309 return this; 310 } 311 312 /** 313 * Bean property setter: <property>links</property>. 314 * 315 * @param value 316 * The new value for this property. 317 * <br>Can be <jk>null</jk> to unset the property. 318 * @return This object 319 */ 320 public Response setLinks(Map<String,Link> value) { 321 links.clear(); 322 if (nn(value)) 323 links.putAll(value); 324 return this; 325 } 326 327 @Override /* Overridden from OpenApiElement */ 328 public Response strict(Object value) { 329 super.strict(value); 330 return this; 331 } 332 333 @Override /* Overridden from OpenApiElement */ 334 protected Response strict() { 335 super.strict(); 336 return this; 337 } 338}