use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use crate::common::formats::{CollectionFormat, IntegerFormat, NumberFormat, StringFormat};
use crate::common::helpers::{Context, ValidateWithContext, validate_pattern};
use crate::v2::items::Items;
use crate::v2::spec::Spec;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(tag = "type")]
pub enum Header {
#[serde(rename = "string")]
String(StringHeader),
#[serde(rename = "integer")]
Integer(IntegerHeader),
#[serde(rename = "number")]
Number(NumberHeader),
#[serde(rename = "boolean")]
Boolean(BooleanHeader),
#[serde(rename = "array")]
Array(ArrayHeader),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct StringHeader {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<StringFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<String>,
#[serde(rename = "enum")]
#[serde(skip_serializing_if = "Option::is_none")]
pub enum_values: Option<Vec<String>>,
#[serde(rename = "maxLength")]
#[serde(skip_serializing_if = "Option::is_none")]
pub max_length: Option<u64>,
#[serde(rename = "minLength")]
#[serde(skip_serializing_if = "Option::is_none")]
pub min_length: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
#[serde(flatten)]
#[serde(with = "crate::common::extensions")]
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<BTreeMap<String, serde_json::Value>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct IntegerHeader {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<IntegerFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<i64>,
#[serde(rename = "enum")]
#[serde(skip_serializing_if = "Option::is_none")]
pub enum_values: Option<Vec<i64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum: Option<i64>,
#[serde(rename = "exclusiveMinimum")]
#[serde(skip_serializing_if = "Option::is_none")]
pub exclusive_minimum: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub maximum: Option<i64>,
#[serde(rename = "exclusiveMaximum")]
#[serde(skip_serializing_if = "Option::is_none")]
pub exclusive_maximum: Option<bool>,
#[serde(rename = "multipleOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub multiple_of: Option<f64>,
#[serde(flatten)]
#[serde(with = "crate::common::extensions")]
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<BTreeMap<String, serde_json::Value>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct NumberHeader {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<NumberFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<f64>,
#[serde(rename = "enum")]
#[serde(skip_serializing_if = "Option::is_none")]
pub enum_values: Option<Vec<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum: Option<f64>,
#[serde(rename = "exclusiveMinimum")]
#[serde(skip_serializing_if = "Option::is_none")]
pub exclusive_minimum: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub maximum: Option<f64>,
#[serde(rename = "exclusiveMaximum")]
#[serde(skip_serializing_if = "Option::is_none")]
pub exclusive_maximum: Option<bool>,
#[serde(rename = "multipleOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub multiple_of: Option<f64>,
#[serde(flatten)]
#[serde(with = "crate::common::extensions")]
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<BTreeMap<String, serde_json::Value>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct BooleanHeader {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<bool>,
#[serde(flatten)]
#[serde(with = "crate::common::extensions")]
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<BTreeMap<String, serde_json::Value>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct ArrayHeader {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub items: Items,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<Vec<serde_json::Value>>,
#[serde(rename = "collectionFormat")]
pub collection_format: Option<CollectionFormat>,
#[serde(rename = "maxItems")]
#[serde(skip_serializing_if = "Option::is_none")]
pub max_items: Option<u64>,
#[serde(rename = "minItems")]
#[serde(skip_serializing_if = "Option::is_none")]
pub min_items: Option<u64>,
#[serde(rename = "uniqueItems")]
#[serde(skip_serializing_if = "Option::is_none")]
pub unique_items: Option<bool>,
#[serde(flatten)]
#[serde(with = "crate::common::extensions")]
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<BTreeMap<String, serde_json::Value>>,
}
impl ValidateWithContext<Spec> for Header {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
match self {
Header::String(header) => header.validate_with_context(ctx, path),
Header::Integer(header) => header.validate_with_context(ctx, path),
Header::Number(header) => header.validate_with_context(ctx, path),
Header::Boolean(header) => header.validate_with_context(ctx, path),
Header::Array(header) => header.validate_with_context(ctx, path),
}
}
}
impl ValidateWithContext<Spec> for StringHeader {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
if let Some(pattern) = &self.pattern {
validate_pattern(pattern, ctx, format!("{path}.pattern"));
}
}
}
impl ValidateWithContext<Spec> for IntegerHeader {
fn validate_with_context(&self, _ctx: &mut Context<Spec>, _path: String) {}
}
impl ValidateWithContext<Spec> for NumberHeader {
fn validate_with_context(&self, _ctx: &mut Context<Spec>, _path: String) {}
}
impl ValidateWithContext<Spec> for BooleanHeader {
fn validate_with_context(&self, _ctx: &mut Context<Spec>, _path: String) {}
}
impl ValidateWithContext<Spec> for ArrayHeader {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
self.items
.validate_with_context(ctx, format!("{path}.items"));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::v2::items::StringItem;
#[test]
fn test_header_deserialize() {
assert_eq!(
serde_json::from_value::<Header>(serde_json::json!({
"type": "string",
"description": "A short description of the header.",
"format": "byte",
"default": "default",
"enum": ["enum"],
"maxLength": 10,
"minLength": 1,
"pattern": "pattern",
"x-extra": "extension",
}))
.unwrap(),
Header::String(StringHeader {
description: Some("A short description of the header.".to_owned()),
format: Some(StringFormat::Byte),
default: Some("default".to_owned()),
enum_values: Some(vec!["enum".to_owned()]),
max_length: Some(10),
min_length: Some(1),
pattern: Some("pattern".to_owned()),
extensions: Some({
let mut map = BTreeMap::new();
map.insert("x-extra".to_owned(), "extension".into());
map
}),
}),
"deserialize string",
);
assert_eq!(
serde_json::from_value::<Header>(serde_json::json!({
"type": "integer",
"description": "A short description of the header.",
"format": "int32",
"default": 5,
"enum": [5],
"maximum": 10,
"exclusiveMaximum": true,
"minimum": 1,
"exclusiveMinimum": true,
"multipleOf": 1.0,
"x-extra": "extension",
}))
.unwrap(),
Header::Integer(IntegerHeader {
description: Some("A short description of the header.".to_owned()),
format: Some(IntegerFormat::Int32),
default: Some(5.to_owned()),
enum_values: Some(vec![5.to_owned()]),
maximum: Some(10),
exclusive_maximum: Some(true),
minimum: Some(1),
exclusive_minimum: Some(true),
multiple_of: Some(1.0),
extensions: Some({
let mut map = BTreeMap::new();
map.insert("x-extra".to_owned(), "extension".into());
map
}),
}),
"deserialize integer",
);
assert_eq!(
serde_json::from_value::<Header>(serde_json::json!({
"type": "number",
"description": "A short description of the header.",
"format": "double",
"default": 5.0,
"enum": [5.0],
"maximum": 10.0,
"exclusiveMaximum": true,
"minimum": 1.0,
"exclusiveMinimum": true,
"multipleOf": 1.0,
"x-extra": "extension",
}))
.unwrap(),
Header::Number(NumberHeader {
description: Some("A short description of the header.".to_owned()),
format: Some(NumberFormat::Double),
default: Some(5.0.to_owned()),
enum_values: Some(vec![5.0.to_owned()]),
maximum: Some(10.0),
exclusive_maximum: Some(true),
minimum: Some(1.0),
exclusive_minimum: Some(true),
multiple_of: Some(1.0),
extensions: Some({
let mut map = BTreeMap::new();
map.insert("x-extra".to_owned(), "extension".into());
map
}),
}),
"deserialize number",
);
assert_eq!(
serde_json::from_value::<Header>(serde_json::json!({
"type": "boolean",
"description": "A short description of the header.",
"default": true,
"x-extra": "extension",
}))
.unwrap(),
Header::Boolean(BooleanHeader {
description: Some("A short description of the header.".to_owned()),
default: Some(true),
extensions: Some({
let mut map = BTreeMap::new();
map.insert("x-extra".to_owned(), "extension".into());
map
}),
}),
"deserialize boolean",
);
assert_eq!(
serde_json::from_value::<Header>(serde_json::json!({
"type": "array",
"items": {
"type": "string",
},
"description": "A short description of the header.",
"default": ["default"],
"collectionFormat": "tsv",
"maxItems": 10,
"minItems": 1,
"uniqueItems": true,
"x-extra": "extension",
}))
.unwrap(),
Header::Array(ArrayHeader {
description: Some("A short description of the header.".to_owned()),
items: Items::String(StringItem::default()),
default: Some(vec![serde_json::json!("default")]),
collection_format: Some(CollectionFormat::TSV),
max_items: Some(10),
min_items: Some(1),
unique_items: Some(true),
extensions: Some({
let mut map = BTreeMap::new();
map.insert("x-extra".to_owned(), "extension".into());
map
}),
}),
"deserialize array",
);
}
#[test]
fn test_header_serialize() {
assert_eq!(
serde_json::to_value(Header::String(StringHeader {
description: Some("A short description of the header.".to_owned()),
format: Some(StringFormat::Byte),
default: Some("default".to_owned()),
enum_values: Some(vec!["enum".to_owned()]),
max_length: Some(10),
min_length: Some(1),
pattern: Some("pattern".to_owned()),
extensions: Some({
let mut map = BTreeMap::new();
map.insert("x-extra".to_owned(), "extension".into());
map
}),
}))
.unwrap(),
serde_json::json!({
"type": "string",
"description": "A short description of the header.",
"format": "byte",
"default": "default",
"enum": ["enum"],
"maxLength": 10,
"minLength": 1,
"pattern": "pattern",
"x-extra": "extension",
}),
"serialize string",
);
assert_eq!(
serde_json::to_value(Header::Integer(IntegerHeader {
description: Some("A short description of the header.".to_owned()),
format: Some(IntegerFormat::Int32),
default: Some(5.to_owned()),
enum_values: Some(vec![5.to_owned()]),
maximum: Some(10),
exclusive_maximum: Some(true),
minimum: Some(1),
exclusive_minimum: Some(true),
multiple_of: Some(1.0),
extensions: Some({
let mut map = BTreeMap::new();
map.insert("x-extra".to_owned(), "extension".into());
map
}),
}))
.unwrap(),
serde_json::json!({
"type": "integer",
"description": "A short description of the header.",
"format": "int32",
"default": 5,
"enum": [5],
"maximum": 10,
"exclusiveMaximum": true,
"minimum": 1,
"exclusiveMinimum": true,
"multipleOf": 1.0,
"x-extra": "extension",
}),
"serialize integer",
);
assert_eq!(
serde_json::to_value(Header::Number(NumberHeader {
description: Some("A short description of the header.".to_owned()),
format: Some(NumberFormat::Double),
default: Some(5.0.to_owned()),
enum_values: Some(vec![5.0.to_owned()]),
maximum: Some(10.0),
exclusive_maximum: Some(true),
minimum: Some(1.0),
exclusive_minimum: Some(true),
multiple_of: Some(1.0),
extensions: Some({
let mut map = BTreeMap::new();
map.insert("x-extra".to_owned(), "extension".into());
map
}),
}))
.unwrap(),
serde_json::json!({
"type": "number",
"description": "A short description of the header.",
"format": "double",
"default": 5.0,
"enum": [5.0],
"maximum": 10.0,
"exclusiveMaximum": true,
"minimum": 1.0,
"exclusiveMinimum": true,
"multipleOf": 1.0,
"x-extra": "extension",
}),
"serialize number",
);
assert_eq!(
serde_json::to_value(Header::Boolean(BooleanHeader {
description: Some("A short description of the header.".to_owned()),
default: Some(true),
extensions: Some({
let mut map = BTreeMap::new();
map.insert("x-extra".to_owned(), "extension".into());
map
}),
}))
.unwrap(),
serde_json::json!({
"type": "boolean",
"description": "A short description of the header.",
"default": true,
"x-extra": "extension",
}),
"serialize boolean",
);
assert_eq!(
serde_json::to_value(Header::Array(ArrayHeader {
description: Some("A short description of the header.".to_owned()),
items: Items::String(StringItem::default()),
default: Some(vec![serde_json::json!("default")]),
collection_format: Some(CollectionFormat::TSV),
max_items: Some(10),
min_items: Some(1),
unique_items: Some(true),
extensions: Some({
let mut map = BTreeMap::new();
map.insert("x-extra".to_owned(), "extension".into());
map
}),
}))
.unwrap(),
serde_json::json!({
"type": "array",
"items": {
"type": "string",
},
"description": "A short description of the header.",
"default": ["default"],
"collectionFormat": "tsv",
"maxItems": 10,
"minItems": 1,
"uniqueItems": true,
"x-extra": "extension",
}),
"serialize array",
);
}
}