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 > 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 }