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 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}