tiny-orm-macros 0.5.1

Macros for Tiny ORM. Not intended to be used directly.
Documentation
use convert_case::{Case, Casing};
use quote::{format_ident, ToTokens};
use regex::Regex;
use std::sync::LazyLock;
use std::{fmt, str::FromStr};
use syn::{Ident, Type};

static FIND_SET_OPTION_REGEX: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"^(?:tiny_orm\s*::\s*)*SetOption\s*<").unwrap());

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum StructType {
    Create,
    Update,
    Generic,
}
impl StructType {
    pub fn default_operation(&self) -> Operations {
        match self {
            StructType::Create => vec![Operation::Create],
            StructType::Update => vec![Operation::Update],
            StructType::Generic => vec![Operation::Get, Operation::List, Operation::Delete],
        }
    }
    pub fn remove_prefix(&self, input: &str) -> String {
        match self {
            StructType::Create => input.replace("New", ""),
            StructType::Update => input.replace("Update", ""),
            StructType::Generic => input.to_string(),
        }
    }
}
impl From<&str> for StructType {
    fn from(input: &str) -> StructType {
        if input.starts_with("New") {
            StructType::Create
        } else if input.starts_with("Update") {
            StructType::Update
        } else {
            StructType::Generic
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedStruct {
    pub name: StructName,
    pub table_name: TableName,
    pub struct_type: StructType,
    pub return_object: ReturnObject,
}
impl ParsedStruct {
    pub fn new(
        struct_name: &Ident,
        table_name: Option<String>,
        return_object: Option<ReturnObject>,
    ) -> Self {
        let name = struct_name.to_string();
        let struct_type = StructType::from(name.as_str());

        let table_name = match table_name {
            Some(value) => value,
            None => struct_type.remove_prefix(&name),
        };

        let return_object = match (return_object, &struct_type) {
            (Some(value), _) => value,
            (None, &StructType::Generic) => format_ident!("Self"),
            (None, _) => format_ident!("{}", struct_type.remove_prefix(&name)),
        };

        Self {
            name: struct_name.clone(),
            table_name: TableName::new(&table_name),
            struct_type,
            return_object,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Operation {
    Get,
    List,
    Create,
    Update,
    Delete,
}

impl Operation {
    pub fn all() -> Vec<Operation> {
        vec![
            Operation::Get,
            Operation::List,
            Operation::Create,
            Operation::Update,
            Operation::Delete,
        ]
    }
}

impl FromStr for Operation {
    type Err = ();

    fn from_str(input: &str) -> Result<Operation, Self::Err> {
        match input {
            "get" => Ok(Operation::Get),
            "list" => Ok(Operation::List),
            "create" => Ok(Operation::Create),
            "update" => Ok(Operation::Update),
            "delete" => Ok(Operation::Delete),
            _ => Err(()),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Column {
    pub name: String,
    pub ident: Ident,
    pub _type: Type,
    pub auto_increment: bool,
    pub primary_key: bool,
}
impl Column {
    pub fn new(name: &str, _type: Type) -> Self {
        Self {
            name: name.to_string(),
            ident: format_ident!("{}", name),
            _type,
            auto_increment: false,
            primary_key: false,
        }
    }
    pub fn set_auto_increment(&mut self) {
        self.auto_increment = true;
    }
    pub fn set_primary_key(&mut self) {
        self.primary_key = true;
    }
    pub fn use_set_options(&self) -> bool {
        FIND_SET_OPTION_REGEX.is_match(&self._type.to_token_stream().to_string())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TableName(pub String);
impl TableName {
    pub fn new(input: &str) -> Self {
        Self(input.to_case(Case::Snake))
    }
}

impl fmt::Display for TableName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

pub type StructName = Ident;
pub type PrimaryKey = Column;
pub type ReturnObject = Ident;
pub type Operations = Vec<Operation>;

#[cfg(test)]
mod tests {
    use syn::parse_quote;

    use super::*;

    mod column {
        use super::*;

        #[test]
        fn test_set_auto_increment() {
            let mut column = Column::new("col_name", parse_quote!(i32));
            assert!(!column.auto_increment);
            column.set_auto_increment();
            assert!(column.auto_increment);
        }

        #[test]
        fn test_set_primary_key() {
            let mut column = Column::new("col_name", parse_quote!(i32));
            assert!(!column.primary_key);
            column.set_primary_key();
            assert!(column.primary_key);
        }

        #[test]
        fn test_use_set_options_true() {
            let col_name = "col_name";
            assert!(
                Column::new(col_name, parse_quote!(tiny_orm::SetOption<i32>)).use_set_options()
            );
            assert!(Column::new(col_name, parse_quote!(SetOption<String>)).use_set_options());
            assert!(Column::new(col_name, parse_quote!(SetOption<!>)).use_set_options());
            assert!(Column::new(col_name, parse_quote!(SetOption<Option<bool>>)).use_set_options());
        }
        #[test]
        fn test_use_set_options_false() {
            let col_name = "col_name";
            assert!(!Column::new(col_name, parse_quote!(Option<i32>)).use_set_options());
            assert!(!Column::new(col_name, parse_quote!(String)).use_set_options());
            assert!(!Column::new(col_name, parse_quote!(!)).use_set_options());
            assert!(!Column::new(col_name, parse_quote!(bool)).use_set_options());
            assert!(!Column::new(col_name, parse_quote!(MyStruct<SetOption>)).use_set_options());
            assert!(!Column::new(col_name, parse_quote!(Option<SetOption>)).use_set_options());
            assert!(
                !Column::new(col_name, parse_quote!(Option<SetOption<bool>>)).use_set_options()
            );
        }
    }
}