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.CollectionUtils.*;
020import static org.apache.juneau.bean.openapi3.OpenApiCopyUtils.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022import static org.apache.juneau.marshall.internal.ConverterUtils.*;
023
024import java.util.*;
025
026import org.apache.juneau.commons.bean.*;
027import org.apache.juneau.commons.collections.*;
028
029/**
030 * Describes a single HTTP header.
031 *
032 * <p>
033 * The Header Object follows the structure of the Parameter Object with the following changes: it does not have a
034 * <c>name</c> field since the header name is specified in the key, and it does not have a <c>required</c> field
035 * since headers are always optional in HTTP.
036 *
037 * <h5 class='section'>OpenAPI Specification:</h5>
038 * <p>
039 * The Header Object is composed of the following fields:
040 * <ul class='spaced-list'>
041 *    <li><c>description</c> (string) - A brief description of the header (CommonMark syntax may be used)
042 *    <li><c>required</c> (boolean) - Determines whether this header is mandatory (default is <jk>false</jk>)
043 *    <li><c>deprecated</c> (boolean) - Specifies that a header is deprecated
044 *    <li><c>allowEmptyValue</c> (boolean) - Sets the ability to pass empty-valued headers
045 *    <li><c>style</c> (string) - Describes how the header value will be serialized
046 *    <li><c>explode</c> (boolean) - When true, header values of type array or object generate separate headers for each value
047 *    <li><c>allowReserved</c> (boolean) - Determines whether the header value should allow reserved characters
048 *    <li><c>schema</c> ({@link SchemaInfo}) - The schema defining the type used for the header
049 *    <li><c>example</c> (any) - Example of the header's potential value
050 *    <li><c>examples</c> (map of {@link Example}) - Examples of the header's potential value
051 * </ul>
052 *
053 * <h5 class='section'>Example:</h5>
054 * <p class='bcode'>
055 *    <jc>// Construct using SwaggerBuilder.</jc>
056 *    HeaderInfo <jv>x</jv> = <jsm>headerInfo</jsm>(<js>"integer"</js>).description(<js>"The number of allowed requests in the current period"</js>);
057 *
058 *    <jc>// Serialize using JsonSerializer.</jc>
059 *    String <jv>json</jv> = Json.<jsm>from</jsm>(<jv>x</jv>);
060 *
061 *    <jc>// Or just use toString() which does the same as above.</jc>
062 *    String <jv>json</jv> = <jv>x</jv>.toString();
063 * </p>
064 * <p class='bcode'>
065 *    <jc>// Output</jc>
066 *    {
067 *       <js>"description"</js>: <js>"The number of allowed requests in the current period"</js>,
068 *       <js>"type"</js>: <js>"integer"</js>
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#header-object">OpenAPI Specification &gt; Header Object</a>
074 *    <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/describing-parameters/">OpenAPI Describing Parameters</a>
075 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a>
076 * </ul>
077 */
078@SuppressWarnings({
079   "java:S1192" // Duplicated literals (property names) read more clearly inline than as constants
080})
081public class HeaderInfo extends OpenApiElement {
082
083   private String description;
084   private String ref;
085   private Boolean required;
086   private Boolean explode;
087   private Boolean deprecated;
088   private Boolean allowEmptyValue;
089   private Boolean allowReserved;
090   private SchemaInfo schema;
091   private Object example;
092   private final Map<String,Example> examples = map();
093
094   /**
095    * Default constructor.
096    */
097   public HeaderInfo() {}
098
099   /**
100    * Copy constructor.
101    *
102    * @param copyFrom The object to copy.
103    */
104   public HeaderInfo(HeaderInfo copyFrom) {
105      super(copyFrom);
106
107      this.description = copyFrom.description;
108      this.example = copyFrom.example;
109      this.allowEmptyValue = copyFrom.allowEmptyValue;
110      this.schema = copyOf(copyFrom.schema);
111      this.allowReserved = copyFrom.allowReserved;
112      this.required = copyFrom.required;
113      this.ref = copyFrom.ref;
114      this.explode = copyFrom.explode;
115      this.deprecated = copyFrom.deprecated;
116      examples.putAll(copyOf(copyFrom.examples, Example::copy));
117   }
118
119   /**
120    * Adds a single value to the <property>examples</property> property.
121    *
122    * @param name The example name.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
123    * @param example The example.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
124    * @return This object
125    */
126   public HeaderInfo addExample(String name, Example example) {
127      reqnn("name", name);
128      reqnn("example", example);
129      examples.put(name, example);
130      return this;
131   }
132
133   /**
134    * Make a deep copy of this object.
135    *
136    * @return A deep copy of this object.
137    */
138   public HeaderInfo copy() {
139      return new HeaderInfo(this);
140   }
141
142   @Override /* Overridden from OpenApiElement */
143   public <T> T get(String property, Class<T> type) {
144      reqnn("property", property);
145      return switch (property) {
146         case "description" -> toType(getDescription(), type);
147         case "required" -> toType(getRequired(), type);
148         case "explode" -> toType(getExplode(), type);
149         case "deprecated" -> toType(getDeprecated(), type);
150         case "allowEmptyValue" -> toType(getAllowEmptyValue(), type);
151         case "allowReserved" -> toType(getAllowReserved(), type);
152         case "$ref" -> toType(getRef(), type);
153         case "schema" -> toType(getSchema(), type);
154         case "x-example" -> toType(getExample(), type);
155         case "examples" -> toType(getExamples(), type);
156         default -> super.get(property, type);
157      };
158   }
159
160   /**
161    * Bean property getter:  <property>allowEmptyValue</property>.
162    *
163    * <p>
164    * Sets the ability to pass empty-valued headers.
165    *
166    * @return The property value, or <jk>null</jk> if it is not set.
167    */
168   public Boolean getAllowEmptyValue() { return allowEmptyValue; }
169
170   /**
171    * Bean property getter:  <property>allowReserved</property>.
172    *
173    * <p>
174    * Determines whether the header value should allow reserved characters.
175    *
176    * @return The property value, or <jk>null</jk> if it is not set.
177    */
178   public Boolean getAllowReserved() { return allowReserved; }
179
180   /**
181    * Bean property getter:  <property>deprecated</property>.
182    *
183    * <p>
184    * Specifies that a header is deprecated.
185    *
186    * @return The property value, or <jk>null</jk> if it is not set.
187    */
188   public Boolean getDeprecated() { return deprecated; }
189
190   /**
191    * Bean property getter:  <property>description</property>.
192    *
193    * <p>
194    * A short description of the header.
195    *
196    * @return The property value, or <jk>null</jk> if it is not set.
197    */
198   public String getDescription() { return description; }
199
200   /**
201    * Bean property getter:  <property>x-example</property>.
202    *
203    * @return The property value, or <jk>null</jk> if it is not set.
204    */
205   @BeanProp("x-example")
206   public Object getExample() { return example; }
207
208   /**
209    * Bean property getter:  <property>examples</property>.
210    *
211    * <p>
212    * Examples of the header's potential value.
213    *
214    * @return The property value, or <jk>null</jk> if it is not set.
215    */
216   public Map<String,Example> getExamples() { return nie(examples); }
217
218   /**
219    * Bean property getter:  <property>explode</property>.
220    *
221    * <p>
222    * When true, header values of type array or object generate separate headers for each value.
223    *
224    * @return The property value, or <jk>null</jk> if it is not set.
225    */
226   public Boolean getExplode() { return explode; }
227
228   /**
229    * Bean property getter:  <property>$ref</property>.
230    *
231    * @return The property value, or <jk>null</jk> if it is not set.
232    */
233   @BeanProp("$ref")
234   public String getRef() { return ref; }
235
236   /**
237    * Bean property getter:  <property>required</property>.
238    *
239    * <p>
240    * Determines whether this header is mandatory.
241    *
242    * @return The property value, or <jk>null</jk> if it is not set.
243    */
244   public Boolean getRequired() { return required; }
245
246   /**
247    * Bean property getter:  <property>schema</property>.
248    *
249    * @return The property value, or <jk>null</jk> if it is not set.
250    */
251   public SchemaInfo getSchema() { return schema; }
252
253   @Override /* Overridden from SwaggerElement */
254   public Set<String> keySet() {
255      // @formatter:off
256      var s = stb(String.class)
257         .addIf(nn(ref), "$ref")
258         .addIf(nn(allowEmptyValue), "allowEmptyValue")
259         .addIf(nn(allowReserved), "allowReserved")
260         .addIf(nn(deprecated), "deprecated")
261         .addIf(nn(description), "description")
262         .addIf(ine(examples), "examples")
263         .addIf(nn(explode), "explode")
264         .addIf(nn(required), "required")
265         .addIf(nn(schema), "schema")
266         .addIf(nn(example), "x-example")
267         .build();
268      // @formatter:on
269      return new MultiSet<>(s, super.keySet());
270   }
271
272   /**
273    * Resolves any <js>"$ref"</js> attributes in this element.
274    *
275    * @param openApi The swagger document containing the definitions.
276    * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops.
277    * @param maxDepth
278    *    The maximum depth to resolve references.
279    *    <br>After that level is reached, <code>$ref</code> references will be left alone.
280    *    <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex.
281    * @return
282    *    This object with references resolved.
283    *    <br>May or may not be the same object.
284    */
285   public HeaderInfo resolveRefs(OpenApi openApi, Deque<String> refStack, int maxDepth) {
286
287      if (nn(ref)) {
288         if (refStack.contains(ref) || refStack.size() >= maxDepth)
289            return this;
290         refStack.addLast(ref);
291         var r = openApi.findRef(ref, HeaderInfo.class);
292         r = r.resolveRefs(openApi, refStack, maxDepth);
293         refStack.removeLast();
294         return r;
295      }
296      return this;
297   }
298
299   @Override /* Overridden from OpenApiElement */
300   public HeaderInfo set(String property, Object value) {
301      reqnn("property", property);
302      return switch (property) {
303         case "$ref" -> setRef(s(value));
304         case "allowEmptyValue" -> setAllowEmptyValue(toBoolean(value));
305         case "allowReserved" -> setAllowReserved(toBoolean(value));
306         case "deprecated" -> setDeprecated(toBoolean(value));
307         case "description" -> setDescription(s(value));
308         case "examples" -> setExamples(toMapBuilder(value, String.class, Example.class).sparse().build());
309         case "explode" -> setExplode(toBoolean(value));
310         case "required" -> setRequired(toBoolean(value));
311         case "schema" -> setSchema(toType(value, SchemaInfo.class));
312         case "x-example" -> setExample(value);
313         default -> {
314            super.set(property, value);
315            yield this;
316         }
317      };
318   }
319
320   /**
321    * Bean property setter:  <property>allowEmptyValue</property>.
322    *
323    * <p>
324    * Sets the ability to pass empty-valued headers.
325    *
326    * @param value
327    *    The new value for this property.
328    *    <br>Can be <jk>null</jk> to unset the property.
329    * @return This object
330    */
331   public HeaderInfo setAllowEmptyValue(Boolean value) {
332      allowEmptyValue = value;
333      return this;
334   }
335
336   /**
337    * Bean property setter:  <property>allowReserved</property>.
338    *
339    * <p>
340    * Determines whether the header value should allow reserved characters.
341    *
342    * @param value
343    *    The new value for this property.
344    *    <br>Can be <jk>null</jk> to unset the property.
345    * @return This object
346    */
347   public HeaderInfo setAllowReserved(Boolean value) {
348      allowReserved = value;
349      return this;
350   }
351
352   /**
353    * Bean property setter:  <property>deprecated</property>.
354    *
355    * <p>
356    * Specifies that a header is deprecated.
357    *
358    * @param value
359    *    The new value for this property.
360    *    <br>Can be <jk>null</jk> to unset the property.
361    * @return This object
362    */
363   public HeaderInfo setDeprecated(Boolean value) {
364      deprecated = value;
365      return this;
366   }
367
368   /**
369    * Bean property setter:  <property>description</property>.
370    *
371    * <p>
372    * A short description of the header.
373    *
374    * @param value
375    *    The new value for this property.
376    *    <br>Can be <jk>null</jk> to unset the property.
377    * @return This object
378    */
379   public HeaderInfo setDescription(String value) {
380      description = value;
381      return this;
382   }
383
384   /**
385    * Bean property setter:  <property>examples</property>.
386    *
387    * @param value
388    *    The new value for this property.
389    *    <br>Can be <jk>null</jk> to unset the property.
390    * @return This object
391    */
392   @BeanProp("x-example")
393   public HeaderInfo setExample(Object value) {
394      example = value;
395      return this;
396   }
397
398   /**
399    * Bean property setter:  <property>examples</property>.
400    *
401    * <p>
402    * Examples of the header's potential value.
403    *
404    * @param value
405    *    The new value for this property.
406    *    <br>Can be <jk>null</jk> to unset the property.
407    * @return This object
408    */
409   public HeaderInfo setExamples(Map<String,Example> value) {
410      examples.clear();
411      if (nn(value))
412         examples.putAll(value);
413      return this;
414   }
415
416   /**
417    * Bean property setter:  <property>explode</property>.
418    *
419    * <p>
420    * When true, header values of type array or object generate separate headers for each value.
421    *
422    * @param value
423    *    The new value for this property.
424    *    <br>Can be <jk>null</jk> to unset the property.
425    * @return This object
426    */
427   public HeaderInfo setExplode(Boolean value) {
428      explode = value;
429      return this;
430   }
431
432   /**
433    * Bean property setter:  <property>$ref</property>.
434    *
435    * @param value
436    *    The new value for this property.
437    *    <br>Can be <jk>null</jk> to unset the property.
438    * @return This object
439    */
440   @BeanProp("$ref")
441   public HeaderInfo setRef(String value) {
442      ref = value;
443      return this;
444   }
445
446   /**
447    * Bean property setter:  <property>required</property>.
448    *
449    * <p>
450    * Determines whether this header is mandatory.
451    *
452    * @param value
453    *    The new value for this property.
454    *    <br>Can be <jk>null</jk> to unset the property.
455    * @return This object
456    */
457   public HeaderInfo setRequired(Boolean value) {
458      required = value;
459      return this;
460   }
461
462   /**
463    * Bean property setter:  <property>schema</property>.
464    *
465    * @param value
466    *    The new value for this property.
467    *    <br>Can be <jk>null</jk> to unset the property.
468    * @return This object
469    */
470   public HeaderInfo setSchema(SchemaInfo value) {
471      schema = value;
472      return this;
473   }
474
475   @Override /* Overridden from OpenApiElement */
476   public HeaderInfo strict(Object value) {
477      super.strict(value);
478      return this;
479   }
480
481   @Override /* Overridden from OpenApiElement */
482   protected HeaderInfo strict() {
483      super.strict();
484      return this;
485   }
486}