pub use super::extensions::{
Coder, Coders, MediaRange, JSON_CODER, JSON_MIME, YAML_CODER, YAML_MIME,
};
use super::schema::Schema;
use crate::error::ValidationError;
use once_cell::sync::Lazy;
use paperclip_macros::api_v2_schema_struct;
use regex::{Captures, Regex};
use serde::ser::{SerializeMap, Serializer};
#[cfg(feature = "actix-base")]
use actix_web::http::Method;
use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet},
fmt::{self, Display},
ops::{Deref, DerefMut},
sync::{Arc, RwLock},
};
static PATH_TEMPLATE_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"\{(.*?)\}").expect("path template regex"));
const SPECIAL_HEADERS: &[&str] = &["content-type", "accept", "authorization"];
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub enum Version {
#[serde(rename = "2.0")]
V2,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum DataType {
Integer,
Number,
String,
Boolean,
Array,
Object,
File,
}
impl DataType {
#[inline]
pub fn is_primitive(self) -> bool {
std::matches!(
self,
DataType::Integer | DataType::Number | DataType::String | DataType::Boolean
)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum DataTypeFormat {
Int32,
Int64,
Float,
Double,
Byte,
Binary,
Date,
#[serde(rename = "date-time")]
DateTime,
Password,
Url,
Uuid,
Ip,
IpV4,
IpV6,
#[serde(other)]
Other,
}
#[allow(clippy::to_string_trait_impl)]
impl ToString for DataTypeFormat {
fn to_string(&self) -> String {
match self {
DataTypeFormat::Int32 => "int32",
DataTypeFormat::Int64 => "int64",
DataTypeFormat::Float => "float",
DataTypeFormat::Double => "double",
DataTypeFormat::Byte => "byte",
DataTypeFormat::Binary => "binary",
DataTypeFormat::Date => "date",
DataTypeFormat::DateTime => "datetime",
DataTypeFormat::Password => "password",
DataTypeFormat::Url => "url",
DataTypeFormat::Uuid => "uuid",
DataTypeFormat::Ip => "ip",
DataTypeFormat::IpV4 => "ipv4",
DataTypeFormat::IpV6 => "ipv6",
DataTypeFormat::Other => "other",
}
.to_string()
}
}
impl From<DataTypeFormat> for DataType {
fn from(src: DataTypeFormat) -> Self {
match src {
DataTypeFormat::Int32 => Self::Integer,
DataTypeFormat::Int64 => Self::Integer,
DataTypeFormat::Float => Self::Number,
DataTypeFormat::Double => Self::Number,
DataTypeFormat::Byte => Self::String,
DataTypeFormat::Binary => Self::String,
DataTypeFormat::Date => Self::String,
DataTypeFormat::DateTime => Self::String,
DataTypeFormat::Password => Self::String,
DataTypeFormat::Url => Self::String,
DataTypeFormat::Uuid => Self::String,
DataTypeFormat::Ip => Self::String,
DataTypeFormat::IpV4 => Self::String,
DataTypeFormat::IpV6 => Self::String,
DataTypeFormat::Other => Self::Object,
}
}
}
pub type ResolvableApi<S> = Api<ResolvableParameter<S>, ResolvableResponse<S>, Resolvable<S>>;
pub type DefaultApiRaw = Api<DefaultParameterRaw, DefaultResponseRaw, DefaultSchemaRaw>;
fn strip_templates_from_paths<P: serde::ser::Serialize, R: serde::ser::Serialize, S: Serializer>(
tree: &BTreeMap<String, PathItem<P, R>>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let len = tree.len();
let mut map = serializer.serialize_map(Some(len))?;
for (k, v) in tree {
let path = strip_pattern_from_template(k);
map.serialize_entry(&path, v)?;
}
map.end()
}
fn strip_pattern_from_template(path: &str) -> String {
let mut clean_path = path.to_string();
for cap in PATH_TEMPLATE_REGEX.captures_iter(path) {
let name_only = cap[1]
.split_once(':')
.map(|t| t.0.to_string())
.unwrap_or_else(|| cap[1].to_string());
if cap[1] != name_only {
clean_path = clean_path.replace(
format!("{{{}}}", &cap[1]).as_str(),
format!("{{{}}}", name_only).as_str(),
);
}
}
clean_path
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Api<P, R, S> {
pub swagger: Version,
#[serde(default = "BTreeMap::new")]
pub definitions: BTreeMap<String, S>,
#[serde(serialize_with = "strip_templates_from_paths")]
pub paths: BTreeMap<String, PathItem<P, R>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub host: Option<String>,
#[serde(rename = "basePath", skip_serializing_if = "Option::is_none")]
pub base_path: Option<String>,
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
pub consumes: BTreeSet<MediaRange>,
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
pub produces: BTreeSet<MediaRange>,
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
pub schemes: BTreeSet<OperationProtocol>,
#[serde(default = "BTreeMap::new", skip_serializing_if = "BTreeMap::is_empty")]
pub parameters: BTreeMap<String, P>,
#[serde(default = "BTreeMap::new", skip_serializing_if = "BTreeMap::is_empty")]
pub responses: BTreeMap<String, R>,
#[serde(
default,
rename = "securityDefinitions",
skip_serializing_if = "BTreeMap::is_empty"
)]
pub security_definitions: BTreeMap<String, SecurityScheme>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub security: Vec<BTreeMap<String, BTreeSet<String>>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<Tag>,
#[serde(rename = "externalDocs", skip_serializing_if = "Option::is_none")]
pub external_docs: Option<ExternalDocs>,
#[serde(
default,
rename = "x-rust-coders",
skip_serializing_if = "<Coders as Deref>::Target::is_empty"
)]
pub coders: Coders,
#[serde(
default,
rename = "x-rust-dependencies",
skip_serializing_if = "BTreeMap::is_empty"
)]
pub support_crates: BTreeMap<String, String>,
#[serde(skip)]
pub spec_format: SpecFormat,
pub info: Info,
#[serde(
flatten,
skip_serializing_if = "BTreeMap::is_empty",
deserialize_with = "crate::v2::extensions::deserialize_extensions"
)]
pub extensions: BTreeMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpecFormat {
Json,
Yaml,
}
impl SpecFormat {
pub fn coder(self) -> Arc<Coder> {
match self {
SpecFormat::Json => JSON_CODER.clone(),
SpecFormat::Yaml => YAML_CODER.clone(),
}
}
pub fn mime(self) -> &'static MediaRange {
match self {
SpecFormat::Json => &JSON_MIME,
SpecFormat::Yaml => &YAML_MIME,
}
}
}
impl<P, R, S> Api<P, R, S> {
pub fn path_parameters_map(
path: &str,
mut f: impl FnMut(&str) -> Cow<'static, str>,
) -> Cow<'_, str> {
PATH_TEMPLATE_REGEX.replace_all(path, |c: &Captures| f(&c[1]))
}
}
use crate as paperclip; #[api_v2_schema_struct]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DefaultSchema;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Info {
pub version: String,
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "termsOfService", skip_serializing_if = "Option::is_none")]
pub terms_of_service: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub contact: Option<Contact>,
#[serde(skip_serializing_if = "Option::is_none")]
pub license: Option<License>,
#[serde(
flatten,
skip_serializing_if = "BTreeMap::is_empty",
deserialize_with = "crate::v2::extensions::deserialize_extensions"
)]
pub extensions: BTreeMap<String, serde_json::Value>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Contact {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct License {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SecurityScheme {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "type")]
pub type_: String,
#[serde(rename = "in", skip_serializing_if = "Option::is_none")]
pub in_: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub flow: Option<String>,
#[serde(rename = "authorizationUrl", skip_serializing_if = "Option::is_none")]
pub auth_url: Option<String>,
#[serde(rename = "tokenUrl", skip_serializing_if = "Option::is_none")]
pub token_url: Option<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub scopes: BTreeMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl SecurityScheme {
pub fn update_definitions(mut self, name: &str, map: &mut BTreeMap<String, SecurityScheme>) {
if let Some(existing) = map.get_mut(name) {
existing.name = existing.name.take().or(self.name);
if !self.type_.is_empty() {
existing.type_ = self.type_;
}
existing.in_ = existing.in_.take().or(self.in_);
existing.flow = existing.flow.take().or(self.flow);
existing.auth_url = existing.auth_url.take().or(self.auth_url);
existing.token_url = existing.token_url.take().or(self.token_url);
existing.scopes.append(&mut self.scopes);
existing.description = existing.description.take().or(self.description);
return;
}
map.insert(name.into(), self);
}
pub fn append_map(
old: BTreeMap<String, SecurityScheme>,
new: &mut BTreeMap<String, SecurityScheme>,
) {
for (name, def) in old {
def.update_definitions(&name, new);
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Tag {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "externalDocs")]
pub external_docs: Option<ExternalDocs>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ExternalDocs {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub url: String,
}
pub type ResolvablePathItem<S> = PathItem<ResolvableParameter<S>, ResolvableResponse<S>>;
pub type DefaultPathItemRaw = PathItem<DefaultParameterRaw, DefaultResponseRaw>;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct PathItem<P, R> {
#[serde(flatten, default = "BTreeMap::default")]
pub methods: BTreeMap<HttpMethod, Operation<P, R>>,
#[serde(default = "Vec::default", skip_serializing_if = "Vec::is_empty")]
pub parameters: Vec<Either<Reference, P>>,
}
impl<S> PathItem<Parameter<S>, Response<S>> {
pub fn normalize(&mut self) {
let mut shared_params = None;
for op in self.methods.values() {
let params = op
.parameters
.iter()
.map(|p| p.name.clone())
.collect::<BTreeSet<_>>();
if let Some(p) = shared_params.take() {
shared_params = Some(&p & ¶ms); } else {
shared_params = Some(params);
}
}
let shared_params = match shared_params {
Some(p) => p,
None => return,
};
for name in &shared_params {
for op in self.methods.values_mut() {
let idx = op
.parameters
.iter()
.position(|p| p.name == name.as_str())
.expect("collected parameter missing?");
let p = op.parameters.swap_remove(idx);
if !self.parameters.iter().any(|p| p.name == name.as_str()) {
self.parameters.push(p);
}
}
}
}
}
pub type ResolvableParameter<S> = Arc<RwLock<Parameter<Resolvable<S>>>>;
pub type DefaultParameterRaw = Parameter<DefaultSchemaRaw>;
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Parameter<S> {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "in")]
pub in_: ParameterIn,
pub name: String,
#[serde(default, skip_serializing_if = "is_false")]
pub required: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub schema: Option<S>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub data_type: Option<DataType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<DataTypeFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<Items>,
#[serde(skip_serializing_if = "Option::is_none")]
pub collection_format: Option<CollectionFormat>,
#[serde(default, skip_serializing_if = "is_false")]
pub allow_empty_value: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub maximum: Option<f32>,
#[serde(rename = "exclusiveMaximum", skip_serializing_if = "Option::is_none")]
pub exclusive_maximum: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum: Option<f32>,
#[serde(rename = "exclusiveMinimum", skip_serializing_if = "Option::is_none")]
pub exclusive_minimum: Option<bool>,
#[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
pub max_length: Option<u32>,
#[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
pub min_length: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
#[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
pub max_items: Option<u32>,
#[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
pub min_items: Option<u32>,
#[serde(default, rename = "uniqueItems", skip_serializing_if = "is_false")]
pub unique_items: bool,
#[serde(rename = "multipleOf", skip_serializing_if = "Option::is_none")]
pub multiple_of: Option<f32>,
#[serde(default, rename = "enum", skip_serializing_if = "Vec::is_empty")]
pub enum_: Vec<serde_json::Value>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Items {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub data_type: Option<DataType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<DataTypeFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<Box<Items>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub collection_format: Option<CollectionFormat>,
#[serde(default, rename = "enum", skip_serializing_if = "Vec::is_empty")]
pub enum_: Vec<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub maximum: Option<f32>,
#[serde(rename = "exclusiveMaximum", skip_serializing_if = "Option::is_none")]
pub exclusive_maximum: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum: Option<f32>,
#[serde(rename = "exclusiveMinimum", skip_serializing_if = "Option::is_none")]
pub exclusive_minimum: Option<bool>,
#[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
pub max_length: Option<u32>,
#[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
pub min_length: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
#[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
pub max_items: Option<u32>,
#[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
pub min_items: Option<u32>,
#[serde(rename = "uniqueItems", skip_serializing_if = "Option::is_none")]
pub unique_items: Option<bool>,
#[serde(rename = "multipleOf", skip_serializing_if = "Option::is_none")]
pub multiple_of: Option<f32>,
}
impl<S> Parameter<Resolvable<S>>
where
S: Schema,
{
pub fn check(&self, path: &str) -> Result<(), ValidationError> {
if self.in_ == ParameterIn::Body {
if self.schema.is_none() {
return Err(ValidationError::MissingSchemaForBodyParameter(
self.name.clone(),
path.into(),
));
}
return Ok(());
} else if self.in_ == ParameterIn::Header {
let lower = self.name.to_lowercase();
if SPECIAL_HEADERS.iter().any(|&h| lower == h) {
return Err(ValidationError::InvalidHeader(
self.name.clone(),
path.into(),
));
}
}
let mut is_invalid = false;
match self.data_type {
Some(dt) if dt.is_primitive() => (),
Some(DataType::Array) => {
let mut inner = self.items.as_ref();
loop {
let dt = inner.as_ref().and_then(|s| s.data_type);
match dt {
Some(ty) if ty.is_primitive() => break,
Some(DataType::Array) => {
inner = inner.as_ref().and_then(|s| s.items.as_deref());
}
None => {
return Err(ValidationError::InvalidParameterType(
self.name.clone(),
path.into(),
dt,
self.in_,
));
}
_ => {
is_invalid = true;
break;
}
}
}
}
Some(DataType::File) => {
if self.in_ != ParameterIn::FormData {
is_invalid = true;
}
}
_ => is_invalid = true,
}
if is_invalid {
return Err(ValidationError::InvalidParameterType(
self.name.clone(),
path.into(),
self.data_type,
self.in_,
));
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
pub enum ParameterIn {
Query,
Header,
Path,
FormData,
Body,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "lowercase")]
pub enum CollectionFormat {
Csv,
Ssv,
Tsv,
Pipes,
Multi,
}
pub type ResolvableOperation<S> = Operation<ResolvableParameter<S>, ResolvableResponse<S>>;
pub type DefaultOperationRaw = Operation<DefaultParameterRaw, DefaultResponseRaw>;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Operation<P, R> {
#[serde(skip_serializing_if = "Option::is_none")]
pub operation_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub consumes: Option<BTreeSet<MediaRange>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub produces: Option<BTreeSet<MediaRange>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub security: Vec<BTreeMap<String, Vec<String>>>,
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
pub schemes: BTreeSet<OperationProtocol>,
pub responses: BTreeMap<String, Either<Reference, R>>,
#[serde(default = "Vec::default", skip_serializing_if = "Vec::is_empty")]
pub parameters: Vec<Either<Reference, P>>,
#[serde(default, skip_serializing_if = "is_false")]
pub deprecated: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
}
impl<S> Operation<Parameter<S>, Response<S>> {
pub fn set_parameter_names_from_path_template(&mut self, path: &str) {
let mut names = vec![];
Api::<(), (), ()>::path_parameters_map(path, |name| {
if self
.parameters
.iter()
.filter(|p| p.in_ == ParameterIn::Path)
.all(|p| p.name != name)
{
names.push(name.to_owned());
}
":".into()
});
for p in self
.parameters
.iter_mut()
.filter(|p| p.in_ == ParameterIn::Path)
.filter(|p| p.name.is_empty())
.rev()
{
if let Some(n) = names.pop() {
if let Some((name, pattern)) = n.split_once(':') {
p.name = name.to_string();
p.pattern = Some(pattern.to_string());
} else {
p.name = n;
}
} else {
break;
}
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
pub struct Reference {
#[serde(rename = "$ref")]
pub reference: String,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "lowercase")]
pub enum OperationProtocol {
Http,
Https,
Ws,
Wss,
}
pub type ResolvableResponse<S> = Arc<RwLock<Response<Resolvable<S>>>>;
pub type DefaultResponseRaw = Response<DefaultSchemaRaw>;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Response<S> {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schema: Option<S>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub headers: BTreeMap<String, Header>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Header {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub data_type: Option<DataType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<DataTypeFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<Items>,
#[serde(skip_serializing_if = "Option::is_none")]
pub collection_format: Option<CollectionFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<serde_json::Value>,
#[serde(default, rename = "enum", skip_serializing_if = "Vec::is_empty")]
pub enum_: Vec<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub maximum: Option<f32>,
#[serde(rename = "exclusiveMaximum", skip_serializing_if = "Option::is_none")]
pub exclusive_maximum: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum: Option<f32>,
#[serde(rename = "exclusiveMinimum", skip_serializing_if = "Option::is_none")]
pub exclusive_minimum: Option<bool>,
#[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
pub max_length: Option<u32>,
#[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
pub min_length: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
#[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
pub max_items: Option<u32>,
#[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
pub min_items: Option<u32>,
#[serde(rename = "uniqueItems", skip_serializing_if = "Option::is_none")]
pub unique_items: Option<bool>,
#[serde(rename = "multipleOf", skip_serializing_if = "Option::is_none")]
pub multiple_of: Option<f32>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "lowercase")]
pub enum HttpMethod {
Get,
Put,
Post,
Delete,
Options,
Head,
Patch,
}
impl HttpMethod {
pub fn allows_body(self) -> bool {
std::matches!(self, HttpMethod::Post | HttpMethod::Put | HttpMethod::Patch)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Either<L, R> {
Left(L),
Right(R),
}
impl<L, R> Either<L, R> {
pub fn right(&self) -> Option<&R> {
match self {
Either::Left(_) => None,
Either::Right(r) => Some(r),
}
}
pub fn right_mut(&mut self) -> Option<&mut R> {
match self {
Either::Left(_) => None,
Either::Right(r) => Some(r),
}
}
pub fn left(&self) -> Option<&L> {
match self {
Either::Left(l) => Some(l),
Either::Right(_) => None,
}
}
pub fn left_mut(&mut self) -> Option<&mut L> {
match self {
Either::Left(l) => Some(l),
Either::Right(_) => None,
}
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum Resolvable<S> {
Raw(Arc<RwLock<S>>),
#[serde(skip)]
Resolved {
new: Arc<RwLock<S>>,
old: Arc<RwLock<S>>,
},
}
impl<S> Resolvable<S>
where
S: Schema,
{
pub fn get_description(&self) -> Option<String> {
match *self {
Resolvable::Raw(ref s) => s.read().unwrap().description().map(String::from),
Resolvable::Resolved { ref old, .. } => {
old.read().unwrap().description().map(String::from)
}
}
}
}
impl Default for SpecFormat {
fn default() -> Self {
SpecFormat::Json
}
}
#[cfg(feature = "actix-base")]
impl From<&Method> for HttpMethod {
fn from(method: &Method) -> HttpMethod {
match method.as_str() {
"PUT" => HttpMethod::Put,
"POST" => HttpMethod::Post,
"DELETE" => HttpMethod::Delete,
"OPTIONS" => HttpMethod::Options,
"HEAD" => HttpMethod::Head,
"PATCH" => HttpMethod::Patch,
_ => HttpMethod::Get,
}
}
}
impl<T> Deref for Either<Reference, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match *self {
Either::Left(_) => panic!("unable to deref because reference is not resolved."),
Either::Right(ref r) => r,
}
}
}
impl<T> DerefMut for Either<Reference, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match *self {
Either::Left(_) => panic!("unable to deref because reference is not resolved."),
Either::Right(ref mut r) => r,
}
}
}
impl<S> Deref for Resolvable<S> {
type Target = Arc<RwLock<S>>;
fn deref(&self) -> &Self::Target {
match *self {
Resolvable::Raw(ref s) => s,
Resolvable::Resolved { ref new, .. } => new,
}
}
}
impl<S> DerefMut for Resolvable<S> {
fn deref_mut(&mut self) -> &mut Self::Target {
match *self {
Resolvable::Raw(ref mut s) => s,
Resolvable::Resolved { ref mut new, .. } => new,
}
}
}
impl<S: Default> Default for Resolvable<S> {
fn default() -> Self {
Resolvable::from(S::default())
}
}
impl<S> From<S> for Resolvable<S> {
fn from(t: S) -> Self {
Resolvable::Raw(Arc::new(RwLock::new(t)))
}
}
impl<S> Clone for Resolvable<S> {
fn clone(&self) -> Self {
match *self {
Resolvable::Raw(ref s) => Resolvable::Raw(s.clone()),
Resolvable::Resolved { ref new, ref old } => Resolvable::Resolved {
new: new.clone(),
old: old.clone(),
},
}
}
}
impl Display for HttpMethod {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl Default for Version {
fn default() -> Self {
Version::V2
}
}
impl Default for CollectionFormat {
fn default() -> Self {
CollectionFormat::Csv
}
}
impl Default for ParameterIn {
fn default() -> Self {
ParameterIn::Body
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_false(val: &bool) -> bool {
!*val
}