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.common.utils.Utils.*;
020import static org.apache.juneau.internal.CollectionUtils.*;
021import static org.apache.juneau.internal.ConverterUtils.*;
022
023import java.util.*;
024
025import org.apache.juneau.annotation.*;
026import org.apache.juneau.common.utils.*;
027import org.apache.juneau.internal.*;
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
081      description,
082      ref;
083   private Boolean
084      required,
085      explode,
086      deprecated,
087      allowEmptyValue,
088      allowReserved;
089   private SchemaInfo schema;
090   private Object example;
091   private Map<String,Example> examples;
092
093   /**
094    * Default constructor.
095    */
096   public HeaderInfo() {}
097
098   /**
099    * Copy constructor.
100    *
101    * @param copyFrom The object to copy.
102    */
103   public HeaderInfo(HeaderInfo copyFrom) {
104      super(copyFrom);
105
106      this.description = copyFrom.description;
107      this.example = copyFrom.example;
108      this.allowEmptyValue = copyFrom.allowEmptyValue;
109      this.schema = copyFrom.schema;
110      this.allowReserved = copyFrom.allowReserved;
111      this.required = copyFrom.required;
112      this.ref = copyFrom.ref;
113      this.explode = copyFrom.explode;
114      this.deprecated = copyFrom.deprecated;
115      this.examples = copyOf(copyFrom.examples, Example::copy);
116   }
117
118   /**
119    * Make a deep copy of this object.
120    *
121    * @return A deep copy of this object.
122    */
123   public HeaderInfo copy() {
124      return new HeaderInfo(this);
125   }
126
127   @Override /* Overridden from OpenApiElement */
128   protected HeaderInfo strict() {
129      super.strict();
130      return this;
131   }
132
133   @Override /* Overridden from OpenApiElement */
134   public HeaderInfo strict(Object value) {
135      super.strict(value);
136      return this;
137   }
138
139   /**
140    * Bean property getter:  <property>description</property>.
141    *
142    * <p>
143    * A short description of the header.
144    *
145    * @return The property value, or <jk>null</jk> if it is not set.
146    */
147   public String getDescription() {
148      return description;
149   }
150
151   /**
152    * Bean property setter:  <property>description</property>.
153    *
154    * <p>
155    * A short description of the header.
156    *
157    * @param value
158    *    The new value for this property.
159    *    <br>Can be <jk>null</jk> to unset the property.
160    * @return This object
161    */
162   public HeaderInfo setDescription(String value) {
163      description = value;
164      return this;
165   }
166
167   /**
168    * Bean property getter:  <property>required</property>.
169    *
170    * <p>
171    * The type of the object.
172    *
173    * @return The property value, or <jk>null</jk> if it is not set.
174    */
175   public Boolean getRequired() {
176      return required;
177   }
178
179   /**
180    * Bean property setter:  <property>required</property>.
181    *
182    * <p>
183    * The type of the object.
184    *
185    * @param value
186    *    The new value for this property.
187    *    <br>Property value is required.
188    *    <br>Valid values:
189    *    <ul>
190    *       <li><js>"string"</js>
191    *       <li><js>"number"</js>
192    *       <li><js>"integer"</js>
193    *       <li><js>"boolean"</js>
194    *       <li><js>"array"</js>
195    *    </ul>
196    *    <br>Can be <jk>null</jk> to unset the property.
197    * @return This object
198    */
199   public HeaderInfo setRequired(Boolean value) {
200      required = value;
201      return this;
202   }
203
204   /**
205    * Bean property getter:  <property>required</property>.
206    *
207    * <p>
208    * The type of the object.
209    *
210    * @return The property value, or <jk>null</jk> if it is not set.
211    */
212   public Boolean getExplode() {
213      return explode;
214   }
215
216   /**
217    * Bean property setter:  <property>explode</property>.
218    *
219    * <p>
220    * The type of the object.
221    *
222    * @param value
223    *    The new value for this property.
224    *    <br>Can be <jk>null</jk> to unset the property.
225    * @return This object
226    */
227   public HeaderInfo setExplode(Boolean value) {
228      explode = value;
229      return this;
230   }
231
232   /**
233    * Bean property getter:  <property>deprecated</property>.
234    *
235    * <p>
236    * The type of the object.
237    *
238    * @return The property value, or <jk>null</jk> if it is not set.
239    */
240   public Boolean getDeprecated() {
241      return deprecated;
242   }
243
244   /**
245    * Bean property setter:  <property>deprecated</property>.
246    *
247    * <p>
248    * The type of the object.
249    *
250    * @param value
251    *    The new value for this property.
252    *    <br>Can be <jk>null</jk> to unset the property.
253    * @return This object
254    */
255   public HeaderInfo setDeprecated(Boolean value) {
256      deprecated = value;
257      return this;
258   }
259
260   /**
261    * Bean property getter:  <property>allowEmptyValue</property>.
262    *
263    * <p>
264    * The type of the object.
265    *
266    * @return The property value, or <jk>null</jk> if it is not set.
267    */
268   public Boolean getAllowEmptyValue() {
269      return allowEmptyValue;
270   }
271
272   /**
273    * Bean property setter:  <property>allowEmptyValue</property>.
274    *
275    * <p>
276    * The type of the object.
277    *
278    * @param value
279    *    The new value for this property.
280    *    <br>Can be <jk>null</jk> to unset the property.
281    * @return This object
282    */
283   public HeaderInfo setAllowEmptyValue(Boolean value) {
284      allowEmptyValue = value;
285      return this;
286   }
287
288   /**
289    * Bean property getter:  <property>allowReserved</property>.
290    *
291    * <p>
292    * The type of the object.
293    *
294    * @return The property value, or <jk>null</jk> if it is not set.
295    */
296   public Boolean getAllowReserved() {
297      return allowReserved;
298   }
299
300   /**
301    * Bean property setter:  <property>allowReserved</property>.
302    *
303    * <p>
304    * The type of the object.
305    *
306    * @param value
307    *    The new value for this property.
308    *    <br>Can be <jk>null</jk> to unset the property.
309    * @return This object
310    */
311   public HeaderInfo setAllowReserved(Boolean value) {
312      allowReserved = value;
313      return this;
314   }
315
316   /**
317    * Bean property getter:  <property>schema</property>.
318    *
319    * @return The property value, or <jk>null</jk> if it is not set.
320    */
321   public SchemaInfo getSchema() {
322      return schema;
323   }
324
325   /**
326    * Bean property setter:  <property>schema</property>.
327    *
328    * @param value
329    *    The new value for this property.
330    *    <br>Can be <jk>null</jk> to unset the property.
331    * @return This object
332    */
333   public HeaderInfo setSchema(SchemaInfo value) {
334      schema = value;
335      return this;
336   }
337
338   /**
339    * Bean property getter:  <property>$ref</property>.
340    *
341    * @return The property value, or <jk>null</jk> if it is not set.
342    */
343   @Beanp("$ref")
344   public String getRef() {
345      return ref;
346   }
347
348   /**
349    * Bean property setter:  <property>$ref</property>.
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   @Beanp("$ref")
357   public HeaderInfo setRef(String value) {
358      ref = value;
359      return this;
360   }
361
362   /**
363    * Bean property getter:  <property>x-example</property>.
364    *
365    * @return The property value, or <jk>null</jk> if it is not set.
366    */
367   @Beanp("x-example")
368   public Object getExample() {
369      return example;
370   }
371
372   /**
373    * Bean property setter:  <property>examples</property>.
374    *
375    * @param value
376    *    The new value for this property.
377    *    <br>Can be <jk>null</jk> to unset the property.
378    * @return This object
379    */
380   @Beanp("x-example")
381   public HeaderInfo setExample(Object value) {
382      example = value;
383      return this;
384   }
385
386   /**
387    * Bean property getter:  <property>examples</property>.
388    *
389    * <p>
390    * The list of possible responses as they are returned from executing this operation.
391    *
392    * @return The property value, or <jk>null</jk> if it is not set.
393    */
394   public Map<String,Example> getExamples() {
395      return examples;
396   }
397
398   /**
399    * Bean property setter:  <property>headers</property>.
400    *
401    * <p>
402    * A list of examples that are sent with the response.
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 = copyOf(value);
411      return this;
412   }
413
414   /**
415    * Adds a single value to the <property>examples</property> property.
416    *
417    * @param name The example name.  Must not be <jk>null</jk>.
418    * @param example The example.  Must not be <jk>null</jk>.
419    * @return This object
420    */
421   public HeaderInfo addExample(String name, Example example) {
422      assertArgNotNull("name", name);
423      assertArgNotNull("example", example);
424      examples = mapBuilder(examples).sparse().add(name, example).build();
425      return this;
426   }
427
428   @Override /* Overridden from OpenApiElement */
429   public <T> T get(String property, Class<T> type) {
430      assertArgNotNull("property", property);
431      return switch (property) {
432         case "description" -> (T)getDescription();
433         case "required" -> toType(getRequired(), type);
434         case "explode" -> toType(getExplode(), type);
435         case "deprecated" -> toType(getDeprecated(), type);
436         case "allowEmptyValue" -> toType(getAllowEmptyValue(), type);
437         case "allowReserved" -> toType(getAllowReserved(), type);
438         case "$ref" -> toType(getRef(), type);
439         case "schema" -> toType(getSchema(), type);
440         case "x-example" -> toType(getExample(), type);
441         case "examples" -> toType(getExamples(), type);
442         default -> super.get(property, type);
443      };
444   }
445
446   @Override /* Overridden from OpenApiElement */
447   public HeaderInfo set(String property, Object value) {
448      assertArgNotNull("property", property);
449      return switch (property) {
450         case "$ref" -> setRef(Utils.s(value));
451         case "allowEmptyValue" -> setAllowEmptyValue(toBoolean(value));
452         case "allowReserved" -> setAllowReserved(toBoolean(value));
453         case "deprecated" -> setDeprecated(toBoolean(value));
454         case "description" -> setDescription(Utils.s(value));
455         case "examples" -> setExamples(mapBuilder(String.class,Example.class).sparse().addAny(value).build());
456         case "explode" -> setExplode(toBoolean(value));
457         case "required" -> setRequired(toBoolean(value));
458         case "schema" -> setSchema(toType(value, SchemaInfo.class));
459         case "x-example" -> setExample(value);
460         default -> {
461            super.set(property, value);
462            yield this;
463         }
464      };
465   }
466
467   @Override /* SwaggerElement */
468   public Set<String> keySet() {
469      var s = setBuilder(String.class)
470         .addIf(ref != null, "$ref")
471         .addIf(allowEmptyValue != null, "allowEmptyValue")
472         .addIf(allowReserved != null, "allowReserved")
473         .addIf(deprecated != null, "deprecated")
474         .addIf(description != null, "description")
475         .addIf(examples != null, "examples")
476         .addIf(explode != null, "explode")
477         .addIf(required != null, "required")
478         .addIf(schema != null, "schema")
479         .addIf(example != null, "x-example")
480         .build();
481      return new MultiSet<>(s, super.keySet());
482
483   }
484
485   /**
486    * Resolves any <js>"$ref"</js> attributes in this element.
487    *
488    * @param openApi The swagger document containing the definitions.
489    * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops.
490    * @param maxDepth
491    *    The maximum depth to resolve references.
492    *    <br>After that level is reached, <code>$ref</code> references will be left alone.
493    *    <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex.
494    * @return
495    *    This object with references resolved.
496    *    <br>May or may not be the same object.
497    */
498   public HeaderInfo resolveRefs(OpenApi openApi, Deque<String> refStack, int maxDepth) {
499
500      if (ref != null) {
501         if (refStack.contains(ref) || refStack.size() >= maxDepth)
502            return this;
503         refStack.addLast(ref);
504         var r = openApi.findRef(ref, HeaderInfo.class);
505         r = r.resolveRefs(openApi, refStack, maxDepth);
506         refStack.removeLast();
507         return r;
508      }
509      return this;
510   }
511}