entity 0.3.2

Library that provides entity-like constructs
Documentation
use super::{NumberType, PrimitiveType, Value};
use strum::ParseError;

/// Represents value types (primitive or complex). Assumes that complex
/// types will contain the same inner type and does not vary
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde-1", derive(serde::Serialize, serde::Deserialize))]
pub enum ValueType {
    List(Box<ValueType>),
    Map(Box<ValueType>),
    Optional(Box<ValueType>),
    Primitive(PrimitiveType),
    Text,
    Custom,
}

impl ValueType {
    pub fn is_primitive_type(&self) -> bool {
        matches!(self, Self::Primitive(_))
    }

    pub fn to_primitive_type(&self) -> Option<PrimitiveType> {
        match self {
            Self::Primitive(x) => Some(*x),
            _ => None,
        }
    }

    /// Constructs a value type from a Rust-based type string similar to what
    /// you would find from `std::any::type_name`
    ///
    /// ## Examples
    ///
    /// ```
    /// use entity::{ValueType as VT, PrimitiveType as PVT, NumberType as NT};
    ///
    /// assert_eq!(
    ///     VT::from_type_name("u8").expect("one"),
    ///     VT::Primitive(PVT::Number(NT::U8)),
    /// );
    ///
    /// assert_eq!(
    ///     VT::from_type_name("std::vec::Vec<std::string::String>").expect("two"),
    ///     VT::List(Box::from(VT::Text)),
    /// );
    ///
    /// assert_eq!(
    ///     VT::from_type_name("Vec<Option<u8>>").expect("three"),
    ///     VT::List(Box::from(VT::Optional(Box::from(VT::Primitive(PVT::Number(NT::U8)))))),
    /// );
    ///
    /// assert_eq!(
    ///     VT::from_type_name("HashMap<String, u8>").expect("four"),
    ///     VT::Map(Box::from(VT::Primitive(PVT::Number(NT::U8)))),
    /// );
    /// ```
    pub fn from_type_name(name: &str) -> Result<Self, ParseError> {
        if name.is_empty() {
            return Err(ParseError::VariantNotFound);
        }

        // Split based on the start of a generic in the form of Outer<Inner>
        let mut tokens = name.split(|c| c == '<');
        let maybe_outer_str = tokens.next();
        let inner_str = {
            let mut x = tokens.collect::<Vec<&str>>().join("<");
            if x.ends_with('>') {
                x.pop();
            }
            x
        };

        // Get the outer type based on an equivalent rust type
        //
        // * HashMap | BTreeMap -> Map
        // * Vec | VecDeque | LinkedList | HashSet | BTreeSet | BinaryHeap -> List
        // * Option -> Optional
        // * String -> Text
        // * (anything else) -> Primitive
        match maybe_outer_str
            .unwrap()
            .split(|c| c == ':')
            .last()
            .unwrap()
            .to_lowercase()
            .as_str()
        {
            // If a map, we expect the form to be ...<String, ...> and will
            // verify that the first type paraemter is String
            "hashmap" | "btreemap" => {
                let mut items = inner_str.split(|c| c == ',');
                if let Some(s) = items.next() {
                    if s.trim().to_lowercase().as_str() != "string" {
                        return Err(ParseError::VariantNotFound);
                    }
                }

                let rest = items.collect::<String>();
                Ok(ValueType::Map(Box::from(Self::from_type_name(
                    &rest.trim(),
                )?)))
            }
            "vec" | "vecdeque" | "linkedlist" | "hashset" | "btreeset" | "binaryheap" => Ok(
                ValueType::List(Box::from(Self::from_type_name(&inner_str)?)),
            ),
            "option" => Ok(ValueType::Optional(Box::from(Self::from_type_name(
                &inner_str,
            )?))),
            "string" => Ok(ValueType::Text),
            x => Ok(ValueType::Primitive(PrimitiveType::from_type_name(x)?)),
        }
    }
}

impl Default for ValueType {
    /// Returns default value type of primitive unit
    fn default() -> Self {
        Self::Primitive(Default::default())
    }
}

impl std::str::FromStr for ValueType {
    type Err = ParseError;

    /// Parses a string delimited by colons into a nested value type
    ///
    /// ## Examples
    ///
    /// ```
    /// use entity::{ValueType as VT, PrimitiveType as PVT, NumberType as NT};
    /// use strum::ParseError;
    /// use std::str::FromStr;
    ///
    /// assert_eq!(VT::from_str("char").unwrap(), VT::Primitive(PVT::Char));
    /// assert_eq!(VT::from_str("u32").unwrap(), VT::Primitive(PVT::Number(NT::U32)));
    /// assert_eq!(VT::from_str("number:u32").unwrap(), VT::Primitive(PVT::Number(NT::U32)));
    /// assert_eq!(VT::from_str("primitive:number:u32").unwrap(), VT::Primitive(PVT::Number(NT::U32)));
    /// assert_eq!(VT::from_str("list:u32").unwrap(), VT::List(Box::from(VT::Primitive(PVT::Number(NT::U32)))));
    /// assert_eq!(VT::from_str("list:number:u32").unwrap(), VT::List(Box::from(VT::Primitive(PVT::Number(NT::U32)))));
    /// assert_eq!(VT::from_str("list:primitive:number:u32").unwrap(), VT::List(Box::from(VT::Primitive(PVT::Number(NT::U32)))));
    /// assert_eq!(VT::from_str("unknown").unwrap_err(), ParseError::VariantNotFound);
    /// ```
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        fn opt_to_err(maybe_type: Option<ValueType>) -> Result<ValueType, ParseError> {
            match maybe_type {
                Some(t) => Ok(t),
                None => Err(ParseError::VariantNotFound),
            }
        }

        fn from_tokens<'a>(
            mut it: impl Iterator<Item = &'a str>,
        ) -> Result<Option<ValueType>, ParseError> {
            match it.next() {
                // Special case where we cannot feed this directly into the
                // primitive value type as it is the following type that is
                // used instead, so we take the next value instead and use it
                Some("number") => from_tokens(it),
                Some(token) => {
                    let maybe_inner = from_tokens(it)?;
                    match token {
                        "list" => Ok(Some(ValueType::List(Box::from(opt_to_err(maybe_inner)?)))),
                        "map" => Ok(Some(ValueType::Map(Box::from(opt_to_err(maybe_inner)?)))),
                        "optional" => Ok(Some(ValueType::Optional(Box::from(opt_to_err(
                            maybe_inner,
                        )?)))),
                        "primitive" => Ok(Some(ValueType::Primitive(
                            opt_to_err(maybe_inner)?
                                .to_primitive_type()
                                .ok_or(ParseError::VariantNotFound)?,
                        ))),
                        "text" => Ok(Some(ValueType::Text)),
                        x => Ok(Some(ValueType::Primitive(PrimitiveType::from_str(x)?))),
                    }
                }
                None => Ok(None),
            }
        }

        match from_tokens(s.split(':')) {
            Ok(Some(value_type)) => Ok(value_type),
            Ok(None) => Err(ParseError::VariantNotFound),
            Err(x) => Err(x),
        }
    }
}

impl std::fmt::Display for ValueType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::List(t) => write!(f, "list:{}", t),
            Self::Map(t) => write!(f, "map:{}", t),
            Self::Optional(t) => write!(f, "optional:{}", t),
            Self::Primitive(t) => write!(f, "{}", t),
            Self::Text => write!(f, "text"),
            Self::Custom => write!(f, "custom"),
        }
    }
}

impl From<Value> for ValueType {
    fn from(value: Value) -> Self {
        Self::from(&value)
    }
}

impl<'a> From<&'a Value> for ValueType {
    /// Produces the type of the referenced value by recursively iterating
    /// through complex types, assuming that the first value in types like
    /// list represent the entire set, defaulting to a primitive unit if
    /// a complex value does not have any items
    fn from(v: &'a Value) -> Self {
        match v {
            Value::List(x) => Self::List(Box::from(
                x.iter().next().map(ValueType::from).unwrap_or_default(),
            )),
            Value::Map(x) => Self::Map(Box::from(
                x.values().next().map(ValueType::from).unwrap_or_default(),
            )),
            Value::Optional(x) => Self::Optional(Box::from(
                x.as_ref()
                    .map(Box::as_ref)
                    .map(ValueType::from)
                    .unwrap_or_default(),
            )),
            Value::Primitive(x) => Self::Primitive(PrimitiveType::from(x)),
            Value::Text(_) => Self::Text,
        }
    }
}

impl From<PrimitiveType> for ValueType {
    /// Converts primitive value type to a value type
    fn from(t: PrimitiveType) -> Self {
        Self::Primitive(t)
    }
}

impl From<NumberType> for ValueType {
    /// Converts number type (subclass of primitive type) to a value type
    fn from(t: NumberType) -> Self {
        Self::Primitive(PrimitiveType::Number(t))
    }
}