satex-core 0.4.1

Satex Core Library
Documentation
use crate::Error;
use regex::Regex;
use serde::de::Error as DeError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter, Write};
use std::str::FromStr;
use std::sync::Arc;

#[derive(Debug, Clone)]
pub enum Expression {
    Equals(Value),
    NotEquals(Value),
    StartsWith(Value),
    NotStartsWith(Value),
    EndsWith(Value),
    NotEndsWith(Value),
    Contains(Value),
    NotContains(Value),
    Exists,
    NotExists,
    Regex(Regex),
}

impl Expression {
    pub fn equals(value: impl AsRef<str>, sensitive: bool) -> Self {
        Expression::Equals(Value::new(value, sensitive))
    }

    pub fn not_equals(value: impl AsRef<str>, sensitive: bool) -> Self {
        Expression::NotEquals(Value::new(value, sensitive))
    }

    pub fn starts_with(value: impl AsRef<str>, sensitive: bool) -> Self {
        Expression::StartsWith(Value::new(value, sensitive))
    }

    pub fn not_starts_with(value: impl AsRef<str>, sensitive: bool) -> Self {
        Expression::NotStartsWith(Value::new(value, sensitive))
    }

    pub fn ends_with(value: impl AsRef<str>, sensitive: bool) -> Self {
        Expression::EndsWith(Value::new(value, sensitive))
    }

    pub fn not_ends_with(value: impl AsRef<str>, sensitive: bool) -> Self {
        Expression::NotEndsWith(Value::new(value, sensitive))
    }

    pub fn contains(value: impl AsRef<str>, sensitive: bool) -> Self {
        Expression::Contains(Value::new(value, sensitive))
    }

    pub fn not_contains(value: impl AsRef<str>, sensitive: bool) -> Self {
        Expression::NotContains(Value::new(value, sensitive))
    }

    pub fn regex(value: impl AsRef<str>) -> Result<Self, Error> {
        Regex::from_str(value.as_ref())
            .map(Expression::Regex)
            .map_err(Error::new)
    }

    pub fn exists() -> Self {
        Self::Exists
    }

    pub fn not_exists() -> Self {
        Self::NotExists
    }

    pub fn matches(&self, text: Option<&str>) -> bool {
        match self {
            Expression::Equals(value) => value.equals(text),
            Expression::NotEquals(value) => value.not_equals(text),
            Expression::StartsWith(value) => value.starts_with(text),
            Expression::NotStartsWith(value) => value.not_starts_with(text),
            Expression::EndsWith(value) => value.ends_with(text),
            Expression::NotEndsWith(value) => value.not_ends_with(text),
            Expression::Contains(value) => value.contains(text),
            Expression::NotContains(value) => value.not_contains(text),
            Expression::Exists => text.is_some(),
            Expression::NotExists => text.is_none(),
            Expression::Regex(regex) => match text {
                Some(text) => regex.is_match(text),
                None => false,
            },
        }
    }
}

impl Display for Expression {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Expression::Equals(value) => {
                f.write_str("Equals")?;
                <Value as Display>::fmt(value, f)
            }
            Expression::NotEquals(value) => {
                f.write_str("NotEquals")?;
                <Value as Display>::fmt(value, f)
            }
            Expression::StartsWith(value) => {
                f.write_str("StartsWith")?;
                <Value as Display>::fmt(value, f)
            }
            Expression::NotStartsWith(value) => {
                f.write_str("NotStartWith")?;
                <Value as Display>::fmt(value, f)
            }
            Expression::EndsWith(value) => {
                f.write_str("EndsWith")?;
                <Value as Display>::fmt(value, f)
            }
            Expression::NotEndsWith(value) => {
                f.write_str("NotEndsWith")?;
                <Value as Display>::fmt(value, f)
            }
            Expression::Contains(value) => {
                f.write_str("Contains")?;
                <Value as Display>::fmt(value, f)
            }
            Expression::NotContains(value) => {
                f.write_str("NotContains")?;
                <Value as Display>::fmt(value, f)
            }
            Expression::Exists => f.write_str("Exists"),
            Expression::NotExists => f.write_str("NotExists"),
            Expression::Regex(regex) => f.write_str(regex.as_str()),
        }
    }
}

impl FromStr for Expression {
    type Err = Error;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        let value = value.trim();
        match value {
            "Exists" => Ok(Expression::Exists),
            "NotExists" => Ok(Expression::NotExists),
            _ => match value.find('(').zip(value.rfind(')')) {
                Some((open, close)) => {
                    let r#type = value[..open].trim();
                    let value = &value[open + 1..close];
                    match r#type {
                        "Equals" => Ok(Expression::equals(value, true)),
                        "?Equals" => Ok(Expression::equals(value, false)),
                        "NotEquals" => Ok(Expression::not_equals(value, true)),
                        "?NotEquals" => Ok(Expression::not_equals(value, false)),
                        "StartsWith" => Ok(Expression::starts_with(value, true)),
                        "?StartsWith" => Ok(Expression::starts_with(value, false)),
                        "NotStartsWith" => Ok(Expression::not_starts_with(value, true)),
                        "?NotStartsWith" => Ok(Expression::not_starts_with(value, false)),
                        "EndsWith" => Ok(Expression::ends_with(value, true)),
                        "?EndsWith" => Ok(Expression::ends_with(value, false)),
                        "NotEndsWith" => Ok(Expression::not_ends_with(value, true)),
                        "?NotEndsWith" => Ok(Expression::not_ends_with(value, false)),
                        "Contains" => Ok(Expression::contains(value, true)),
                        "?Contains" => Ok(Expression::contains(value, false)),
                        "NotContains" => Ok(Expression::not_contains(value, true)),
                        "?NotContains" => Ok(Expression::not_contains(value, false)),
                        "Regex" => Expression::regex(value),
                        _ => Err(Error::new(format!("invalid expression type: {}", r#type))),
                    }
                }
                None => Err(Error::new(format!("invalid expression: {}", value))),
            },
        }
    }
}

impl<'de> Deserialize<'de> for Expression {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let text = String::deserialize(deserializer)?;
        Expression::from_str(&text).map_err(D::Error::custom)
    }
}

impl Serialize for Expression {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.to_string().serialize(serializer)
    }
}

#[derive(Debug, Clone)]
pub struct Value {
    raw: Arc<str>,
    sensitive: bool,
}

impl Display for Value {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let (value, enabled) = match self.sensitive {
            true => (self.raw.as_ref(), true),
            false => (self.raw.as_ref(), false),
        };
        f.write_char('(')?;
        f.write_str(value)?;
        f.write_char(',')?;
        f.write_str(if enabled { "true" } else { "false" })?;
        f.write_char(')')
    }
}

impl Value {
    pub fn new(value: impl AsRef<str>, sensitive: bool) -> Self {
        let raw = if sensitive {
            Arc::from(value.as_ref())
        } else {
            Arc::from(value.as_ref().to_lowercase())
        };
        Self { raw, sensitive }
    }

    pub fn equals(&self, value: Option<&str>) -> bool {
        match value {
            Some(value) => {
                if self.sensitive {
                    self.raw.as_ref() == value
                } else {
                    self.raw.as_ref().eq_ignore_ascii_case(value)
                }
            }
            None => false,
        }
    }

    pub fn not_equals(&self, value: Option<&str>) -> bool {
        !self.equals(value)
    }

    pub fn starts_with(&self, value: Option<&str>) -> bool {
        match value {
            Some(value) => {
                if self.sensitive {
                    value.starts_with(self.raw.as_ref())
                } else {
                    value.to_lowercase().starts_with(self.raw.as_ref())
                }
            }
            None => false,
        }
    }

    pub fn not_starts_with(&self, value: Option<&str>) -> bool {
        !self.starts_with(value)
    }

    pub fn ends_with(&self, value: Option<&str>) -> bool {
        match value {
            Some(value) => {
                if self.sensitive {
                    value.ends_with(self.raw.as_ref())
                } else {
                    value.to_lowercase().ends_with(self.raw.as_ref())
                }
            }
            None => false,
        }
    }

    pub fn not_ends_with(&self, value: Option<&str>) -> bool {
        !self.ends_with(value)
    }

    pub fn contains(&self, value: Option<&str>) -> bool {
        match value {
            Some(value) => {
                if self.sensitive {
                    value.contains(self.raw.as_ref())
                } else {
                    value.to_lowercase().contains(self.raw.as_ref())
                }
            }
            None => false,
        }
    }

    pub fn not_contains(&self, value: Option<&str>) -> bool {
        !self.contains(value)
    }
}