poem_openapi/registry/
mod.rs

1mod clean_unused;
2mod ser;
3
4use std::{
5    cmp::Ordering,
6    collections::{BTreeMap, BTreeSet, HashMap},
7    hash::{Hash, Hasher},
8};
9
10use poem::http::Method;
11pub(crate) use ser::Document;
12use serde::{Serialize, Serializer, ser::SerializeMap};
13use serde_json::Value;
14
15use crate::{ParameterStyle, types::Type};
16
17#[allow(clippy::trivially_copy_pass_by_ref)]
18#[inline]
19const fn is_false(value: &bool) -> bool {
20    !*value
21}
22
23#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
24#[serde(rename_all = "camelCase")]
25pub struct MetaDiscriminatorObject {
26    pub property_name: &'static str,
27    #[serde(
28        skip_serializing_if = "Vec::is_empty",
29        serialize_with = "serialize_mapping"
30    )]
31    pub mapping: Vec<(String, String)>,
32}
33
34fn serialize_mapping<S: Serializer>(
35    mapping: &[(String, String)],
36    serializer: S,
37) -> Result<S::Ok, S::Error> {
38    let mut s = serializer.serialize_map(None)?;
39    for (name, ref_name) in mapping {
40        s.serialize_entry(name, ref_name)?;
41    }
42    s.end()
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize)]
46#[serde(rename_all = "camelCase")]
47pub struct MetaSchema {
48    #[serde(skip)]
49    pub rust_typename: Option<&'static str>,
50
51    #[serde(rename = "type", skip_serializing_if = "str::is_empty")]
52    pub ty: &'static str,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub format: Option<&'static str>,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub title: Option<String>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub description: Option<&'static str>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub external_docs: Option<MetaExternalDocument>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub default: Option<Value>,
63    #[serde(skip_serializing_if = "Vec::is_empty")]
64    pub required: Vec<&'static str>,
65    #[serde(
66        skip_serializing_if = "Vec::is_empty",
67        serialize_with = "serialize_properties"
68    )]
69    pub properties: Vec<(&'static str, MetaSchemaRef)>,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub items: Option<Box<MetaSchemaRef>>,
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub additional_properties: Option<Box<MetaSchemaRef>>,
74    #[serde(rename = "enum", skip_serializing_if = "Vec::is_empty")]
75    pub enum_items: Vec<Value>,
76    #[serde(skip_serializing_if = "is_false")]
77    pub deprecated: bool,
78    #[serde(skip_serializing_if = "is_false")]
79    pub nullable: bool,
80    #[serde(skip_serializing_if = "Vec::is_empty")]
81    pub any_of: Vec<MetaSchemaRef>,
82    #[serde(skip_serializing_if = "Vec::is_empty")]
83    pub one_of: Vec<MetaSchemaRef>,
84    #[serde(skip_serializing_if = "Vec::is_empty")]
85    pub all_of: Vec<MetaSchemaRef>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub discriminator: Option<MetaDiscriminatorObject>,
88    #[serde(skip_serializing_if = "is_false")]
89    pub read_only: bool,
90    #[serde(skip_serializing_if = "is_false")]
91    pub write_only: bool,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub example: Option<Value>,
94
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub multiple_of: Option<f64>,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub maximum: Option<f64>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub exclusive_maximum: Option<bool>,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub minimum: Option<f64>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub exclusive_minimum: Option<bool>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub max_length: Option<usize>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub min_length: Option<usize>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub pattern: Option<String>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub max_items: Option<usize>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub min_items: Option<usize>,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub unique_items: Option<bool>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub max_properties: Option<usize>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub min_properties: Option<usize>,
121}
122
123fn serialize_properties<S: Serializer>(
124    properties: &[(&'static str, MetaSchemaRef)],
125    serializer: S,
126) -> Result<S::Ok, S::Error> {
127    let mut s = serializer.serialize_map(None)?;
128    for item in properties {
129        s.serialize_entry(item.0, &item.1)?;
130    }
131    s.end()
132}
133
134impl MetaSchema {
135    pub const ANY: Self = MetaSchema {
136        rust_typename: None,
137        ty: "",
138        format: None,
139        title: None,
140        description: None,
141        external_docs: None,
142        default: None,
143        required: vec![],
144        properties: vec![],
145        items: None,
146        additional_properties: None,
147        enum_items: vec![],
148        deprecated: false,
149        any_of: vec![],
150        one_of: vec![],
151        all_of: vec![],
152        discriminator: None,
153        read_only: false,
154        write_only: false,
155        nullable: false,
156        example: None,
157        multiple_of: None,
158        maximum: None,
159        exclusive_maximum: None,
160        minimum: None,
161        exclusive_minimum: None,
162        max_length: None,
163        min_length: None,
164        pattern: None,
165        max_items: None,
166        min_items: None,
167        unique_items: None,
168        max_properties: None,
169        min_properties: None,
170    };
171
172    pub fn new(ty: &'static str) -> Self {
173        Self { ty, ..Self::ANY }
174    }
175
176    pub fn new_with_format(ty: &'static str, format: &'static str) -> Self {
177        MetaSchema {
178            ty,
179            format: Some(format),
180            ..Self::ANY
181        }
182    }
183
184    pub fn is_empty(&self) -> bool {
185        self == &Self::ANY
186    }
187
188    #[must_use]
189    pub fn merge(
190        mut self,
191        MetaSchema {
192            default,
193            read_only,
194            write_only,
195            deprecated,
196            nullable,
197            title,
198            description,
199            external_docs,
200            items,
201            additional_properties,
202            example,
203            multiple_of,
204            maximum,
205            exclusive_maximum,
206            minimum,
207            exclusive_minimum,
208            max_length,
209            min_length,
210            pattern,
211            max_items,
212            min_items,
213            unique_items,
214            max_properties,
215            min_properties,
216            ..
217        }: MetaSchema,
218    ) -> Self {
219        self.read_only |= read_only;
220        self.write_only |= write_only;
221        self.nullable |= nullable;
222        self.deprecated |= deprecated;
223
224        macro_rules! merge_optional {
225            ($($name:ident),*) => {
226                $(
227                if $name.is_some() {
228                    self.$name = $name;
229                }
230                )*
231            };
232        }
233
234        merge_optional!(
235            default,
236            title,
237            description,
238            external_docs,
239            example,
240            multiple_of,
241            maximum,
242            exclusive_maximum,
243            minimum,
244            exclusive_minimum,
245            max_length,
246            min_length,
247            pattern,
248            max_items,
249            min_items,
250            unique_items,
251            max_properties,
252            min_properties
253        );
254
255        if let Some(items) = items {
256            if let Some(self_items) = self.items {
257                let items = *items;
258
259                match items {
260                    MetaSchemaRef::Inline(items) => {
261                        self.items = Some(Box::new(self_items.merge(*items)))
262                    }
263                    MetaSchemaRef::Reference(_) => {
264                        self.items = Some(Box::new(MetaSchemaRef::Inline(Box::new(MetaSchema {
265                            any_of: vec![*self_items, items],
266                            ..MetaSchema::ANY
267                        }))));
268                    }
269                }
270            } else {
271                self.items = Some(items);
272            }
273        }
274
275        if let Some(additional_properties) = additional_properties {
276            if let Some(self_additional_properties) = self.additional_properties {
277                let additional_properties = *additional_properties;
278
279                match additional_properties {
280                    MetaSchemaRef::Inline(additional_properties) => {
281                        self.additional_properties = Some(Box::new(
282                            self_additional_properties.merge(*additional_properties),
283                        ))
284                    }
285                    MetaSchemaRef::Reference(_) => {
286                        self.additional_properties =
287                            Some(Box::new(MetaSchemaRef::Inline(Box::new(MetaSchema {
288                                any_of: vec![*self_additional_properties, additional_properties],
289                                ..MetaSchema::ANY
290                            }))));
291                    }
292                }
293            } else {
294                self.additional_properties = Some(additional_properties);
295            }
296        }
297
298        self
299    }
300}
301
302#[derive(Debug, Clone, PartialEq)]
303pub enum MetaSchemaRef {
304    Inline(Box<MetaSchema>),
305    Reference(String),
306}
307
308impl MetaSchemaRef {
309    pub fn is_array(&self) -> bool {
310        matches!(self, MetaSchemaRef::Inline(schema) if schema.ty == "array")
311    }
312
313    pub fn is_object(&self) -> bool {
314        matches!(self, MetaSchemaRef::Inline(schema) if schema.ty == "object")
315    }
316
317    pub fn unwrap_inline(&self) -> &MetaSchema {
318        match &self {
319            MetaSchemaRef::Inline(schema) => schema,
320            MetaSchemaRef::Reference(_) => panic!(),
321        }
322    }
323
324    pub fn unwrap_reference(&self) -> &str {
325        match self {
326            MetaSchemaRef::Inline(_) => panic!(),
327            MetaSchemaRef::Reference(name) => name,
328        }
329    }
330
331    #[must_use]
332    pub fn merge(self, other: MetaSchema) -> Self {
333        match self {
334            MetaSchemaRef::Inline(schema) => MetaSchemaRef::Inline(Box::new(schema.merge(other))),
335            MetaSchemaRef::Reference(name) => {
336                let other = MetaSchema::ANY.merge(other);
337                if other.is_empty() {
338                    MetaSchemaRef::Reference(name)
339                } else {
340                    MetaSchemaRef::Inline(Box::new(MetaSchema {
341                        all_of: vec![
342                            MetaSchemaRef::Reference(name),
343                            MetaSchemaRef::Inline(Box::new(other.clone())),
344                        ],
345                        ..other
346                    }))
347                }
348            }
349        }
350    }
351}
352
353#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize)]
354#[serde(rename_all = "lowercase")]
355pub enum MetaParamIn {
356    Query,
357    Header,
358    Path,
359    Cookie,
360    #[serde(rename = "cookie")]
361    CookiePrivate,
362    #[serde(rename = "cookie")]
363    CookieSigned,
364}
365
366#[derive(Debug, PartialEq, Serialize)]
367pub struct MetaOperationParam {
368    pub name: String,
369    pub schema: MetaSchemaRef,
370    #[serde(rename = "in")]
371    pub in_type: MetaParamIn,
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub description: Option<String>,
374    pub required: bool,
375    pub deprecated: bool,
376    pub explode: bool,
377    #[serde(skip_serializing_if = "Option::is_none")]
378    pub style: Option<ParameterStyle>,
379}
380
381#[derive(Debug, PartialEq, Serialize)]
382pub struct MetaMediaType {
383    #[serde(skip)]
384    pub content_type: &'static str,
385    pub schema: MetaSchemaRef,
386}
387
388#[derive(Debug, PartialEq, Serialize)]
389pub struct MetaRequest {
390    #[serde(skip_serializing_if = "Option::is_none")]
391    pub description: Option<&'static str>,
392    #[serde(
393        skip_serializing_if = "Vec::is_empty",
394        serialize_with = "serialize_content"
395    )]
396    pub content: Vec<MetaMediaType>,
397    pub required: bool,
398}
399
400fn serialize_content<S: Serializer>(
401    content: &[MetaMediaType],
402    serializer: S,
403) -> Result<S::Ok, S::Error> {
404    let mut s = serializer.serialize_map(None)?;
405    for item in content {
406        s.serialize_entry(item.content_type, item)?;
407    }
408    s.end()
409}
410
411#[derive(Debug, PartialEq)]
412pub struct MetaResponses {
413    pub responses: Vec<MetaResponse>,
414}
415
416#[derive(Debug, PartialEq, Serialize)]
417#[serde(rename_all = "camelCase")]
418pub struct MetaHeader {
419    #[serde(skip)]
420    pub name: String,
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub description: Option<String>,
423    #[serde(skip_serializing_if = "is_false")]
424    pub required: bool,
425    pub deprecated: bool,
426    pub schema: MetaSchemaRef,
427}
428
429#[derive(Debug, PartialEq, Serialize)]
430pub struct MetaResponse {
431    pub description: &'static str,
432    #[serde(skip)]
433    pub status: Option<u16>,
434    #[serde(skip)]
435    pub status_range: Option<String>,
436    #[serde(
437        skip_serializing_if = "Vec::is_empty",
438        serialize_with = "serialize_content"
439    )]
440    pub content: Vec<MetaMediaType>,
441    #[serde(
442        skip_serializing_if = "Vec::is_empty",
443        serialize_with = "serialize_headers"
444    )]
445    pub headers: Vec<MetaHeader>,
446}
447
448fn serialize_headers<S: Serializer>(
449    properties: &[MetaHeader],
450    serializer: S,
451) -> Result<S::Ok, S::Error> {
452    let mut s = serializer.serialize_map(None)?;
453    for header in properties {
454        s.serialize_entry(&header.name, &header)?;
455    }
456    s.end()
457}
458
459#[derive(Debug, PartialEq, Serialize)]
460#[serde(rename_all = "camelCase")]
461pub struct MetaWebhook {
462    pub name: &'static str,
463    pub operation: MetaOperation,
464}
465
466#[derive(Debug, Eq, PartialEq, Serialize)]
467#[serde(rename_all = "camelCase")]
468pub struct MetaCodeSample {
469    pub lang: &'static str,
470    #[serde(skip_serializing_if = "Option::is_none")]
471    pub label: Option<&'static str>,
472    pub source: &'static str,
473}
474
475#[derive(Debug, PartialEq, Serialize)]
476#[serde(rename_all = "camelCase")]
477pub struct MetaOperation {
478    #[serde(skip)]
479    pub method: Method,
480    #[serde(skip_serializing_if = "Vec::is_empty")]
481    pub tags: Vec<&'static str>,
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub summary: Option<&'static str>,
484    #[serde(skip_serializing_if = "Option::is_none")]
485    pub description: Option<&'static str>,
486    #[serde(skip_serializing_if = "Option::is_none")]
487    pub external_docs: Option<MetaExternalDocument>,
488    #[serde(rename = "parameters", skip_serializing_if = "Vec::is_empty")]
489    pub params: Vec<MetaOperationParam>,
490    #[serde(rename = "requestBody", skip_serializing_if = "Option::is_none")]
491    pub request: Option<MetaRequest>,
492    pub responses: MetaResponses,
493    #[serde(skip_serializing_if = "is_false")]
494    pub deprecated: bool,
495    #[serde(skip_serializing_if = "Vec::is_empty")]
496    pub security: Vec<HashMap<&'static str, Vec<&'static str>>>,
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub operation_id: Option<&'static str>,
499    #[serde(rename = "x-code-samples", skip_serializing_if = "Vec::is_empty")]
500    pub code_samples: Vec<MetaCodeSample>,
501}
502
503#[derive(Debug, PartialEq)]
504pub struct MetaPath {
505    pub path: String,
506    pub operations: Vec<MetaOperation>,
507}
508
509#[derive(Debug, Default, Eq, PartialEq, Serialize, Clone)]
510pub struct MetaContact {
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub name: Option<String>,
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub url: Option<String>,
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub email: Option<String>,
517}
518
519#[derive(Debug, Default, Eq, PartialEq, Serialize, Clone)]
520pub struct MetaLicense {
521    pub name: String,
522    #[serde(skip_serializing_if = "Option::is_none")]
523    pub identifier: Option<String>,
524    #[serde(skip_serializing_if = "Option::is_none")]
525    pub url: Option<String>,
526}
527
528#[derive(Debug, Default, Eq, PartialEq, Serialize, Clone)]
529#[serde(rename_all = "camelCase")]
530pub struct MetaInfo {
531    pub title: String,
532    #[serde(skip_serializing_if = "Option::is_none")]
533    pub summary: Option<String>,
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub description: Option<String>,
536    pub version: String,
537    #[serde(skip_serializing_if = "Option::is_none")]
538    pub terms_of_service: Option<String>,
539    #[serde(skip_serializing_if = "Option::is_none")]
540    pub contact: Option<MetaContact>,
541    #[serde(skip_serializing_if = "Option::is_none")]
542    pub license: Option<MetaLicense>,
543}
544
545#[derive(Debug, Eq, PartialEq, Serialize, Clone)]
546pub struct MetaServer {
547    pub url: String,
548    #[serde(skip_serializing_if = "Option::is_none")]
549    pub description: Option<String>,
550
551    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
552    pub variables: BTreeMap<String, MetaServerVariable>,
553}
554
555#[derive(Debug, Eq, PartialEq, Serialize, Clone)]
556pub struct MetaServerVariable {
557    pub default: String,
558
559    #[serde(skip_serializing_if = "String::is_empty")]
560    pub description: String,
561
562    #[serde(rename = "enum", skip_serializing_if = "Vec::is_empty")]
563    pub enum_values: Vec<String>,
564}
565
566#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
567pub struct MetaExternalDocument {
568    pub url: String,
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub description: Option<String>,
571}
572
573#[derive(Debug, Serialize)]
574#[serde(rename_all = "camelCase")]
575pub struct MetaTag {
576    pub name: &'static str,
577    #[serde(skip_serializing_if = "Option::is_none")]
578    pub description: Option<&'static str>,
579    #[serde(skip_serializing_if = "Option::is_none")]
580    pub external_docs: Option<MetaExternalDocument>,
581}
582
583impl PartialEq for MetaTag {
584    fn eq(&self, other: &Self) -> bool {
585        self.name.eq(other.name)
586    }
587}
588
589impl Eq for MetaTag {}
590
591impl Ord for MetaTag {
592    fn cmp(&self, other: &Self) -> Ordering {
593        self.name.cmp(other.name)
594    }
595}
596
597impl PartialOrd for MetaTag {
598    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
599        Some(self.cmp(other))
600    }
601}
602
603impl Hash for MetaTag {
604    fn hash<H: Hasher>(&self, state: &mut H) {
605        self.name.hash(state);
606    }
607}
608
609#[derive(Debug, Eq, PartialEq, Serialize)]
610pub struct MetaOAuthScope {
611    pub name: &'static str,
612    pub description: Option<&'static str>,
613}
614
615#[derive(Debug, Eq, PartialEq, Serialize)]
616#[serde(rename_all = "camelCase")]
617pub struct MetaOAuthFlow {
618    #[serde(skip_serializing_if = "Option::is_none")]
619    pub authorization_url: Option<&'static str>,
620    #[serde(skip_serializing_if = "Option::is_none")]
621    pub token_url: Option<&'static str>,
622    #[serde(skip_serializing_if = "Option::is_none")]
623    pub refresh_url: Option<&'static str>,
624    #[serde(
625        skip_serializing_if = "Vec::is_empty",
626        serialize_with = "serialize_oauth_flow_scopes"
627    )]
628    pub scopes: Vec<MetaOAuthScope>,
629}
630
631fn serialize_oauth_flow_scopes<S: Serializer>(
632    properties: &[MetaOAuthScope],
633    serializer: S,
634) -> Result<S::Ok, S::Error> {
635    let mut s = serializer.serialize_map(None)?;
636    for item in properties {
637        s.serialize_entry(item.name, item.description.unwrap_or_default())?;
638    }
639    s.end()
640}
641
642#[derive(Debug, Eq, PartialEq, Serialize)]
643#[serde(rename_all = "camelCase")]
644pub struct MetaOAuthFlows {
645    #[serde(skip_serializing_if = "Option::is_none")]
646    pub implicit: Option<MetaOAuthFlow>,
647    #[serde(skip_serializing_if = "Option::is_none")]
648    pub password: Option<MetaOAuthFlow>,
649    #[serde(skip_serializing_if = "Option::is_none")]
650    pub client_credentials: Option<MetaOAuthFlow>,
651    #[serde(skip_serializing_if = "Option::is_none")]
652    pub authorization_code: Option<MetaOAuthFlow>,
653}
654
655#[derive(Debug, Serialize, Eq, PartialEq)]
656#[serde(rename_all = "camelCase")]
657pub struct MetaSecurityScheme {
658    #[serde(rename = "type")]
659    pub ty: &'static str,
660    #[serde(skip_serializing_if = "Option::is_none")]
661    pub description: Option<&'static str>,
662    #[serde(skip_serializing_if = "Option::is_none")]
663    pub name: Option<&'static str>,
664    #[serde(rename = "in", skip_serializing_if = "Option::is_none")]
665    pub key_in: Option<&'static str>,
666    #[serde(skip_serializing_if = "Option::is_none")]
667    pub scheme: Option<&'static str>,
668    #[serde(skip_serializing_if = "Option::is_none")]
669    pub bearer_format: Option<&'static str>,
670    #[serde(skip_serializing_if = "Option::is_none")]
671    pub flows: Option<MetaOAuthFlows>,
672    #[serde(rename = "openIdConnectUrl", skip_serializing_if = "Option::is_none")]
673    pub openid_connect_url: Option<&'static str>,
674}
675
676#[derive(Debug, PartialEq)]
677pub struct MetaApi {
678    pub paths: Vec<MetaPath>,
679}
680
681#[derive(Default)]
682pub struct Registry {
683    pub schemas: BTreeMap<String, MetaSchema>,
684    pub tags: BTreeSet<MetaTag>,
685    pub security_schemes: BTreeMap<&'static str, MetaSecurityScheme>,
686}
687
688impl Registry {
689    pub fn new() -> Self {
690        Default::default()
691    }
692
693    pub fn create_schema<T, F>(&mut self, name: String, f: F)
694    where
695        F: FnOnce(&mut Registry) -> MetaSchema,
696    {
697        match self.schemas.get(&name) {
698            Some(schema) => {
699                if let Some(prev_typename) = schema.rust_typename {
700                    if prev_typename != std::any::type_name::<T>() {
701                        panic!(
702                            "`{}` and `{}` have the same OpenAPI name `{}`",
703                            prev_typename,
704                            std::any::type_name::<T>(),
705                            name,
706                        );
707                    }
708                }
709            }
710            None => {
711                // Inserting a fake type before calling the function allows recursive types to
712                // exist.
713                self.schemas.insert(name.clone(), MetaSchema::new("fake"));
714                let mut meta_schema = f(self);
715                meta_schema.rust_typename = Some(std::any::type_name::<T>());
716                *self.schemas.get_mut(&name).unwrap() = meta_schema;
717            }
718        }
719    }
720
721    pub fn create_fake_schema<T: Type>(&mut self) -> MetaSchema {
722        match T::schema_ref() {
723            MetaSchemaRef::Inline(schema) => *schema,
724            MetaSchemaRef::Reference(name) => {
725                T::register(self);
726                self.schemas
727                    .get(&name)
728                    .cloned()
729                    .expect("You definitely encountered a bug!")
730            }
731        }
732    }
733
734    pub fn create_tag(&mut self, tag: MetaTag) {
735        self.tags.insert(tag);
736    }
737
738    pub fn create_security_scheme(
739        &mut self,
740        name: &'static str,
741        security_scheme: MetaSecurityScheme,
742    ) {
743        if !self.security_schemes.contains_key(name) {
744            self.security_schemes.insert(name, security_scheme);
745        }
746    }
747}