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.util.*;
25
26 import org.apache.juneau.annotation.*;
27 import org.apache.juneau.commons.collections.*;
28
29 /**
30 * Describes a single HTTP header.
31 *
32 * <p>
33 * The Header Object follows the structure of the Parameter Object with the following changes: it does not have a
34 * <c>name</c> field since the header name is specified in the key, and it does not have a <c>required</c> field
35 * since headers are always optional in HTTP.
36 *
37 * <h5 class='section'>OpenAPI Specification:</h5>
38 * <p>
39 * The Header Object is composed of the following fields:
40 * <ul class='spaced-list'>
41 * <li><c>description</c> (string) - A brief description of the header (CommonMark syntax may be used)
42 * <li><c>required</c> (boolean) - Determines whether this header is mandatory (default is <jk>false</jk>)
43 * <li><c>deprecated</c> (boolean) - Specifies that a header is deprecated
44 * <li><c>allowEmptyValue</c> (boolean) - Sets the ability to pass empty-valued headers
45 * <li><c>style</c> (string) - Describes how the header value will be serialized
46 * <li><c>explode</c> (boolean) - When true, header values of type array or object generate separate headers for each value
47 * <li><c>allowReserved</c> (boolean) - Determines whether the header value should allow reserved characters
48 * <li><c>schema</c> ({@link SchemaInfo}) - The schema defining the type used for the header
49 * <li><c>example</c> (any) - Example of the header's potential value
50 * <li><c>examples</c> (map of {@link Example}) - Examples of the header's potential value
51 * </ul>
52 *
53 * <h5 class='section'>Example:</h5>
54 * <p class='bcode'>
55 * <jc>// Construct using SwaggerBuilder.</jc>
56 * HeaderInfo <jv>x</jv> = <jsm>headerInfo</jsm>(<js>"integer"</js>).description(<js>"The number of allowed requests in the current period"</js>);
57 *
58 * <jc>// Serialize using JsonSerializer.</jc>
59 * String <jv>json</jv> = Json.<jsm>from</jsm>(<jv>x</jv>);
60 *
61 * <jc>// Or just use toString() which does the same as above.</jc>
62 * String <jv>json</jv> = <jv>x</jv>.toString();
63 * </p>
64 * <p class='bcode'>
65 * <jc>// Output</jc>
66 * {
67 * <js>"description"</js>: <js>"The number of allowed requests in the current period"</js>,
68 * <js>"type"</js>: <js>"integer"</js>
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#header-object">OpenAPI Specification > Header Object</a>
74 * <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/describing-parameters/">OpenAPI Describing Parameters</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 HeaderInfo extends OpenApiElement {
79
80 private String description, ref;
81 private Boolean required, explode, deprecated, allowEmptyValue, allowReserved;
82 private SchemaInfo schema;
83 private Object example;
84 private Map<String,Example> examples = map();
85
86 /**
87 * Default constructor.
88 */
89 public HeaderInfo() {}
90
91 /**
92 * Copy constructor.
93 *
94 * @param copyFrom The object to copy.
95 */
96 public HeaderInfo(HeaderInfo copyFrom) {
97 super(copyFrom);
98
99 this.description = copyFrom.description;
100 this.example = copyFrom.example;
101 this.allowEmptyValue = copyFrom.allowEmptyValue;
102 this.schema = copyFrom.schema;
103 this.allowReserved = copyFrom.allowReserved;
104 this.required = copyFrom.required;
105 this.ref = copyFrom.ref;
106 this.explode = copyFrom.explode;
107 this.deprecated = copyFrom.deprecated;
108 if (nn(copyFrom.examples))
109 examples.putAll(copyOf(copyFrom.examples, Example::copy));
110 }
111
112 /**
113 * Adds a single value to the <property>examples</property> property.
114 *
115 * @param name The example name. Must not be <jk>null</jk>.
116 * @param example The example. Must not be <jk>null</jk>.
117 * @return This object
118 */
119 public HeaderInfo addExample(String name, Example example) {
120 assertArgNotNull("name", name);
121 assertArgNotNull("example", example);
122 examples.put(name, example);
123 return this;
124 }
125
126 /**
127 * Make a deep copy of this object.
128 *
129 * @return A deep copy of this object.
130 */
131 public HeaderInfo copy() {
132 return new HeaderInfo(this);
133 }
134
135 @Override /* Overridden from OpenApiElement */
136 public <T> T get(String property, Class<T> type) {
137 assertArgNotNull("property", property);
138 return switch (property) {
139 case "description" -> toType(getDescription(), type);
140 case "required" -> toType(getRequired(), type);
141 case "explode" -> toType(getExplode(), type);
142 case "deprecated" -> toType(getDeprecated(), type);
143 case "allowEmptyValue" -> toType(getAllowEmptyValue(), type);
144 case "allowReserved" -> toType(getAllowReserved(), type);
145 case "$ref" -> toType(getRef(), type);
146 case "schema" -> toType(getSchema(), type);
147 case "x-example" -> toType(getExample(), type);
148 case "examples" -> toType(getExamples(), type);
149 default -> super.get(property, type);
150 };
151 }
152
153 /**
154 * Bean property getter: <property>allowEmptyValue</property>.
155 *
156 * <p>
157 * The type of the object.
158 *
159 * @return The property value, or <jk>null</jk> if it is not set.
160 */
161 public Boolean getAllowEmptyValue() { return allowEmptyValue; }
162
163 /**
164 * Bean property getter: <property>allowReserved</property>.
165 *
166 * <p>
167 * The type of the object.
168 *
169 * @return The property value, or <jk>null</jk> if it is not set.
170 */
171 public Boolean getAllowReserved() { return allowReserved; }
172
173 /**
174 * Bean property getter: <property>deprecated</property>.
175 *
176 * <p>
177 * The type of the object.
178 *
179 * @return The property value, or <jk>null</jk> if it is not set.
180 */
181 public Boolean getDeprecated() { return deprecated; }
182
183 /**
184 * Bean property getter: <property>description</property>.
185 *
186 * <p>
187 * A short description of the header.
188 *
189 * @return The property value, or <jk>null</jk> if it is not set.
190 */
191 public String getDescription() { return description; }
192
193 /**
194 * Bean property getter: <property>x-example</property>.
195 *
196 * @return The property value, or <jk>null</jk> if it is not set.
197 */
198 @Beanp("x-example")
199 public Object getExample() { return example; }
200
201 /**
202 * Bean property getter: <property>examples</property>.
203 *
204 * <p>
205 * The list of possible responses as they are returned from executing this operation.
206 *
207 * @return The property value, or <jk>null</jk> if it is not set.
208 */
209 public Map<String,Example> getExamples() { return nullIfEmpty(examples); }
210
211 /**
212 * Bean property getter: <property>required</property>.
213 *
214 * <p>
215 * The type of the object.
216 *
217 * @return The property value, or <jk>null</jk> if it is not set.
218 */
219 public Boolean getExplode() { return explode; }
220
221 /**
222 * Bean property getter: <property>$ref</property>.
223 *
224 * @return The property value, or <jk>null</jk> if it is not set.
225 */
226 @Beanp("$ref")
227 public String getRef() { return ref; }
228
229 /**
230 * Bean property getter: <property>required</property>.
231 *
232 * <p>
233 * The type of the object.
234 *
235 * @return The property value, or <jk>null</jk> if it is not set.
236 */
237 public Boolean getRequired() { return required; }
238
239 /**
240 * Bean property getter: <property>schema</property>.
241 *
242 * @return The property value, or <jk>null</jk> if it is not set.
243 */
244 public SchemaInfo getSchema() { return schema; }
245
246 @Override /* Overridden from SwaggerElement */
247 public Set<String> keySet() {
248 // @formatter:off
249 var s = setb(String.class)
250 .addIf(nn(ref), "$ref")
251 .addIf(nn(allowEmptyValue), "allowEmptyValue")
252 .addIf(nn(allowReserved), "allowReserved")
253 .addIf(nn(deprecated), "deprecated")
254 .addIf(nn(description), "description")
255 .addIf(ne(examples), "examples")
256 .addIf(nn(explode), "explode")
257 .addIf(nn(required), "required")
258 .addIf(nn(schema), "schema")
259 .addIf(nn(example), "x-example")
260 .build();
261 // @formatter:on
262 return new MultiSet<>(s, super.keySet());
263 }
264
265 /**
266 * Resolves any <js>"$ref"</js> attributes in this element.
267 *
268 * @param openApi The swagger document containing the definitions.
269 * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops.
270 * @param maxDepth
271 * The maximum depth to resolve references.
272 * <br>After that level is reached, <code>$ref</code> references will be left alone.
273 * <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex.
274 * @return
275 * This object with references resolved.
276 * <br>May or may not be the same object.
277 */
278 public HeaderInfo resolveRefs(OpenApi openApi, Deque<String> refStack, int maxDepth) {
279
280 if (nn(ref)) {
281 if (refStack.contains(ref) || refStack.size() >= maxDepth)
282 return this;
283 refStack.addLast(ref);
284 var r = openApi.findRef(ref, HeaderInfo.class);
285 r = r.resolveRefs(openApi, refStack, maxDepth);
286 refStack.removeLast();
287 return r;
288 }
289 return this;
290 }
291
292 @Override /* Overridden from OpenApiElement */
293 public HeaderInfo set(String property, Object value) {
294 assertArgNotNull("property", property);
295 return switch (property) {
296 case "$ref" -> setRef(s(value));
297 case "allowEmptyValue" -> setAllowEmptyValue(toBoolean(value));
298 case "allowReserved" -> setAllowReserved(toBoolean(value));
299 case "deprecated" -> setDeprecated(toBoolean(value));
300 case "description" -> setDescription(s(value));
301 case "examples" -> setExamples(toMapBuilder(value, String.class, Example.class).sparse().build());
302 case "explode" -> setExplode(toBoolean(value));
303 case "required" -> setRequired(toBoolean(value));
304 case "schema" -> setSchema(toType(value, SchemaInfo.class));
305 case "x-example" -> setExample(value);
306 default -> {
307 super.set(property, value);
308 yield this;
309 }
310 };
311 }
312
313 /**
314 * Bean property setter: <property>allowEmptyValue</property>.
315 *
316 * <p>
317 * The type of the object.
318 *
319 * @param value
320 * The new value for this property.
321 * <br>Can be <jk>null</jk> to unset the property.
322 * @return This object
323 */
324 public HeaderInfo setAllowEmptyValue(Boolean value) {
325 allowEmptyValue = value;
326 return this;
327 }
328
329 /**
330 * Bean property setter: <property>allowReserved</property>.
331 *
332 * <p>
333 * The type of the object.
334 *
335 * @param value
336 * The new value for this property.
337 * <br>Can be <jk>null</jk> to unset the property.
338 * @return This object
339 */
340 public HeaderInfo setAllowReserved(Boolean value) {
341 allowReserved = value;
342 return this;
343 }
344
345 /**
346 * Bean property setter: <property>deprecated</property>.
347 *
348 * <p>
349 * The type of the object.
350 *
351 * @param value
352 * The new value for this property.
353 * <br>Can be <jk>null</jk> to unset the property.
354 * @return This object
355 */
356 public HeaderInfo setDeprecated(Boolean value) {
357 deprecated = value;
358 return this;
359 }
360
361 /**
362 * Bean property setter: <property>description</property>.
363 *
364 * <p>
365 * A short description of the header.
366 *
367 * @param value
368 * The new value for this property.
369 * <br>Can be <jk>null</jk> to unset the property.
370 * @return This object
371 */
372 public HeaderInfo setDescription(String value) {
373 description = value;
374 return this;
375 }
376
377 /**
378 * Bean property setter: <property>examples</property>.
379 *
380 * @param value
381 * The new value for this property.
382 * <br>Can be <jk>null</jk> to unset the property.
383 * @return This object
384 */
385 @Beanp("x-example")
386 public HeaderInfo setExample(Object value) {
387 example = value;
388 return this;
389 }
390
391 /**
392 * Bean property setter: <property>headers</property>.
393 *
394 * <p>
395 * A list of examples that are sent with the response.
396 *
397 * @param value
398 * The new value for this property.
399 * <br>Can be <jk>null</jk> to unset the property.
400 * @return This object
401 */
402 public HeaderInfo setExamples(Map<String,Example> value) {
403 examples.clear();
404 if (nn(value))
405 examples.putAll(value);
406 return this;
407 }
408
409 /**
410 * Bean property setter: <property>explode</property>.
411 *
412 * <p>
413 * The type of the object.
414 *
415 * @param value
416 * The new value for this property.
417 * <br>Can be <jk>null</jk> to unset the property.
418 * @return This object
419 */
420 public HeaderInfo setExplode(Boolean value) {
421 explode = value;
422 return this;
423 }
424
425 /**
426 * Bean property setter: <property>$ref</property>.
427 *
428 * @param value
429 * The new value for this property.
430 * <br>Can be <jk>null</jk> to unset the property.
431 * @return This object
432 */
433 @Beanp("$ref")
434 public HeaderInfo setRef(String value) {
435 ref = value;
436 return this;
437 }
438
439 /**
440 * Bean property setter: <property>required</property>.
441 *
442 * <p>
443 * The type of the object.
444 *
445 * @param value
446 * The new value for this property.
447 * <br>Property value is required.
448 * <br>Valid values:
449 * <ul>
450 * <li><js>"string"</js>
451 * <li><js>"number"</js>
452 * <li><js>"integer"</js>
453 * <li><js>"boolean"</js>
454 * <li><js>"array"</js>
455 * </ul>
456 * <br>Can be <jk>null</jk> to unset the property.
457 * @return This object
458 */
459 public HeaderInfo setRequired(Boolean value) {
460 required = value;
461 return this;
462 }
463
464 /**
465 * Bean property setter: <property>schema</property>.
466 *
467 * @param value
468 * The new value for this property.
469 * <br>Can be <jk>null</jk> to unset the property.
470 * @return This object
471 */
472 public HeaderInfo setSchema(SchemaInfo value) {
473 schema = value;
474 return this;
475 }
476
477 @Override /* Overridden from OpenApiElement */
478 public HeaderInfo strict(Object value) {
479 super.strict(value);
480 return this;
481 }
482
483 @Override /* Overridden from OpenApiElement */
484 protected HeaderInfo strict() {
485 super.strict();
486 return this;
487 }
488 }