paperclip_core/v2/
models.rs

1//! Models used by OpenAPI v2.
2
3pub use super::extensions::{
4    Coder, Coders, MediaRange, JSON_CODER, JSON_MIME, YAML_CODER, YAML_MIME,
5};
6
7use super::schema::Schema;
8use crate::error::ValidationError;
9use once_cell::sync::Lazy;
10use paperclip_macros::api_v2_schema_struct;
11use regex::{Captures, Regex};
12use serde::ser::{SerializeMap, Serializer};
13
14#[cfg(feature = "actix-base")]
15use actix_web::http::Method;
16
17use std::{
18    borrow::Cow,
19    collections::{BTreeMap, BTreeSet},
20    fmt::{self, Display},
21    ops::{Deref, DerefMut},
22    sync::{Arc, RwLock},
23};
24
25/// Regex that can be used for fetching templated path parameters.
26static PATH_TEMPLATE_REGEX: Lazy<Regex> =
27    Lazy::new(|| Regex::new(r"\{(.*?)\}").expect("path template regex"));
28
29// Headers that have special meaning in OpenAPI. These cannot be used in header parameter.
30// Ensure that they're all lowercase for case insensitive check.
31const SPECIAL_HEADERS: &[&str] = &["content-type", "accept", "authorization"];
32
33/// OpenAPI version.
34#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]
35pub enum Version {
36    #[serde(rename = "2.0")]
37    V2,
38}
39
40/// Supported data types.
41#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]
42#[serde(rename_all = "lowercase")]
43pub enum DataType {
44    Integer,
45    Number,
46    String,
47    Boolean,
48    Array,
49    Object,
50    File,
51}
52
53impl DataType {
54    /// Checks if this is a primitive type.
55    #[inline]
56    pub fn is_primitive(self) -> bool {
57        std::matches!(
58            self,
59            DataType::Integer | DataType::Number | DataType::String | DataType::Boolean
60        )
61    }
62}
63
64/// Supported data type formats.
65#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
66#[serde(rename_all = "lowercase")]
67pub enum DataTypeFormat {
68    Int32,
69    Int64,
70    Float,
71    Double,
72    Byte,
73    Binary,
74    Date,
75    #[serde(rename = "date-time")]
76    DateTime,
77    Password,
78    Url,
79    Uuid,
80    Ip,
81    IpV4,
82    IpV6,
83    #[serde(other)]
84    Other,
85}
86
87#[allow(clippy::to_string_trait_impl)]
88impl ToString for DataTypeFormat {
89    fn to_string(&self) -> String {
90        match self {
91            DataTypeFormat::Int32 => "int32",
92            DataTypeFormat::Int64 => "int64",
93            DataTypeFormat::Float => "float",
94            DataTypeFormat::Double => "double",
95            DataTypeFormat::Byte => "byte",
96            DataTypeFormat::Binary => "binary",
97            DataTypeFormat::Date => "date",
98            DataTypeFormat::DateTime => "datetime",
99            DataTypeFormat::Password => "password",
100            DataTypeFormat::Url => "url",
101            DataTypeFormat::Uuid => "uuid",
102            DataTypeFormat::Ip => "ip",
103            DataTypeFormat::IpV4 => "ipv4",
104            DataTypeFormat::IpV6 => "ipv6",
105            // would be nice if Other was Other(String)
106            DataTypeFormat::Other => "other",
107        }
108        .to_string()
109    }
110}
111
112impl From<DataTypeFormat> for DataType {
113    fn from(src: DataTypeFormat) -> Self {
114        match src {
115            DataTypeFormat::Int32 => Self::Integer,
116            DataTypeFormat::Int64 => Self::Integer,
117            DataTypeFormat::Float => Self::Number,
118            DataTypeFormat::Double => Self::Number,
119            DataTypeFormat::Byte => Self::String,
120            DataTypeFormat::Binary => Self::String,
121            DataTypeFormat::Date => Self::String,
122            DataTypeFormat::DateTime => Self::String,
123            DataTypeFormat::Password => Self::String,
124            DataTypeFormat::Url => Self::String,
125            DataTypeFormat::Uuid => Self::String,
126            DataTypeFormat::Ip => Self::String,
127            DataTypeFormat::IpV4 => Self::String,
128            DataTypeFormat::IpV6 => Self::String,
129            DataTypeFormat::Other => Self::Object,
130        }
131    }
132}
133
134/// OpenAPI v2 spec which can be traversed and resolved for codegen.
135pub type ResolvableApi<S> = Api<ResolvableParameter<S>, ResolvableResponse<S>, Resolvable<S>>;
136
137/// OpenAPI v2 spec with defaults.
138pub type DefaultApiRaw = Api<DefaultParameterRaw, DefaultResponseRaw, DefaultSchemaRaw>;
139
140fn strip_templates_from_paths<P: serde::ser::Serialize, R: serde::ser::Serialize, S: Serializer>(
141    tree: &BTreeMap<String, PathItem<P, R>>,
142    serializer: S,
143) -> Result<S::Ok, S::Error> {
144    let len = tree.len();
145    let mut map = serializer.serialize_map(Some(len))?;
146    for (k, v) in tree {
147        let path = strip_pattern_from_template(k);
148        map.serialize_entry(&path, v)?;
149    }
150    map.end()
151}
152
153fn strip_pattern_from_template(path: &str) -> String {
154    let mut clean_path = path.to_string();
155    for cap in PATH_TEMPLATE_REGEX.captures_iter(path) {
156        let name_only = cap[1]
157            .split_once(':')
158            .map(|t| t.0.to_string())
159            .unwrap_or_else(|| cap[1].to_string());
160        if cap[1] != name_only {
161            clean_path = clean_path.replace(
162                format!("{{{}}}", &cap[1]).as_str(),
163                format!("{{{}}}", name_only).as_str(),
164            );
165        }
166    }
167    clean_path
168}
169
170/// OpenAPI v2 (swagger) spec generic over parameter and schema.
171///
172/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object>
173#[derive(Clone, Debug, Default, Serialize, Deserialize)]
174pub struct Api<P, R, S> {
175    pub swagger: Version,
176    #[serde(default = "BTreeMap::new")]
177    pub definitions: BTreeMap<String, S>,
178    #[serde(serialize_with = "strip_templates_from_paths")]
179    pub paths: BTreeMap<String, PathItem<P, R>>,
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub host: Option<String>,
182    #[serde(rename = "basePath", skip_serializing_if = "Option::is_none")]
183    pub base_path: Option<String>,
184    #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
185    pub consumes: BTreeSet<MediaRange>,
186    #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
187    pub produces: BTreeSet<MediaRange>,
188    #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
189    pub schemes: BTreeSet<OperationProtocol>,
190    #[serde(default = "BTreeMap::new", skip_serializing_if = "BTreeMap::is_empty")]
191    pub parameters: BTreeMap<String, P>,
192    #[serde(default = "BTreeMap::new", skip_serializing_if = "BTreeMap::is_empty")]
193    pub responses: BTreeMap<String, R>,
194    #[serde(
195        default,
196        rename = "securityDefinitions",
197        skip_serializing_if = "BTreeMap::is_empty"
198    )]
199    pub security_definitions: BTreeMap<String, SecurityScheme>,
200    #[serde(default, skip_serializing_if = "Vec::is_empty")]
201    pub security: Vec<BTreeMap<String, BTreeSet<String>>>,
202    #[serde(default, skip_serializing_if = "Vec::is_empty")]
203    pub tags: Vec<Tag>,
204    #[serde(rename = "externalDocs", skip_serializing_if = "Option::is_none")]
205    pub external_docs: Option<ExternalDocs>,
206    /// Extension for custom coders to be used for decoding API objects.
207    ///
208    /// An example for JSON would be:
209    /// ```yaml
210    /// x-rust-coders:
211    ///   application/json:
212    ///     encoder_path: serde_json::to_writer
213    ///     decoder_path: serde_json::from_reader
214    ///     any_value: serde_json::Value
215    ///     error_path: serde_json::Error
216    /// ```
217    /// **NOTE:** JSON and YAML encodings are already supported.
218    #[serde(
219        default,
220        rename = "x-rust-coders",
221        skip_serializing_if = "<Coders as Deref>::Target::is_empty"
222    )]
223    pub coders: Coders,
224    /// Additional crates that need to be added to the manifest.
225    ///
226    /// The key is the LHS of a dependency, which is the crate name.
227    /// The value is the RHS of a crate's requirements as it would appear
228    /// in the manifest. Note that the caller must add proper escaping
229    /// wherever required.
230    ///
231    /// For example, the following are all valid:
232    /// - `my_crate: "0.7"`
233    /// - `my_crate: "{ git = \"git://foo.bar/repo\" }"`
234    /// - `my_crate: "{ version = \"0.9\", features = [\"booya\"] }"`
235    #[serde(
236        default,
237        rename = "x-rust-dependencies",
238        skip_serializing_if = "BTreeMap::is_empty"
239    )]
240    pub support_crates: BTreeMap<String, String>,
241    /// This field is set manually, because we don't know the format in which
242    /// the spec was provided and we need to use this as the fallback encoding.
243    #[serde(skip)]
244    pub spec_format: SpecFormat,
245    pub info: Info,
246
247    #[serde(
248        flatten,
249        skip_serializing_if = "BTreeMap::is_empty",
250        deserialize_with = "crate::v2::extensions::deserialize_extensions"
251    )]
252    pub extensions: BTreeMap<String, serde_json::Value>,
253}
254
255/// The format used by spec (JSON/YAML).
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
257pub enum SpecFormat {
258    Json,
259    Yaml,
260}
261
262impl SpecFormat {
263    /// The en/decoder used for this format.
264    pub fn coder(self) -> Arc<Coder> {
265        match self {
266            SpecFormat::Json => JSON_CODER.clone(),
267            SpecFormat::Yaml => YAML_CODER.clone(),
268        }
269    }
270
271    /// The mime for this format.
272    pub fn mime(self) -> &'static MediaRange {
273        match self {
274            SpecFormat::Json => &JSON_MIME,
275            SpecFormat::Yaml => &YAML_MIME,
276        }
277    }
278}
279
280impl<P, R, S> Api<P, R, S> {
281    /// Gets the parameters from the given path template and calls
282    /// the given function with the parameter names.
283    pub fn path_parameters_map(
284        path: &str,
285        mut f: impl FnMut(&str) -> Cow<'static, str>,
286    ) -> Cow<'_, str> {
287        PATH_TEMPLATE_REGEX.replace_all(path, |c: &Captures| f(&c[1]))
288    }
289}
290
291use crate as paperclip; // hack for proc macro
292
293/// Default schema if your schema doesn't have any custom fields.
294///
295/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject>
296#[api_v2_schema_struct]
297#[derive(Clone, Debug, Serialize, Deserialize)]
298pub struct DefaultSchema;
299
300/// Info object.
301///
302/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#infoObject>
303#[derive(Clone, Debug, Default, Serialize, Deserialize)]
304pub struct Info {
305    pub version: String,
306    pub title: String,
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub description: Option<String>,
309    #[serde(rename = "termsOfService", skip_serializing_if = "Option::is_none")]
310    pub terms_of_service: Option<String>,
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub contact: Option<Contact>,
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub license: Option<License>,
315    /// Inline extensions to this object.
316    #[serde(
317        flatten,
318        skip_serializing_if = "BTreeMap::is_empty",
319        deserialize_with = "crate::v2::extensions::deserialize_extensions"
320    )]
321    pub extensions: BTreeMap<String, serde_json::Value>,
322}
323
324/// Contact object.
325///
326/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#contactObject>
327#[derive(Clone, Debug, Default, Serialize, Deserialize)]
328pub struct Contact {
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub name: Option<String>,
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub url: Option<String>,
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub email: Option<String>,
335}
336
337/// License object.
338///
339/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#licenseObject>
340#[derive(Clone, Debug, Default, Serialize, Deserialize)]
341pub struct License {
342    #[serde(skip_serializing_if = "Option::is_none")]
343    pub name: Option<String>,
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub url: Option<String>,
346}
347
348/// Security Scheme object.
349///
350/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#security-scheme-object>
351#[derive(Clone, Debug, Default, Serialize, Deserialize)]
352pub struct SecurityScheme {
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub name: Option<String>,
355    #[serde(rename = "type")]
356    pub type_: String,
357    #[serde(rename = "in", skip_serializing_if = "Option::is_none")]
358    pub in_: Option<String>,
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub flow: Option<String>,
361    #[serde(rename = "authorizationUrl", skip_serializing_if = "Option::is_none")]
362    pub auth_url: Option<String>,
363    #[serde(rename = "tokenUrl", skip_serializing_if = "Option::is_none")]
364    pub token_url: Option<String>,
365    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
366    pub scopes: BTreeMap<String, String>,
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub description: Option<String>,
369}
370
371impl SecurityScheme {
372    /// Adds or updates this definition to the map of security definitions.
373    pub fn update_definitions(mut self, name: &str, map: &mut BTreeMap<String, SecurityScheme>) {
374        if let Some(existing) = map.get_mut(name) {
375            existing.name = existing.name.take().or(self.name);
376            if !self.type_.is_empty() {
377                existing.type_ = self.type_;
378            }
379            existing.in_ = existing.in_.take().or(self.in_);
380            existing.flow = existing.flow.take().or(self.flow);
381            existing.auth_url = existing.auth_url.take().or(self.auth_url);
382            existing.token_url = existing.token_url.take().or(self.token_url);
383            existing.scopes.append(&mut self.scopes);
384            existing.description = existing.description.take().or(self.description);
385            return;
386        }
387
388        map.insert(name.into(), self);
389    }
390
391    /// Appends one map to the other whilst merging individual scheme properties.
392    pub fn append_map(
393        old: BTreeMap<String, SecurityScheme>,
394        new: &mut BTreeMap<String, SecurityScheme>,
395    ) {
396        for (name, def) in old {
397            def.update_definitions(&name, new);
398        }
399    }
400}
401
402/// Tag object.
403///
404/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#tag-object>
405#[derive(Clone, Debug, Default, Serialize, Deserialize)]
406pub struct Tag {
407    pub name: String,
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub description: Option<String>,
410    #[serde(skip_serializing_if = "Option::is_none")]
411    #[serde(rename = "externalDocs")]
412    pub external_docs: Option<ExternalDocs>,
413}
414
415/// External Documentation object.
416///
417/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#external-documentation-object>
418#[derive(Clone, Debug, Default, Serialize, Deserialize)]
419pub struct ExternalDocs {
420    #[serde(skip_serializing_if = "Option::is_none")]
421    pub description: Option<String>,
422    pub url: String,
423}
424
425/// Path item that can be traversed and resolved for codegen.
426pub type ResolvablePathItem<S> = PathItem<ResolvableParameter<S>, ResolvableResponse<S>>;
427
428/// Path item with default parameter and response.
429pub type DefaultPathItemRaw = PathItem<DefaultParameterRaw, DefaultResponseRaw>;
430
431/// Path item object.
432///
433/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#pathItemObject>
434#[derive(Clone, Debug, Default, Serialize, Deserialize)]
435pub struct PathItem<P, R> {
436    #[serde(flatten, default = "BTreeMap::default")]
437    pub methods: BTreeMap<HttpMethod, Operation<P, R>>,
438    #[serde(default = "Vec::default", skip_serializing_if = "Vec::is_empty")]
439    pub parameters: Vec<Either<Reference, P>>,
440}
441
442impl<S> PathItem<Parameter<S>, Response<S>> {
443    /// Normalizes this operation map.
444    /// - Collects and removes parameters shared across operations
445    ///   and adds them to the list global to this map.
446    pub fn normalize(&mut self) {
447        // We're using `Option<BTreeSet>` over `BTreeSet` because we need to
448        // differentiate between the first operation that we use for initial
449        // value of the set and  an operation that doesn't have any parameters.
450        let mut shared_params = None;
451        for op in self.methods.values() {
452            let params = op
453                .parameters
454                .iter()
455                .map(|p| p.name.clone())
456                .collect::<BTreeSet<_>>();
457            if let Some(p) = shared_params.take() {
458                shared_params = Some(&p & &params); // set intersection
459            } else {
460                shared_params = Some(params);
461            }
462        }
463
464        let shared_params = match shared_params {
465            Some(p) => p,
466            None => return,
467        };
468
469        // FIXME: A parameter defined at path level could be overridden at
470        // the operation level with a different type. We shouldn't remove such
471        // path-level parameters.
472        for name in &shared_params {
473            for op in self.methods.values_mut() {
474                let idx = op
475                    .parameters
476                    .iter()
477                    .position(|p| p.name == name.as_str())
478                    .expect("collected parameter missing?");
479                let p = op.parameters.swap_remove(idx);
480                if !self.parameters.iter().any(|p| p.name == name.as_str()) {
481                    self.parameters.push(p);
482                }
483            }
484        }
485    }
486}
487
488/// Parameter that can be traversed and resolved for codegen.
489pub type ResolvableParameter<S> = Arc<RwLock<Parameter<Resolvable<S>>>>;
490
491/// Parameter with the default raw schema.
492pub type DefaultParameterRaw = Parameter<DefaultSchemaRaw>;
493
494/// Request parameter object.
495///
496/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#parameterObject>
497#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
498#[serde(rename_all = "camelCase")]
499pub struct Parameter<S> {
500    #[serde(skip_serializing_if = "Option::is_none")]
501    pub description: Option<String>,
502    #[serde(rename = "in")]
503    pub in_: ParameterIn,
504    pub name: String,
505    #[serde(default, skip_serializing_if = "is_false")]
506    pub required: bool,
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub schema: Option<S>,
509    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
510    pub data_type: Option<DataType>,
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub format: Option<DataTypeFormat>,
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub items: Option<Items>,
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub collection_format: Option<CollectionFormat>,
517    #[serde(default, skip_serializing_if = "is_false")]
518    pub allow_empty_value: bool,
519    #[serde(skip_serializing_if = "Option::is_none")]
520    pub default: Option<serde_json::Value>,
521    #[serde(skip_serializing_if = "Option::is_none")]
522    pub maximum: Option<f32>,
523    #[serde(rename = "exclusiveMaximum", skip_serializing_if = "Option::is_none")]
524    pub exclusive_maximum: Option<bool>,
525    #[serde(skip_serializing_if = "Option::is_none")]
526    pub minimum: Option<f32>,
527    #[serde(rename = "exclusiveMinimum", skip_serializing_if = "Option::is_none")]
528    pub exclusive_minimum: Option<bool>,
529    #[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
530    pub max_length: Option<u32>,
531    #[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
532    pub min_length: Option<u32>,
533    #[serde(skip_serializing_if = "Option::is_none")]
534    pub pattern: Option<String>,
535    #[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
536    pub max_items: Option<u32>,
537    #[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
538    pub min_items: Option<u32>,
539    #[serde(default, rename = "uniqueItems", skip_serializing_if = "is_false")]
540    pub unique_items: bool,
541    #[serde(rename = "multipleOf", skip_serializing_if = "Option::is_none")]
542    pub multiple_of: Option<f32>,
543    #[serde(default, rename = "enum", skip_serializing_if = "Vec::is_empty")]
544    pub enum_: Vec<serde_json::Value>,
545}
546
547/// Items object.
548///
549/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#itemsObject>
550#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
551#[serde(rename_all = "camelCase")]
552pub struct Items {
553    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
554    pub data_type: Option<DataType>,
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub format: Option<DataTypeFormat>,
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub items: Option<Box<Items>>,
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub collection_format: Option<CollectionFormat>,
561    #[serde(default, rename = "enum", skip_serializing_if = "Vec::is_empty")]
562    pub enum_: Vec<serde_json::Value>,
563    #[serde(skip_serializing_if = "Option::is_none")]
564    pub maximum: Option<f32>,
565    #[serde(rename = "exclusiveMaximum", skip_serializing_if = "Option::is_none")]
566    pub exclusive_maximum: Option<bool>,
567    #[serde(skip_serializing_if = "Option::is_none")]
568    pub minimum: Option<f32>,
569    #[serde(rename = "exclusiveMinimum", skip_serializing_if = "Option::is_none")]
570    pub exclusive_minimum: Option<bool>,
571    #[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
572    pub max_length: Option<u32>,
573    #[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
574    pub min_length: Option<u32>,
575    #[serde(skip_serializing_if = "Option::is_none")]
576    pub pattern: Option<String>,
577    #[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
578    pub max_items: Option<u32>,
579    #[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
580    pub min_items: Option<u32>,
581    #[serde(rename = "uniqueItems", skip_serializing_if = "Option::is_none")]
582    pub unique_items: Option<bool>,
583    #[serde(rename = "multipleOf", skip_serializing_if = "Option::is_none")]
584    pub multiple_of: Option<f32>,
585}
586
587impl<S> Parameter<Resolvable<S>>
588where
589    S: Schema,
590{
591    /// Checks the validity of this parameter using the relative URL
592    /// path it's associated with.
593    pub fn check(&self, path: &str) -> Result<(), ValidationError> {
594        if self.in_ == ParameterIn::Body {
595            // Body parameter must specify a schema.
596            if self.schema.is_none() {
597                return Err(ValidationError::MissingSchemaForBodyParameter(
598                    self.name.clone(),
599                    path.into(),
600                ));
601            }
602
603            return Ok(());
604        } else if self.in_ == ParameterIn::Header {
605            // Some headers aren't allowed.
606            let lower = self.name.to_lowercase();
607            if SPECIAL_HEADERS.iter().any(|&h| lower == h) {
608                return Err(ValidationError::InvalidHeader(
609                    self.name.clone(),
610                    path.into(),
611                ));
612            }
613        }
614
615        // Non-body parameters must be primitives or an array - they can't have objects.
616        let mut is_invalid = false;
617        match self.data_type {
618            Some(dt) if dt.is_primitive() => (),
619            Some(DataType::Array) => {
620                let mut inner = self.items.as_ref();
621                loop {
622                    let dt = inner.as_ref().and_then(|s| s.data_type);
623                    match dt {
624                        Some(ty) if ty.is_primitive() => break,
625                        Some(DataType::Array) => {
626                            inner = inner.as_ref().and_then(|s| s.items.as_deref());
627                        }
628                        None => {
629                            return Err(ValidationError::InvalidParameterType(
630                                self.name.clone(),
631                                path.into(),
632                                dt,
633                                self.in_,
634                            ));
635                        }
636                        _ => {
637                            is_invalid = true;
638                            break;
639                        }
640                    }
641                }
642            }
643            // If "file" is specified, then it must be `formData` parameter.
644            Some(DataType::File) => {
645                // FIXME: Check against `consumes` and `produces` fields.
646                if self.in_ != ParameterIn::FormData {
647                    is_invalid = true;
648                }
649            }
650            _ => is_invalid = true,
651        }
652
653        if is_invalid {
654            return Err(ValidationError::InvalidParameterType(
655                self.name.clone(),
656                path.into(),
657                self.data_type,
658                self.in_,
659            ));
660        }
661
662        Ok(())
663    }
664}
665
666/// The location of the parameter.
667#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
668#[serde(rename_all = "camelCase")]
669pub enum ParameterIn {
670    Query,
671    Header,
672    Path,
673    FormData,
674    Body,
675}
676
677/// Possible formats for array values in parameter.
678#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
679#[serde(rename_all = "lowercase")]
680pub enum CollectionFormat {
681    Csv,
682    Ssv,
683    Tsv,
684    Pipes,
685    Multi,
686}
687
688/// Operation that can be traversed and resolved for codegen.
689pub type ResolvableOperation<S> = Operation<ResolvableParameter<S>, ResolvableResponse<S>>;
690
691/// Operation with default raw parameter and response.
692pub type DefaultOperationRaw = Operation<DefaultParameterRaw, DefaultResponseRaw>;
693
694/// Operation object.
695///
696/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operationObject>
697#[derive(Clone, Debug, Default, Serialize, Deserialize)]
698#[serde(rename_all = "camelCase")]
699pub struct Operation<P, R> {
700    #[serde(skip_serializing_if = "Option::is_none")]
701    pub operation_id: Option<String>,
702    #[serde(skip_serializing_if = "Option::is_none")]
703    pub summary: Option<String>,
704    #[serde(skip_serializing_if = "Option::is_none")]
705    pub description: Option<String>,
706    // *NOTE:* `consumes` and `produces` are optional, because
707    // local media ranges can be used to override global media ranges
708    // (including setting it to empty), so we cannot go for an empty set.
709    #[serde(default, skip_serializing_if = "Option::is_none")]
710    pub consumes: Option<BTreeSet<MediaRange>>,
711    #[serde(default, skip_serializing_if = "Option::is_none")]
712    pub produces: Option<BTreeSet<MediaRange>>,
713    #[serde(default, skip_serializing_if = "Vec::is_empty")]
714    pub security: Vec<BTreeMap<String, Vec<String>>>,
715    #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
716    pub schemes: BTreeSet<OperationProtocol>,
717    // FIXME: Validate using `http::status::StatusCode::from_u16`
718    pub responses: BTreeMap<String, Either<Reference, R>>,
719    #[serde(default = "Vec::default", skip_serializing_if = "Vec::is_empty")]
720    pub parameters: Vec<Either<Reference, P>>,
721    #[serde(default, skip_serializing_if = "is_false")]
722    pub deprecated: bool,
723    #[serde(default, skip_serializing_if = "Vec::is_empty")]
724    pub tags: Vec<String>,
725}
726
727impl<S> Operation<Parameter<S>, Response<S>> {
728    /// Overwrites the names of parameters in this operation using the
729    /// given path template.
730    pub fn set_parameter_names_from_path_template(&mut self, path: &str) {
731        let mut names = vec![];
732        Api::<(), (), ()>::path_parameters_map(path, |name| {
733            if self
734                .parameters
735                .iter()
736                .filter(|p| p.in_ == ParameterIn::Path)
737                .all(|p| p.name != name)
738            {
739                names.push(name.to_owned());
740            }
741
742            ":".into()
743        });
744
745        for p in self
746            .parameters
747            .iter_mut()
748            .filter(|p| p.in_ == ParameterIn::Path)
749            .filter(|p| p.name.is_empty())
750            .rev()
751        {
752            if let Some(n) = names.pop() {
753                if let Some((name, pattern)) = n.split_once(':') {
754                    p.name = name.to_string();
755                    p.pattern = Some(pattern.to_string());
756                } else {
757                    p.name = n;
758                }
759            } else {
760                break;
761            }
762        }
763    }
764}
765
766/// Reference object.
767///
768/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#referenceObject>
769#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
770pub struct Reference {
771    #[serde(rename = "$ref")]
772    pub reference: String,
773}
774
775/// The protocol used for an operation.
776#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
777#[serde(rename_all = "lowercase")]
778pub enum OperationProtocol {
779    Http,
780    Https,
781    Ws,
782    Wss,
783}
784
785/// Response that can be traversed and resolved for codegen.
786pub type ResolvableResponse<S> = Arc<RwLock<Response<Resolvable<S>>>>;
787
788/// Response with the default raw schema.
789pub type DefaultResponseRaw = Response<DefaultSchemaRaw>;
790
791/// Response object.
792///
793/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#responseObject>
794#[derive(Clone, Debug, Default, Serialize, Deserialize)]
795pub struct Response<S> {
796    #[serde(skip_serializing_if = "Option::is_none")]
797    pub description: Option<String>,
798    #[serde(skip_serializing_if = "Option::is_none")]
799    pub schema: Option<S>,
800    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
801    pub headers: BTreeMap<String, Header>,
802}
803
804/// Header object.
805///
806/// <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#headerObject>
807#[derive(Clone, Debug, Default, Serialize, Deserialize)]
808pub struct Header {
809    #[serde(skip_serializing_if = "Option::is_none")]
810    pub description: Option<String>,
811    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
812    pub data_type: Option<DataType>,
813    #[serde(skip_serializing_if = "Option::is_none")]
814    pub format: Option<DataTypeFormat>,
815    #[serde(skip_serializing_if = "Option::is_none")]
816    pub items: Option<Items>,
817    #[serde(skip_serializing_if = "Option::is_none")]
818    pub collection_format: Option<CollectionFormat>,
819    #[serde(skip_serializing_if = "Option::is_none")]
820    pub default: Option<serde_json::Value>,
821    #[serde(default, rename = "enum", skip_serializing_if = "Vec::is_empty")]
822    pub enum_: Vec<serde_json::Value>,
823    #[serde(skip_serializing_if = "Option::is_none")]
824    pub maximum: Option<f32>,
825    #[serde(rename = "exclusiveMaximum", skip_serializing_if = "Option::is_none")]
826    pub exclusive_maximum: Option<bool>,
827    #[serde(skip_serializing_if = "Option::is_none")]
828    pub minimum: Option<f32>,
829    #[serde(rename = "exclusiveMinimum", skip_serializing_if = "Option::is_none")]
830    pub exclusive_minimum: Option<bool>,
831    #[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
832    pub max_length: Option<u32>,
833    #[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
834    pub min_length: Option<u32>,
835    #[serde(skip_serializing_if = "Option::is_none")]
836    pub pattern: Option<String>,
837    #[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
838    pub max_items: Option<u32>,
839    #[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
840    pub min_items: Option<u32>,
841    #[serde(rename = "uniqueItems", skip_serializing_if = "Option::is_none")]
842    pub unique_items: Option<bool>,
843    #[serde(rename = "multipleOf", skip_serializing_if = "Option::is_none")]
844    pub multiple_of: Option<f32>,
845}
846
847/// The HTTP method used for an operation.
848#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
849#[serde(rename_all = "lowercase")]
850pub enum HttpMethod {
851    Get,
852    Put,
853    Post,
854    Delete,
855    Options,
856    Head,
857    Patch,
858}
859
860impl HttpMethod {
861    /// Whether this method allows body in requests.
862    pub fn allows_body(self) -> bool {
863        std::matches!(self, HttpMethod::Post | HttpMethod::Put | HttpMethod::Patch)
864    }
865}
866
867/* Helpers */
868
869/// `Either` from "either" crate. We can't use that crate because
870/// we don't want the enum to be tagged during de/serialization.
871#[derive(Debug, Clone, Serialize, Deserialize)]
872#[serde(untagged)]
873pub enum Either<L, R> {
874    Left(L),
875    Right(R),
876}
877
878impl<L, R> Either<L, R> {
879    /// Get a readable reference to the right variant (if it exists).
880    pub fn right(&self) -> Option<&R> {
881        match self {
882            Either::Left(_) => None,
883            Either::Right(r) => Some(r),
884        }
885    }
886
887    /// Get a mutable reference to the right variant (if it exists).
888    pub fn right_mut(&mut self) -> Option<&mut R> {
889        match self {
890            Either::Left(_) => None,
891            Either::Right(r) => Some(r),
892        }
893    }
894
895    /// Get a readable reference to the left variant (if it exists).
896    pub fn left(&self) -> Option<&L> {
897        match self {
898            Either::Left(l) => Some(l),
899            Either::Right(_) => None,
900        }
901    }
902
903    /// Get a mutable reference to the left variant (if it exists).
904    pub fn left_mut(&mut self) -> Option<&mut L> {
905        match self {
906            Either::Left(l) => Some(l),
907            Either::Right(_) => None,
908        }
909    }
910}
911
912/// Wrapper for schema. This uses `Arc<RwLock<S>>` for interior
913/// mutability and differentiates raw schema from resolved schema
914/// (i.e., the one where `$ref` references point to the actual schema).
915#[derive(Debug, Deserialize)]
916#[serde(untagged)]
917pub enum Resolvable<S> {
918    Raw(Arc<RwLock<S>>),
919    #[serde(skip)]
920    Resolved {
921        new: Arc<RwLock<S>>,
922        old: Arc<RwLock<S>>,
923    },
924}
925
926impl<S> Resolvable<S>
927where
928    S: Schema,
929{
930    /// Fetch the description for this schema.
931    pub fn get_description(&self) -> Option<String> {
932        match *self {
933            Resolvable::Raw(ref s) => s.read().unwrap().description().map(String::from),
934            // We don't want parameters/fields to describe the actual referenced object.
935            Resolvable::Resolved { ref old, .. } => {
936                old.read().unwrap().description().map(String::from)
937            }
938        }
939    }
940}
941
942/* Common trait impls */
943
944impl Default for SpecFormat {
945    fn default() -> Self {
946        SpecFormat::Json
947    }
948}
949
950#[cfg(feature = "actix-base")]
951impl From<&Method> for HttpMethod {
952    fn from(method: &Method) -> HttpMethod {
953        match method.as_str() {
954            "PUT" => HttpMethod::Put,
955            "POST" => HttpMethod::Post,
956            "DELETE" => HttpMethod::Delete,
957            "OPTIONS" => HttpMethod::Options,
958            "HEAD" => HttpMethod::Head,
959            "PATCH" => HttpMethod::Patch,
960            _ => HttpMethod::Get,
961        }
962    }
963}
964
965impl<T> Deref for Either<Reference, T> {
966    type Target = T;
967
968    fn deref(&self) -> &Self::Target {
969        match *self {
970            Either::Left(_) => panic!("unable to deref because reference is not resolved."),
971            Either::Right(ref r) => r,
972        }
973    }
974}
975
976impl<T> DerefMut for Either<Reference, T> {
977    fn deref_mut(&mut self) -> &mut Self::Target {
978        match *self {
979            Either::Left(_) => panic!("unable to deref because reference is not resolved."),
980            Either::Right(ref mut r) => r,
981        }
982    }
983}
984
985impl<S> Deref for Resolvable<S> {
986    type Target = Arc<RwLock<S>>;
987
988    fn deref(&self) -> &Self::Target {
989        match *self {
990            Resolvable::Raw(ref s) => s,
991            Resolvable::Resolved { ref new, .. } => new,
992        }
993    }
994}
995
996impl<S> DerefMut for Resolvable<S> {
997    fn deref_mut(&mut self) -> &mut Self::Target {
998        match *self {
999            Resolvable::Raw(ref mut s) => s,
1000            Resolvable::Resolved { ref mut new, .. } => new,
1001        }
1002    }
1003}
1004
1005impl<S: Default> Default for Resolvable<S> {
1006    fn default() -> Self {
1007        Resolvable::from(S::default())
1008    }
1009}
1010
1011impl<S> From<S> for Resolvable<S> {
1012    fn from(t: S) -> Self {
1013        Resolvable::Raw(Arc::new(RwLock::new(t)))
1014    }
1015}
1016
1017impl<S> Clone for Resolvable<S> {
1018    fn clone(&self) -> Self {
1019        match *self {
1020            Resolvable::Raw(ref s) => Resolvable::Raw(s.clone()),
1021            Resolvable::Resolved { ref new, ref old } => Resolvable::Resolved {
1022                new: new.clone(),
1023                old: old.clone(),
1024            },
1025        }
1026    }
1027}
1028
1029impl Display for HttpMethod {
1030    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1031        write!(f, "{:?}", self)
1032    }
1033}
1034
1035impl Default for Version {
1036    fn default() -> Self {
1037        Version::V2
1038    }
1039}
1040
1041impl Default for CollectionFormat {
1042    fn default() -> Self {
1043        CollectionFormat::Csv
1044    }
1045}
1046
1047/// **NOTE:** This is just a stub. This is usually set explicitly.
1048impl Default for ParameterIn {
1049    fn default() -> Self {
1050        ParameterIn::Body
1051    }
1052}
1053
1054/* Serde helpers */
1055
1056#[allow(clippy::trivially_copy_pass_by_ref)]
1057fn is_false(val: &bool) -> bool {
1058    !*val
1059}