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.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.Utils.*;
022import static org.apache.juneau.internal.ConverterUtils.*;
023
024import java.util.*;
025
026import org.apache.juneau.annotation.*;
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 */
078public class HeaderInfo extends OpenApiElement {
079
080   private String description, ref;
081   private Boolean required, explode, deprecated, allowEmptyValue, allowReserved;
082   private SchemaInfo schema;
083   private Object example;
084   private Map<String,Example> examples = map();
085
086   /**
087    * Default constructor.
088    */
089   public HeaderInfo() {}
090
091   /**
092    * Copy constructor.
093    *
094    * @param copyFrom The object to copy.
095    */
096   public HeaderInfo(HeaderInfo copyFrom) {
097      super(copyFrom);
098
099      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}