use serde_json::Value;
use std::marker::PhantomData;
use crate::error::{issue_error, IssueCode, SizeOrigin, VldError};
use crate::schema::VldSchema;
use super::{Num, ZNumber};
mod sealed {
pub trait Sealed {}
}
pub trait IntType: sealed::Sealed + Copy + 'static {
const MIN: i128;
const MAX: i128;
const FORMAT: &'static str;
#[doc(hidden)]
fn from_i128(v: i128) -> Self;
#[doc(hidden)]
fn to_i128(self) -> i128;
}
macro_rules! int_types {
($($t:ty => $fmt:expr),+ $(,)?) => {$(
impl sealed::Sealed for $t {}
impl IntType for $t {
const MIN: i128 = <$t>::MIN as i128;
const MAX: i128 = <$t>::MAX as i128;
const FORMAT: &'static str = $fmt;
fn from_i128(v: i128) -> Self {
v as $t
}
fn to_i128(self) -> i128 {
self as i128
}
}
)+};
}
int_types! {
i8 => "int8", i16 => "int16", i32 => "int32", i64 => "int64",
u8 => "uint8", u16 => "uint16", u32 => "uint32", u64 => "uint64",
isize => if isize::BITS == 64 { "int64" } else { "int32" },
usize => if usize::BITS == 64 { "uint64" } else { "uint32" },
}
pub struct ZInteger<T: IntType> {
inner: ZNumber,
custom_int_error: Option<String>,
typed: bool,
_type: PhantomData<fn() -> T>,
}
pub type ZInt = ZInteger<i64>;
impl<T: IntType> Clone for ZInteger<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
custom_int_error: self.custom_int_error.clone(),
typed: self.typed,
_type: PhantomData,
}
}
}
impl<T: IntType> Default for ZInteger<T> {
fn default() -> Self {
Self::new()
}
}
impl ZInteger<i64> {
pub(super) fn from_number(inner: ZNumber) -> Self {
Self {
inner,
custom_int_error: None,
typed: false,
_type: PhantomData,
}
}
}
impl<T: IntType> ZInteger<T> {
pub fn new() -> Self {
Self {
inner: ZNumber::new(),
custom_int_error: None,
typed: true,
_type: PhantomData,
}
}
fn with(mut self, f: impl FnOnce(ZNumber) -> ZNumber) -> Self {
self.inner = f(self.inner);
self
}
pub fn type_error(self, msg: impl Into<String>) -> Self {
self.with(|n| n.type_error(msg))
}
pub fn int_error(mut self, msg: impl Into<String>) -> Self {
self.custom_int_error = Some(msg.into());
self
}
pub fn with_messages<F>(mut self, f: F) -> Self
where
F: Fn(&str) -> Option<String> + Send + Sync + 'static,
{
if let Some(msg) = f("not_int") {
self.custom_int_error = Some(msg);
}
self.with(|n| n.with_messages(f))
}
pub fn coerce(mut self) -> Self {
self.inner.coerce = true;
self
}
pub fn min(self, val: T) -> Self {
self.with(|n| n.min_n(Num::I(val.to_i128())))
}
pub fn max(self, val: T) -> Self {
self.with(|n| n.max_n(Num::I(val.to_i128())))
}
pub fn gt(self, val: T) -> Self {
self.with(|n| n.gt_n(Num::I(val.to_i128())))
}
pub fn gte(self, val: T) -> Self {
self.min(val)
}
pub fn lt(self, val: T) -> Self {
self.with(|n| n.lt_n(Num::I(val.to_i128())))
}
pub fn lte(self, val: T) -> Self {
self.max(val)
}
pub fn positive(self) -> Self {
self.with(ZNumber::positive)
}
pub fn negative(self) -> Self {
self.with(ZNumber::negative)
}
pub fn non_negative(self) -> Self {
self.with(ZNumber::non_negative)
}
pub fn non_positive(self) -> Self {
self.with(ZNumber::non_positive)
}
pub fn safe(self) -> Self {
self.with(ZNumber::safe)
}
pub fn multiple_of(self, val: T) -> Self {
self.with(|n| n.multiple_of_n(Num::I(val.to_i128())))
}
fn not_int(&self, value: &Value) -> VldError {
issue_error(IssueCode::NotInt, self.custom_int_error.as_ref(), value)
}
fn int_from_f64(&self, f: f64, value: &Value) -> Result<i128, VldError> {
if f.fract() != 0.0 {
return Err(self.not_int(value));
}
Ok(f as i128)
}
fn extract_int(&self, value: &Value) -> Result<i128, VldError> {
let i = match value {
Value::Number(n) => match (n.as_i64(), n.as_u64()) {
(Some(i), _) => i128::from(i),
(_, Some(u)) => i128::from(u),
_ => self.int_from_f64(n.as_f64().unwrap_or(f64::NAN), value)?,
},
Value::String(s) if self.inner.coerce => match s.parse::<i128>() {
Ok(i) => i,
Err(_) => {
let f = self.inner.extract_number(value)?;
self.int_from_f64(f, value)?
}
},
_ => self.inner.extract_number(value)? as i128,
};
Ok(i)
}
fn type_range(&self, i: i128, value: &Value) -> Result<(), VldError> {
if i < T::MIN {
return Err(VldError::from_code_with_value(
IssueCode::TooSmall {
origin: SizeOrigin::Number,
minimum: Num::I(T::MIN).json(),
inclusive: true,
},
value,
));
}
if i > T::MAX {
return Err(VldError::from_code_with_value(
IssueCode::TooBig {
origin: SizeOrigin::Number,
maximum: Num::I(T::MAX).json(),
inclusive: true,
},
value,
));
}
Ok(())
}
}
impl<T: IntType> VldSchema for ZInteger<T> {
type Output = T;
fn parse_value(&self, value: &Value) -> Result<T, VldError> {
let n = self.extract_int(value)?;
self.inner.validate(Num::I(n), value)?;
self.type_range(n, value)?;
Ok(T::from_i128(n))
}
}
#[cfg(feature = "openapi")]
impl<T: IntType> crate::openapi::JsonSchema for ZInteger<T> {
fn json_schema(&self) -> serde_json::Value {
if !self.typed {
return self.inner.schema("integer", None);
}
let mut schema = self
.inner
.schema("integer", Some((Num::I(T::MIN), Num::I(T::MAX))));
schema["format"] = serde_json::json!(T::FORMAT);
schema
}
}
#[cfg(feature = "openapi")]
impl<T: IntType> crate::openapi::CollectNestedSchemas for ZInteger<T> {}