View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.bean.openapi3;
18  
19  import static org.apache.juneau.commons.utils.AssertionUtils.*;
20  import static org.apache.juneau.commons.utils.CollectionUtils.*;
21  import static org.apache.juneau.commons.utils.Utils.*;
22  import static org.apache.juneau.internal.ConverterUtils.*;
23  
24  import java.net.*;
25  import java.util.*;
26  
27  import org.apache.juneau.*;
28  import org.apache.juneau.commons.collections.*;
29  
30  /**
31   * Describes a single response from an API operation.
32   *
33   * <p>
34   * The Response Object describes a single response from an API operation, including a description, headers, content, and links.
35   * Responses are returned based on the HTTP status code, with the most common being success responses (2xx), redirects (3xx),
36   * client errors (4xx), and server errors (5xx).
37   *
38   * <h5 class='section'>OpenAPI Specification:</h5>
39   * <p>
40   * The Response Object is composed of the following fields:
41   * <ul class='spaced-list'>
42   * 	<li><c>description</c> (string, REQUIRED) - A short description of the response (CommonMark syntax may be used)
43   * 	<li><c>headers</c> (map of {@link HeaderInfo}) - Maps a header name to its definition
44   * 	<li><c>content</c> (map of {@link MediaType}) - A map containing descriptions of potential response payloads (keys are media types)
45   * 	<li><c>links</c> (map of {@link Link}) - A map of operations links that can be followed from the response
46   * </ul>
47   *
48   * <h5 class='section'>Example:</h5>
49   * <p class='bjava'>
50   * 	<jc>// Create a successful response with JSON content</jc>
51   * 	Response <jv>response</jv> = <jk>new</jk> Response()
52   * 		.setDescription(<js>"A list of pets"</js>)
53   * 		.setContent(
54   * 			JsonMap.<jsm>of</jsm>(
55   * 				<js>"application/json"</js>, <jk>new</jk> MediaType()
56   * 					.setSchema(
57   * 						<jk>new</jk> SchemaInfo()
58   * 							.setType(<js>"array"</js>)
59   * 							.setItems(<jk>new</jk> Items().setRef(<js>"#/components/schemas/Pet"</js>))
60   * 					)
61   * 			)
62   * 		)
63   * 		.setHeaders(
64   * 			JsonMap.<jsm>of</jsm>(
65   * 				<js>"X-Rate-Limit"</js>, <jk>new</jk> HeaderInfo()
66   * 					.setDescription(<js>"Requests per hour allowed by the user"</js>)
67   * 					.setSchema(<jk>new</jk> SchemaInfo().setType(<js>"integer"</js>))
68   * 			)
69   * 		);
70   * </p>
71   *
72   * <h5 class='section'>See Also:</h5><ul>
73   * 	<li class='link'><a class="doclink" href="https://spec.openapis.org/oas/v3.0.0#response-object">OpenAPI Specification &gt; Response Object</a>
74   * 	<li class='link'><a class="doclink" href="https://swagger.io/docs/specification/describing-responses/">OpenAPI Describing Responses</a>
75   * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a>
76   * </ul>
77   */
78  public class Response extends OpenApiElement {
79  
80  	private String description;
81  	private Map<String,HeaderInfo> headers = map();
82  	private Map<String,MediaType> content = map();
83  	private Map<String,Link> links = map();
84  
85  	/**
86  	 * Default constructor.
87  	 */
88  	public Response() {}
89  
90  	/**
91  	 * Copy constructor.
92  	 *
93  	 * @param copyFrom The object to copy.
94  	 */
95  	public Response(Response copyFrom) {
96  		super(copyFrom);
97  
98  		this.description = copyFrom.description;
99  		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 }