use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{
Map,
model::{
body::media_type::MediaType,
example::{Example, Examples, examples_from, examples_into, examples_with_named},
extensions::Extensions,
parameter::{ParameterConflict, shape_from, style::HeaderStyle},
reference::{Ref, RefOr},
schema::Schema,
},
};
pub const IGNORED_HEADER_PARAMETERS: &[&str] = &["Accept", "Content-Type", "Authorization"];
#[must_use]
pub fn is_ignored_header_parameter(name: &str) -> bool {
IGNORED_HEADER_PARAMETERS
.iter()
.any(|ignored| ignored.eq_ignore_ascii_case(name))
}
pub const IGNORED_HEADERS: &[&str] = &["Content-Type"];
#[must_use]
pub fn is_ignored_header(name: &str) -> bool {
IGNORED_HEADERS
.iter()
.any(|ignored| ignored.eq_ignore_ascii_case(name))
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "RawHeader", into = "RawHeader")]
pub struct Header {
pub description: Option<String>,
pub required: Option<bool>,
pub deprecated: Option<bool>,
shape: HeaderShape,
examples: Option<Examples>,
pub extensions: Extensions,
}
#[derive(Clone, Debug, PartialEq)]
pub enum HeaderShape {
Schema {
schema: Schema,
style: Option<HeaderStyle>,
explode: Option<bool>,
},
Content {
media_type: String,
value: Box<MediaType>,
},
}
impl Header {
#[must_use]
pub fn new(schema: Schema) -> Self {
Self::shaped(HeaderShape::Schema {
schema,
style: None,
explode: None,
})
}
pub fn with_content(media_type: impl Into<String>, value: MediaType) -> Self {
Self::shaped(HeaderShape::Content {
media_type: media_type.into(),
value: Box::new(value),
})
}
fn shaped(shape: HeaderShape) -> Self {
Self {
description: None,
required: None,
deprecated: None,
shape,
examples: None,
extensions: Extensions::default(),
}
}
#[must_use]
pub fn shape(&self) -> &HeaderShape {
&self.shape
}
pub fn shape_mut(&mut self) -> &mut HeaderShape {
&mut self.shape
}
#[must_use]
pub fn schema(&self) -> Option<&Schema> {
match &self.shape {
HeaderShape::Schema { schema, .. } => Some(schema),
HeaderShape::Content { .. } => None,
}
}
#[must_use]
pub fn content(&self) -> Option<(&str, &MediaType)> {
match &self.shape {
HeaderShape::Content { media_type, value } => Some((media_type, &**value)),
HeaderShape::Schema { .. } => None,
}
}
#[must_use]
pub fn style(&self) -> Option<HeaderStyle> {
match self.shape {
HeaderShape::Schema { style, .. } => style,
HeaderShape::Content { .. } => None,
}
}
#[must_use]
pub fn with_style(mut self, style: HeaderStyle, explode: bool) -> Self {
if let HeaderShape::Schema {
style: slot,
explode: exploded,
..
} = &mut self.shape
{
*slot = Some(style);
*exploded = Some(explode);
}
self
}
#[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 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 with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
#[must_use]
pub fn required(mut self, required: bool) -> Self {
self.required = Some(required);
self
}
}
#[derive(Serialize, Deserialize)]
struct RawHeader {
#[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(default, skip_serializing_if = "Option::is_none")]
style: Option<HeaderStyle>,
#[serde(default, skip_serializing_if = "Option::is_none")]
explode: 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,
}
impl TryFrom<RawHeader> for Header {
type Error = ParameterConflict;
fn try_from(raw: RawHeader) -> Result<Self, Self::Error> {
let shape = match shape_from(raw.schema, raw.content)? {
(_, Some((media_type, value))) => HeaderShape::Content {
media_type,
value: Box::new(value),
},
(schema, None) => HeaderShape::Schema {
schema,
style: raw.style,
explode: raw.explode,
},
};
Ok(Self {
description: raw.description,
required: raw.required,
deprecated: raw.deprecated,
shape,
examples: examples_from(raw.example, raw.examples)?,
extensions: raw.extensions,
})
}
}
impl From<Header> for RawHeader {
fn from(header: Header) -> Self {
let (schema, style, explode, content) = match header.shape {
HeaderShape::Schema {
schema,
style,
explode,
} => (Some(schema), style, explode, Map::new()),
HeaderShape::Content { media_type, value } => {
(None, None, None, Map::from_iter([(media_type, *value)]))
}
};
let (example, examples) = examples_into(header.examples);
Self {
description: header.description,
required: header.required,
deprecated: header.deprecated,
style,
explode,
schema,
example,
examples,
content,
extensions: header.extensions,
}
}
}