pub mod header;
pub mod style;
use std::fmt;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{
Map,
model::{
body::media_type::MediaType,
example::{
Example, Examples, ExamplesConflict, examples_from, examples_into, examples_with_named,
},
extensions::Extensions,
parameter::style::Style,
reference::{Ref, RefOr},
schema::Schema,
},
};
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ParameterIn {
#[default]
Query,
Header,
Path,
Cookie,
#[cfg(feature = "openapi32")]
Querystring,
}
impl ParameterIn {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Query => "query",
Self::Header => "header",
Self::Path => "path",
Self::Cookie => "cookie",
#[cfg(feature = "openapi32")]
Self::Querystring => "querystring",
}
}
}
impl fmt::Display for ParameterIn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "RawParameter", into = "RawParameter")]
pub struct Parameter {
pub name: String,
#[serde(rename = "in")]
pub location: ParameterIn,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
pub deprecated: Option<bool>,
pub allow_empty_value: Option<bool>,
shape: ParameterShape,
examples: Option<Examples>,
pub extensions: Extensions,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ParameterShape {
Schema {
schema: Schema,
style: Option<Style>,
explode: Option<bool>,
allow_reserved: Option<bool>,
},
Content {
media_type: String,
value: Box<MediaType>,
},
}
impl Parameter {
pub fn new(name: impl Into<String>, location: ParameterIn, schema: Schema) -> Self {
Self::shaped(
name,
location,
ParameterShape::Schema {
schema,
style: None,
explode: None,
allow_reserved: None,
},
)
}
pub fn with_content(
name: impl Into<String>,
location: ParameterIn,
media_type: impl Into<String>,
value: MediaType,
) -> Self {
Self::shaped(
name,
location,
ParameterShape::Content {
media_type: media_type.into(),
value: Box::new(value),
},
)
}
fn shaped(name: impl Into<String>, location: ParameterIn, shape: ParameterShape) -> Self {
Self {
name: name.into(),
location,
description: None,
required: (location == ParameterIn::Path).then_some(true),
deprecated: None,
allow_empty_value: None,
shape,
examples: None,
extensions: Extensions::default(),
}
}
pub fn path(name: impl Into<String>, schema: Schema) -> Self {
Self::new(name, ParameterIn::Path, schema)
}
pub fn query(name: impl Into<String>, schema: Schema) -> Self {
Self::new(name, ParameterIn::Query, schema)
}
pub fn header(name: impl Into<String>, schema: Schema) -> Self {
Self::new(name, ParameterIn::Header, schema)
}
pub fn cookie(name: impl Into<String>, schema: Schema) -> Self {
Self::new(name, ParameterIn::Cookie, schema)
}
#[must_use]
pub fn required(mut self, required: bool) -> Self {
self.required = Some(required);
self
}
#[must_use]
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
#[must_use]
pub fn with_style(mut self, style: Style, explode: bool) -> Self {
if let ParameterShape::Schema {
style: slot,
explode: exploded,
..
} = &mut self.shape
{
*slot = Some(style);
*exploded = Some(explode);
}
self
}
#[must_use]
pub fn with_example(mut self, value: impl Into<Value>) -> Self {
self.examples = Some(Examples::Inline(value.into()));
self
}
#[must_use]
pub fn with_named_example(mut self, name: impl Into<String>, example: Example) -> Self {
self.examples = Some(examples_with_named(
self.examples,
name.into(),
RefOr::Item(example),
));
self
}
#[must_use]
pub fn with_named_example_ref(mut self, name: impl Into<String>, example: Ref) -> Self {
self.examples = Some(examples_with_named(
self.examples,
name.into(),
RefOr::Ref(example),
));
self
}
#[must_use]
pub fn shape(&self) -> &ParameterShape {
&self.shape
}
pub fn shape_mut(&mut self) -> &mut ParameterShape {
&mut self.shape
}
#[must_use]
pub fn schema(&self) -> Option<&Schema> {
match &self.shape {
ParameterShape::Schema { schema, .. } => Some(schema),
ParameterShape::Content { .. } => None,
}
}
#[must_use]
pub fn content(&self) -> Option<(&str, &MediaType)> {
match &self.shape {
ParameterShape::Content { media_type, value } => Some((media_type, &**value)),
ParameterShape::Schema { .. } => None,
}
}
#[must_use]
pub fn style(&self) -> Option<Style> {
match self.shape {
ParameterShape::Schema { style, .. } => style,
ParameterShape::Content { .. } => None,
}
}
#[must_use]
pub fn allow_reserved(&self) -> Option<bool> {
match self.shape {
ParameterShape::Schema { allow_reserved, .. } => allow_reserved,
ParameterShape::Content { .. } => None,
}
}
#[must_use]
pub fn examples(&self) -> Option<&Examples> {
self.examples.as_ref()
}
#[must_use]
pub fn example(&self) -> Option<&Value> {
match &self.examples {
Some(Examples::Inline(value)) => Some(value),
Some(Examples::Named(_)) | None => None,
}
}
#[must_use]
pub fn named_examples(&self) -> Option<&Map<RefOr<Example>>> {
match &self.examples {
Some(Examples::Named(named)) => Some(named),
Some(Examples::Inline(_)) | None => None,
}
}
#[must_use]
pub fn effective_style(&self) -> Option<Style> {
match self.shape {
ParameterShape::Schema { style, .. } => {
Some(style.unwrap_or_else(|| Style::default_for(self.location)))
}
ParameterShape::Content { .. } => None,
}
}
#[must_use]
pub fn effective_explode(&self) -> Option<bool> {
match self.shape {
ParameterShape::Schema { explode, .. } => Some(
explode
.unwrap_or_else(|| self.effective_style().is_some_and(Style::default_explode)),
),
ParameterShape::Content { .. } => None,
}
}
}
#[derive(Serialize, Deserialize)]
struct RawParameter {
name: String,
#[serde(rename = "in")]
location: ParameterIn,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
required: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
deprecated: Option<bool>,
#[serde(
rename = "allowEmptyValue",
default,
skip_serializing_if = "Option::is_none"
)]
allow_empty_value: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
style: Option<Style>,
#[serde(default, skip_serializing_if = "Option::is_none")]
explode: Option<bool>,
#[serde(
rename = "allowReserved",
default,
skip_serializing_if = "Option::is_none"
)]
allow_reserved: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
schema: Option<Schema>,
#[serde(
default,
deserialize_with = "crate::model::nullable::some",
skip_serializing_if = "Option::is_none"
)]
example: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
examples: Option<Map<RefOr<Example>>>,
#[serde(default, skip_serializing_if = "Map::is_empty")]
content: Map<MediaType>,
#[serde(flatten)]
extensions: Extensions,
}
#[derive(Debug)]
pub(crate) enum ParameterConflict {
Shape(ShapeConflict),
Examples(ExamplesConflict),
}
impl fmt::Display for ParameterConflict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Shape(conflict) => conflict.fmt(f),
Self::Examples(conflict) => conflict.fmt(f),
}
}
}
impl From<ShapeConflict> for ParameterConflict {
fn from(conflict: ShapeConflict) -> Self {
Self::Shape(conflict)
}
}
impl From<ExamplesConflict> for ParameterConflict {
fn from(conflict: ExamplesConflict) -> Self {
Self::Examples(conflict)
}
}
#[derive(Debug)]
pub(crate) enum ShapeConflict {
Neither,
Both,
ContentNotSingular(usize),
}
impl fmt::Display for ShapeConflict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Neither => f.write_str("one of `schema` and `content` is required"),
Self::Both => f.write_str("`schema` and `content` are mutually exclusive"),
Self::ContentNotSingular(found) => {
write!(f, "`content` must hold exactly one entry, found {found}")
}
}
}
}
pub(crate) fn shape_from(
schema: Option<Schema>,
content: Map<MediaType>,
) -> Result<(Schema, Option<(String, MediaType)>), ShapeConflict> {
match (schema, content.len()) {
(Some(_), 1..) => Err(ShapeConflict::Both),
(None, 0) => Err(ShapeConflict::Neither),
(None, 1) => {
let (media_type, value) = content
.into_iter()
.next()
.expect("a map of length one has an entry");
Ok((Schema::Bool(true), Some((media_type, value))))
}
(None, found) => Err(ShapeConflict::ContentNotSingular(found)),
(Some(schema), 0) => Ok((schema, None)),
}
}
impl TryFrom<RawParameter> for Parameter {
type Error = ParameterConflict;
fn try_from(raw: RawParameter) -> Result<Self, Self::Error> {
let shape = match shape_from(raw.schema, raw.content)? {
(_, Some((media_type, value))) => ParameterShape::Content {
media_type,
value: Box::new(value),
},
(schema, None) => ParameterShape::Schema {
schema,
style: raw.style,
explode: raw.explode,
allow_reserved: raw.allow_reserved,
},
};
Ok(Self {
name: raw.name,
location: raw.location,
description: raw.description,
required: raw.required,
deprecated: raw.deprecated,
allow_empty_value: raw.allow_empty_value,
shape,
examples: examples_from(raw.example, raw.examples)?,
extensions: raw.extensions,
})
}
}
impl From<Parameter> for RawParameter {
fn from(parameter: Parameter) -> Self {
let (schema, style, explode, allow_reserved, content) = match parameter.shape {
ParameterShape::Schema {
schema,
style,
explode,
allow_reserved,
} => (Some(schema), style, explode, allow_reserved, Map::new()),
ParameterShape::Content { media_type, value } => (
None,
None,
None,
None,
Map::from_iter([(media_type, *value)]),
),
};
let (example, examples) = examples_into(parameter.examples);
Self {
name: parameter.name,
location: parameter.location,
description: parameter.description,
required: parameter.required,
deprecated: parameter.deprecated,
allow_empty_value: parameter.allow_empty_value,
style,
explode,
allow_reserved,
schema,
example,
examples,
content,
extensions: parameter.extensions,
}
}
}
#[cfg(test)]
mod tests;