use crate::common::formats::{CollectionFormat, IntegerFormat, NumberFormat, StringFormat};
use crate::common::helpers::{
Context, ValidateWithContext, validate_pattern, validate_required_string,
};
use crate::common::reference::RefOr;
use crate::v2::items::Items;
use crate::v2::schema::Schema;
use crate::v2::spec::Spec;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(tag = "in")]
pub enum Parameter {
#[serde(rename = "body")]
Body(Box<InBody>),
#[serde(rename = "header")]
Header(Box<InHeader>),
#[serde(rename = "query")]
Query(Box<InQuery>),
#[serde(rename = "path")]
Path(Box<InPath>),
#[serde(rename = "formData")]
FormData(Box<InFormData>),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct InBody {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
pub schema: RefOr<Schema>,
#[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)]
#[serde(tag = "type")]
pub enum InHeader {
#[serde(rename = "string")]
String(StringParameter),
#[serde(rename = "integer")]
Integer(IntegerParameter),
#[serde(rename = "number")]
Number(NumberParameter),
#[serde(rename = "boolean")]
Boolean(BooleanParameter),
#[serde(rename = "array")]
Array(ArrayParameter),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(tag = "type")]
pub enum InPath {
#[serde(rename = "string")]
String(StringParameter),
#[serde(rename = "integer")]
Integer(IntegerParameter),
#[serde(rename = "number")]
Number(NumberParameter),
#[serde(rename = "boolean")]
Boolean(BooleanParameter),
#[serde(rename = "array")]
Array(ArrayParameter),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(tag = "type")]
pub enum InQuery {
#[serde(rename = "string")]
String(StringParameter),
#[serde(rename = "integer")]
Integer(IntegerParameter),
#[serde(rename = "number")]
Number(NumberParameter),
#[serde(rename = "boolean")]
Boolean(BooleanParameter),
#[serde(rename = "array")]
Array(ArrayParameter),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(tag = "type")]
pub enum InFormData {
#[serde(rename = "string")]
String(StringParameter),
#[serde(rename = "integer")]
Integer(IntegerParameter),
#[serde(rename = "number")]
Number(NumberParameter),
#[serde(rename = "boolean")]
Boolean(BooleanParameter),
#[serde(rename = "array")]
Array(ArrayParameter),
#[serde(rename = "file")]
File(FileParameter),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct StringParameter {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "allowEmptyValue")]
pub allow_empty_value: Option<bool>,
#[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 IntegerParameter {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "allowEmptyValue")]
pub allow_empty_value: Option<bool>,
#[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 NumberParameter {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "allowEmptyValue")]
pub allow_empty_value: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[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 BooleanParameter {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "allowEmptyValue")]
pub allow_empty_value: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[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, Default)]
pub struct ArrayParameter {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "allowEmptyValue")]
pub allow_empty_value: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
pub items: Items,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<Vec<serde_json::Value>>,
#[serde(rename = "collectionFormat")]
#[serde(skip_serializing_if = "Option::is_none")]
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>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct FileParameter {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<serde_json::Value>,
#[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 Parameter {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
match self {
Parameter::Body(p) => p.validate_with_context(ctx, path),
Parameter::Header(p) => p.validate_with_context(ctx, path),
Parameter::Query(p) => p.validate_with_context(ctx, path),
Parameter::Path(p) => p.validate_with_context(ctx, path),
Parameter::FormData(p) => p.validate_with_context(ctx, path),
}
}
}
impl ValidateWithContext<Spec> for InBody {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
validate_required_string(&self.name, ctx, format!("{path}name"));
self.schema
.validate_with_context(ctx, format!("{path}.schema"));
}
}
impl ValidateWithContext<Spec> for InHeader {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
match self {
InHeader::String(p) => {
p.validate_with_context(ctx, path.clone());
must_not_allow_empty_value(&p.allow_empty_value, ctx, path.clone(), p.name.clone());
if let Some(pattern) = &p.pattern {
validate_pattern(pattern, ctx, format!("{path}.pattern"));
}
}
InHeader::Integer(p) => {
p.validate_with_context(ctx, path.clone());
must_not_allow_empty_value(&p.allow_empty_value, ctx, path.clone(), p.name.clone());
}
InHeader::Number(p) => {
p.validate_with_context(ctx, path.clone());
must_not_allow_empty_value(&p.allow_empty_value, ctx, path.clone(), p.name.clone());
}
InHeader::Boolean(p) => {
p.validate_with_context(ctx, path.clone());
must_not_allow_empty_value(&p.allow_empty_value, ctx, path.clone(), p.name.clone());
}
InHeader::Array(p) => {
p.validate_with_context(ctx, path.clone());
must_not_allow_empty_value(&p.allow_empty_value, ctx, path.clone(), p.name.clone());
}
}
}
}
impl ValidateWithContext<Spec> for InQuery {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
match self {
InQuery::String(p) => {
p.validate_with_context(ctx, path);
}
InQuery::Integer(p) => {
p.validate_with_context(ctx, path);
}
InQuery::Number(p) => {
p.validate_with_context(ctx, path);
}
InQuery::Boolean(p) => {
p.validate_with_context(ctx, path);
}
InQuery::Array(p) => {
p.validate_with_context(ctx, path);
}
}
}
}
impl ValidateWithContext<Spec> for InPath {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
match self {
InPath::String(p) => {
must_be_required(&p.required, ctx, path.clone(), p.name.clone());
must_not_allow_empty_value(&p.allow_empty_value, ctx, path.clone(), p.name.clone());
}
InPath::Integer(p) => {
must_be_required(&p.required, ctx, path.clone(), p.name.clone());
must_not_allow_empty_value(&p.allow_empty_value, ctx, path.clone(), p.name.clone());
}
InPath::Number(p) => {
must_be_required(&p.required, ctx, path.clone(), p.name.clone());
must_not_allow_empty_value(&p.allow_empty_value, ctx, path.clone(), p.name.clone());
}
InPath::Boolean(p) => {
must_be_required(&p.required, ctx, path.clone(), p.name.clone());
must_not_allow_empty_value(&p.allow_empty_value, ctx, path.clone(), p.name.clone());
}
InPath::Array(p) => {
must_be_required(&p.required, ctx, path.clone(), p.name.clone());
must_not_allow_empty_value(&p.allow_empty_value, ctx, path.clone(), p.name.clone());
}
}
}
}
impl ValidateWithContext<Spec> for InFormData {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
match self {
InFormData::String(p) => {
p.validate_with_context(ctx, path);
}
InFormData::Integer(p) => {
p.validate_with_context(ctx, path);
}
InFormData::Number(p) => {
p.validate_with_context(ctx, path);
}
InFormData::Boolean(p) => {
p.validate_with_context(ctx, path);
}
InFormData::Array(p) => {
p.validate_with_context(ctx, path);
}
InFormData::File(p) => {
p.validate_with_context(ctx, path);
}
}
}
}
impl ValidateWithContext<Spec> for StringParameter {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
validate_required_string(&self.name, ctx, format!("{path}.name"));
}
}
impl ValidateWithContext<Spec> for IntegerParameter {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
validate_required_string(&self.name, ctx, format!("{path}.name"));
}
}
impl ValidateWithContext<Spec> for NumberParameter {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
validate_required_string(&self.name, ctx, format!("{path}.name"));
}
}
impl ValidateWithContext<Spec> for BooleanParameter {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
validate_required_string(&self.name, ctx, format!("{path}.name"));
}
}
impl ValidateWithContext<Spec> for ArrayParameter {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
validate_required_string(&self.name, ctx, format!("{path}.name"));
}
}
impl ValidateWithContext<Spec> for FileParameter {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
validate_required_string(&self.name, ctx, format!("{path}.name"));
}
}
fn must_be_required(p: &Option<bool>, ctx: &mut Context<Spec>, path: String, name: String) {
if !p.is_some_and(|x| x) {
ctx.errors.push(format!("{path}.{name}: must be required"));
}
}
fn must_not_allow_empty_value(
p: &Option<bool>,
ctx: &mut Context<Spec>,
path: String,
name: String,
) {
if p.is_some_and(|x| x) {
ctx.errors
.push(format!("{path}.{name}: must not allow empty value"));
}
}