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.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 &gt; 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 }