use crate::config::*;
use serde::{Deserialize, Serialize};
use snafu::prelude::*;
use std::ops::Deref;
type Result<T> = std::result::Result<T, ValidationError>;
#[allow(missing_docs)]
#[cfg_attr(
feature = "python-bindings",
gen_stub_pyclass_complex_enum,
pyclass(from_py_object)
)]
#[derive(Snafu, Debug, Clone, Copy, PartialEq)]
pub enum ValidationError {
#[snafu(display("Value {} is out of range. Should be within ({}, {})", value, min, max))]
ExclusiveValueOutOfRange { value: f64, min: f64, max: f64 },
#[snafu(display("Value {} is out of range. Should be within [{}, {}]", value, min, max))]
InclusiveValueOutOfRange { value: f64, min: f64, max: f64 },
}
#[cfg(feature = "python-bindings")]
impl From<ValidationError> for PyErr {
fn from(value: ValidationError) -> Self {
PyErr::new::<ValidationError, _>(value)
}
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, derive_more::Display)]
#[cfg_attr(feature = "python-bindings", gen_stub_pyclass, pyclass(from_py_object))]
pub struct Positive(Flt);
impl Positive {
pub fn new(value: Flt) -> Result<Self> {
ensure!(
value >= 0.0,
InclusiveValueOutOfRangeSnafu {
min: 0.0,
max: FltInf,
value
}
);
Ok(Self(value))
}
}
#[cfg(feature = "python-bindings")]
#[cfg_attr(feature = "python-bindings", gen_stub_pymethods, pymethods)]
impl Positive {
#[allow(dead_code)]
fn toFloat(&self) -> Flt {
self.0
}
#[new]
fn py_new(value: Flt) -> PyResult<Self> {
Ok(Self::new(value)?)
}
}
impl TryFrom<Flt> for Positive {
type Error = ValidationError;
fn try_from(value: Flt) -> Result<Self> {
Self::new(value)
}
}
impl Deref for Positive {
type Target = Flt;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg_attr(feature = "python-bindings", gen_stub_pyclass, pyclass(from_py_object))]
#[derive(Copy, Clone, Debug, PartialEq, derive_more::Display, Serialize, Deserialize)]
pub struct StrictlyPositive(Flt);
impl StrictlyPositive {
pub fn new(value: Flt) -> Result<Self> {
ensure!(
value > 0.0,
ExclusiveValueOutOfRangeSnafu {
min: 0.0,
max: FltInf,
value
}
);
Ok(Self(value))
}
pub fn one() -> Self {
Self(1.0)
}
}
#[cfg_attr(feature = "python-bindings", gen_stub_pymethods, pymethods)]
impl StrictlyPositive {
#[allow(dead_code)]
fn toFloat(&self) -> Flt {
self.0
}
#[cfg(feature = "python-bindings")]
#[new]
fn new_py(val: Flt) -> PyResult<Self> {
Ok(Self::new(val)?)
}
}
impl TryFrom<Flt> for StrictlyPositive {
type Error = ValidationError;
fn try_from(value: Flt) -> Result<Self> {
Self::new(value)
}
}
impl Deref for StrictlyPositive {
type Target = Flt;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
pub struct Bounded<const MIN: i32, const MAX: i32>(Flt);
pub type Percentage = Bounded<0, 100>;
impl<const MIN: i32, const MAX: i32> Bounded<MIN, MAX> {
pub fn new(value: Flt) -> Result<Self> {
let min = MIN as Flt;
let max = MAX as Flt;
debug_assert!(min < max);
ensure!(
value >= min,
InclusiveValueOutOfRangeSnafu { min, max, value }
);
ensure!(
value <= max,
InclusiveValueOutOfRangeSnafu { min, max, value }
);
Ok(Self(value))
}
}
impl<const MIN: i32, const MAX: i32> TryFrom<Flt> for Bounded<MIN, MAX> {
type Error = ValidationError;
fn try_from(value: Flt) -> Result<Self> {
Self::new(value)
}
}
impl<const MIN: i32, const MAX: i32> Deref for Bounded<MIN, MAX> {
type Target = Flt;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::Result;
use super::*;
#[test]
fn test_positive_valid() {
let pos = Positive::new(5.0).unwrap();
assert_eq!(*pos, 5.0);
let pos = Positive::new(0.1).unwrap();
assert_eq!(*pos, 0.1);
let pos = Positive::new(1000.0).unwrap();
assert_eq!(*pos, 1000.0);
}
#[test]
fn test_positive_invalid() {
assert!(Positive::new(-1e-300).is_err());
assert!(Positive::new(-1.0).is_err());
assert!(Positive::new(Flt::NAN).is_err());
}
#[test]
fn test_positive_try_from() {
let pos: Result<Positive> = 5.0.try_into();
assert!(pos.is_ok());
assert_eq!(*pos.unwrap(), 5.0);
let pos: Result<Positive> = (-1.0).try_into();
assert!(pos.is_err());
}
#[test]
fn test_bounded_valid() {
type TestBounded = Bounded<1, 10>;
let bounded = TestBounded::new(5.0).unwrap();
assert_eq!(*bounded, 5.0);
let bounded = TestBounded::new(1.0).unwrap();
assert_eq!(*bounded, 1.0);
let bounded = TestBounded::new(10.0).unwrap();
assert_eq!(*bounded, 10.0);
}
#[test]
fn test_bounded_invalid() {
type TestBounded = Bounded<1, 10>;
assert!(TestBounded::new(0.9).is_err());
assert!(TestBounded::new(10.1).is_err());
assert!(TestBounded::new(-1.0).is_err());
assert!(TestBounded::new(0.0).is_err());
}
#[test]
fn test_strictlybounded_invalid_bounds() {
type InvalidBounds1 = StrictBounded<0, 10>; type InvalidBounds2 = StrictBounded<8, 10>;
assert!(InvalidBounds1::new(10.0).is_err());
assert!(InvalidBounds2::new(7.0).is_err());
}
#[test]
fn test_bounded_try_from() {
type TestBounded = Bounded<1, 10>;
let bounded: Result<TestBounded> = 5.0.try_into();
assert!(bounded.is_ok());
assert_eq!(*bounded.unwrap(), 5.0);
let bounded: Result<TestBounded> = 15.0.try_into();
assert!(bounded.is_err());
}
#[test]
fn test_strict_bounded_valid() {
type TestStrictBounded = StrictBounded<1, 10>;
let strict = TestStrictBounded::new(5.0).unwrap();
assert_eq!(*strict, 5.0);
let strict = TestStrictBounded::new(1.1).unwrap();
assert_eq!(*strict, 1.1);
let strict = TestStrictBounded::new(9.9).unwrap();
assert_eq!(*strict, 9.9);
}
#[test]
fn test_strict_bounded_invalid() {
type TestStrictBounded = StrictBounded<1, 10>;
assert!(TestStrictBounded::new(1.0).is_err());
assert!(TestStrictBounded::new(10.0).is_err());
assert!(TestStrictBounded::new(0.9).is_err());
assert!(TestStrictBounded::new(10.1).is_err());
assert!(TestStrictBounded::new(-1.0).is_err());
assert!(TestStrictBounded::new(0.0).is_err());
}
#[test]
fn test_strict_bounded_try_from() {
type TestStrictBounded = StrictBounded<1, 10>;
let strict: Result<TestStrictBounded> = 5.0.try_into();
assert!(strict.is_ok());
assert_eq!(*strict.unwrap(), 5.0);
let strict: Result<TestStrictBounded> = 1.0.try_into();
assert!(strict.is_err());
let strict: Result<TestStrictBounded> = 10.0.try_into();
assert!(strict.is_err());
}
#[test]
fn test_different_bounded_types() {
type SmallBounded = Bounded<1, 10>;
type LargeBounded = Bounded<100, 1000>;
let small = SmallBounded::new(5.0).unwrap();
let large = LargeBounded::new(500.0).unwrap();
assert_eq!(*small, 5.0);
assert_eq!(*large, 500.0);
}
#[test]
fn test_serde_serialization() {
use serde_json;
let pos = Positive::new(5.0).unwrap();
let serialized = serde_json::to_string(&pos).unwrap();
let deserialized: Positive = serde_json::from_str(&serialized).unwrap();
assert_eq!(*pos, *deserialized);
type TestBounded = Bounded<1, 10>;
let bounded = TestBounded::new(5.0).unwrap();
let serialized = serde_json::to_string(&bounded).unwrap();
let deserialized: TestBounded = serde_json::from_str(&serialized).unwrap();
assert_eq!(*bounded, *deserialized);
type TestStrictBounded = StrictBounded<1, 10>;
let strict = TestStrictBounded::new(5.0).unwrap();
let serialized = serde_json::to_string(&strict).unwrap();
let deserialized: TestStrictBounded = serde_json::from_str(&serialized).unwrap();
assert_eq!(*strict, *deserialized);
}
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
pub struct StrictBounded<const MIN: i32, const MAX: i32>(Flt);
impl<const MIN: i32, const MAX: i32> StrictBounded<MIN, MAX> {
pub fn new(value: Flt) -> Result<Self> {
let min = MIN as Flt;
let max = MAX as Flt;
debug_assert!(min < max);
ensure!(
value > min && value < max,
ExclusiveValueOutOfRangeSnafu { min, max, value }
);
Ok(Self(value))
}
}
impl<const MIN: i32, const MAX: i32> TryFrom<Flt> for StrictBounded<MIN, MAX> {
type Error = ValidationError;
fn try_from(value: Flt) -> Result<Self> {
Self::new(value)
}
}
impl<const MIN: i32, const MAX: i32> Deref for StrictBounded<MIN, MAX> {
type Target = Flt;
fn deref(&self) -> &Self::Target {
&self.0
}
}