plugx-input 0.3.1

simple and flexible data-structure with diff, merge, and validation.
Documentation
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::ops::Range;

#[derive(Clone, Debug, Copy, Deserialize, Serialize)]
#[serde(
    rename_all = "snake_case",
    untagged,
    deny_unknown_fields,
    expecting = "Expecting an unsigned integer or an object containing `min` and `max` keys with unsigned integer values"
)]
pub enum InputSchemaTypeSize {
    Max(usize),
    MinMax {
        #[serde(rename = "max", skip_serializing_if = "Option::is_none")]
        maybe_max: Option<usize>,
        #[serde(rename = "min", skip_serializing_if = "Option::is_none")]
        maybe_min: Option<usize>,
    },
}

impl Display for InputSchemaTypeSize {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str(
            match (self.maybe_min(), self.maybe_max()) {
                (Some(min), Some(max)) => format!("size bigger than {min} and lower than {max}"),
                (Some(min), None) => format!("size bigger than {min}"),
                (None, Some(max)) => format!("size lower than {max}"),
                (None, None) => "size".into(),
            }
            .as_str(),
        )
    }
}

impl InputSchemaTypeSize {
    pub fn new_with_max<M: Into<usize>>(max: M) -> Self {
        Self::Max(max.into())
    }

    pub fn new_with_min<M: Into<usize>>(min: M) -> Self {
        Self::MinMax {
            maybe_max: None,
            maybe_min: Some(min.into()),
        }
    }

    pub fn new_with_min_max<M: Into<usize>>(min: M, max: M) -> Self {
        Self::MinMax {
            maybe_max: Some(max.into()),
            maybe_min: Some(min.into()),
        }
    }

    pub fn set_min<M: Into<usize>>(&mut self, min: M) {
        *self = match self {
            Self::Max(max) => Self::MinMax {
                maybe_max: Some(*max),
                maybe_min: Some(min.into()),
            },
            Self::MinMax { maybe_max, .. } => Self::MinMax {
                maybe_max: *maybe_max,
                maybe_min: Some(min.into()),
            },
        };
    }

    pub fn with_min<M: Into<usize>>(mut self, min: M) -> Self {
        self.set_min(min);
        self
    }

    pub fn set_max<M: Into<usize>>(&mut self, max: M) {
        *self = match self {
            Self::Max(..) => Self::Max(max.into()),
            Self::MinMax { maybe_min, .. } => Self::MinMax {
                maybe_max: Some(max.into()),
                maybe_min: *maybe_min,
            },
        };
    }

    pub fn with_max<M: Into<usize>>(mut self, max: M) -> Self {
        self.set_max(max);
        self
    }

    pub fn maybe_min(&self) -> Option<usize> {
        match self {
            Self::MinMax { maybe_min, .. } => *maybe_min,
            _ => None,
        }
    }

    pub fn maybe_max(&self) -> Option<usize> {
        match self {
            Self::MinMax { maybe_max, .. } => *maybe_max,
            Self::Max(max) => Some(*max),
        }
    }
}

impl PartialEq for InputSchemaTypeSize {
    fn eq(&self, other: &Self) -> bool {
        self.maybe_max() == other.maybe_max() && self.maybe_min() == other.maybe_min()
    }
}

impl<T: Into<usize>> From<Range<T>> for InputSchemaTypeSize {
    fn from(range: Range<T>) -> Self {
        Self::MinMax {
            maybe_max: Some(range.end.into()),
            maybe_min: Some(range.start.into()),
        }
    }
}

impl<T1: Into<usize>, T2: Into<usize>> From<(T1, T2)> for InputSchemaTypeSize {
    fn from((min, max): (T1, T2)) -> Self {
        Self::MinMax {
            maybe_max: Some(max.into()),
            maybe_min: Some(min.into()),
        }
    }
}

impl From<usize> for InputSchemaTypeSize {
    fn from(max: usize) -> Self {
        Self::Max(max)
    }
}

impl From<u32> for InputSchemaTypeSize {
    fn from(max: u32) -> Self {
        Self::Max(max as usize)
    }
}

impl From<u16> for InputSchemaTypeSize {
    fn from(max: u16) -> Self {
        Self::Max(max as usize)
    }
}

impl From<u8> for InputSchemaTypeSize {
    fn from(max: u8) -> Self {
        Self::Max(max as usize)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn from() {
        assert_eq!(InputSchemaTypeSize::from(0u8).maybe_max(), Some(0u8.into()));
        assert_eq!(
            InputSchemaTypeSize::from((10usize, 20usize)).maybe_max(),
            Some(20)
        );
        assert_eq!(
            InputSchemaTypeSize::from((10usize, 20usize)).maybe_min(),
            Some(10)
        );
        assert_eq!(InputSchemaTypeSize::from(10usize..20).maybe_max(), Some(20));
        assert_eq!(InputSchemaTypeSize::from(10usize..20).maybe_min(), Some(10));
    }

    #[test]
    fn serde() {
        let json = serde_json::to_string_pretty(&serde_json::json!(3)).unwrap();
        assert_eq!(
            serde_json::from_str::<InputSchemaTypeSize>(json.as_str())
                .unwrap()
                .maybe_min(),
            None
        );
        assert_eq!(
            serde_json::from_str::<InputSchemaTypeSize>(json.as_str())
                .unwrap()
                .maybe_max(),
            Some(3)
        );

        let json = serde_json::to_string_pretty(&serde_json::json!({"min": 10})).unwrap();
        assert_eq!(
            serde_json::from_str::<InputSchemaTypeSize>(json.as_str())
                .unwrap()
                .maybe_min(),
            Some(10)
        );
        assert_eq!(
            serde_json::from_str::<InputSchemaTypeSize>(json.as_str())
                .unwrap()
                .maybe_max(),
            None
        );

        let json = serde_json::to_string_pretty(&serde_json::json!({"min": 10, "max": 4})).unwrap();
        assert_eq!(
            serde_json::from_str::<InputSchemaTypeSize>(json.as_str())
                .unwrap()
                .maybe_min(),
            Some(10)
        );
        assert_eq!(
            serde_json::from_str::<InputSchemaTypeSize>(json.as_str())
                .unwrap()
                .maybe_max(),
            Some(4)
        );

        let json =
            serde_json::to_string_pretty(&serde_json::json!({"min": 10, "max": -4})).unwrap();
        assert!(serde_json::from_str::<InputSchemaTypeSize>(json.as_str()).is_err());
    }
}