use oas3::spec::ObjectSchema;
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use serde_json::Number;
use crate::generator::ast::{RustPrimitive, StructToken, TypeRef, types::render_unsigned_integer};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct RegexKey {
owner_type: StructToken,
owner_variant: Option<String>,
field: String,
}
impl RegexKey {
pub fn for_struct(type_name: &StructToken, field_name: &str) -> Self {
Self {
owner_type: type_name.clone(),
owner_variant: None,
field: field_name.to_string(),
}
}
pub fn parts(&self) -> Vec<&str> {
let mut parts = vec![self.owner_type.as_str()];
if let Some(variant) = &self.owner_variant {
parts.push(variant.as_str());
}
parts.push(self.field.as_str());
parts
}
}
#[derive(Debug, Clone)]
pub enum ValidationAttribute {
Email,
Url,
Nested,
Length {
min: Option<u64>,
max: Option<u64>,
},
Range {
primitive: RustPrimitive,
min: Option<Number>,
max: Option<Number>,
exclusive_min: Option<Number>,
exclusive_max: Option<Number>,
},
Regex(String),
}
impl ValidationAttribute {
pub fn range(schema: &ObjectSchema, type_ref: &TypeRef) -> Option<Self> {
let exclusive_min = schema.exclusive_minimum.clone();
let exclusive_max = schema.exclusive_maximum.clone();
let min = schema.minimum.clone();
let max = schema.maximum.clone();
if exclusive_min.is_none() && exclusive_max.is_none() && min.is_none() && max.is_none() {
return None;
}
Some(Self::Range {
primitive: type_ref.base_type.clone(),
min,
max,
exclusive_min,
exclusive_max,
})
}
pub fn length(min: Option<u64>, max: Option<u64>, is_required_non_empty: bool) -> Option<Self> {
match (min, max) {
(Some(min), Some(max)) => Some(Self::Length {
min: Some(min),
max: Some(max),
}),
(Some(min), None) => Some(Self::Length {
min: Some(min),
max: None,
}),
(None, Some(max)) => Some(Self::Length {
min: None,
max: Some(max),
}),
(None, None) if is_required_non_empty => Some(Self::Length {
min: Some(1),
max: None,
}),
_ => None,
}
}
}
impl PartialEq for ValidationAttribute {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Email, Self::Email) | (Self::Url, Self::Url) | (Self::Nested, Self::Nested) => true,
(Self::Length { min: min1, max: max1 }, Self::Length { min: min2, max: max2 }) => min1 == min2 && max1 == max2,
(
Self::Range {
primitive: p1,
min: min1,
max: max1,
exclusive_min: emin1,
exclusive_max: emax1,
},
Self::Range {
primitive: p2,
min: min2,
max: max2,
exclusive_min: emin2,
exclusive_max: emax2,
},
) => {
p1 == p2
&& compare_numbers(min1.as_ref(), min2.as_ref())
&& compare_numbers(max1.as_ref(), max2.as_ref())
&& compare_numbers(emin1.as_ref(), emin2.as_ref())
&& compare_numbers(emax1.as_ref(), emax2.as_ref())
}
(Self::Regex(s1), Self::Regex(s2)) => s1 == s2,
_ => false,
}
}
}
impl Eq for ValidationAttribute {}
fn compare_numbers(n1: Option<&Number>, n2: Option<&Number>) -> bool {
match (n1, n2) {
(None, None) => true,
(Some(a), Some(b)) => {
if let (Some(a_i64), Some(b_i64)) = (a.as_i64(), b.as_i64()) {
a_i64 == b_i64
} else if let (Some(a_u64), Some(b_u64)) = (a.as_u64(), b.as_u64()) {
a_u64 == b_u64
} else if let (Some(a_f64), Some(b_f64)) = (a.as_f64(), b.as_f64()) {
(a_f64 - b_f64).abs() < f64::EPSILON
} else {
false
}
}
_ => false,
}
}
impl ToTokens for ValidationAttribute {
fn to_tokens(&self, tokens: &mut TokenStream) {
let attr = match self {
Self::Email => quote! { email },
Self::Url => quote! { url },
Self::Nested => quote! { nested },
Self::Regex(path) => quote! { regex(path = #path) },
Self::Length { min, max } => {
let min_part = min.map(|m| {
let lit = render_unsigned_integer(&RustPrimitive::U64, m)
.parse::<TokenStream>()
.unwrap();
quote! { min = #lit }
});
let max_part = max.map(|m| {
let lit = render_unsigned_integer(&RustPrimitive::U64, m)
.parse::<TokenStream>()
.unwrap();
quote! { max = #lit }
});
match (min_part, max_part) {
(Some(min), Some(max)) => quote! { length(#min, #max) },
(Some(min), None) => quote! { length(#min) },
(None, Some(max)) => quote! { length(#max) },
(None, None) => quote! { length() },
}
}
Self::Range {
primitive,
min,
max,
exclusive_min,
exclusive_max,
} => {
let mut parts = vec![];
if let Some(m) = min {
let lit = primitive.format_number(m).parse::<TokenStream>().unwrap();
parts.push(quote! { min = #lit });
}
if let Some(m) = max {
let lit = primitive.format_number(m).parse::<TokenStream>().unwrap();
parts.push(quote! { max = #lit });
}
if let Some(m) = exclusive_min {
let lit = primitive.format_number(m).parse::<TokenStream>().unwrap();
parts.push(quote! { exclusive_min = #lit });
}
if let Some(m) = exclusive_max {
let lit = primitive.format_number(m).parse::<TokenStream>().unwrap();
parts.push(quote! { exclusive_max = #lit });
}
quote! { range(#(#parts),*) }
}
};
tokens.extend(attr);
}
}