sqlx-models-proc-macro 0.0.3

A helper crate for `sqlx-models`
pub struct Constraints(pub Vec<Constraint>);
#[derive(Debug)]
pub enum Constraint {
    ForeignKey(ForeignKey),
    Unique(Unique),
    Primary(Unique),
}
#[derive(Debug)]
pub struct NamedConstraint {
    pub name: String,
    pub field_name: Ident,
    pub constr: Constraint,
}
use std::fmt::Debug;

use crate::prelude::*;

#[derive(Default, Debug)]
pub struct Unique {
    columns: Vec<Ident>,
}
impl ForeignKey {
    fn into_tokens(&self, constr_name: &str, local_col: &Ident) -> TokenStream2 {
        let foreign_col = &self.column;
        let foreign_table = &self.foreign_table.get_ident();

        let on_update = self
            .on_update
            .clone()
            .map(|x| x.value())
            .unwrap_or_default();
        let on_delete = self
            .on_delete
            .clone()
            .map(|x| x.value())
            .unwrap_or_default();
        quote! {
            __sqlx_models_table.constraints.push(
                ::sqlx_models::private::constraint::foreign_key(
                    #constr_name,
                    stringify!(#local_col),
                    stringify!(#foreign_table),
                    stringify!(#foreign_col),
                    #on_delete,
                    #on_update,
                )
            );
            // Validation
            let _ = |__sqlx_models_validation: #foreign_table| {
                __sqlx_models_validation.#foreign_col;
            };
        }
    }
}

impl Unique {
    fn into_tokens(
        &self,
        constr_name: &str,
        ty: &Ident,
        field_name: &Ident,
        method: TokenStream2,
    ) -> TokenStream2 {
        let columns = self.columns.iter();
        let columns1 = self.columns.iter();

        quote! {
            __sqlx_models_table.constraints.push(
                ::sqlx_models::private::constraint::#method(
                    #constr_name,
                    &[stringify!(#field_name), #(stringify!(#columns)),*]
                )
            );
            let _ = |__sqlx_models_validation: #ty| {
                #(__sqlx_models_validation.#columns1;)*
            };
        }
    }
}

impl NamedConstraint {
    pub fn into_tokens(&self, ty: &Ident) -> TokenStream2 {
        match &self.constr {
            Constraint::ForeignKey(fk) => {
                let constr_name = self.constr_name(ty, &[fk.column.clone()], "foreign");
                fk.into_tokens(&constr_name, &self.field_name)
            }
            Constraint::Primary(pk) => {
                let constr_name = self.constr_name(ty, &pk.columns, "primary");
                pk.into_tokens(&constr_name, ty, &self.field_name, quote!(primary))
            }
            Constraint::Unique(u) => {
                let constr_name = self.constr_name(ty, &u.columns, "unique");
                u.into_tokens(&constr_name, ty, &self.field_name, quote!(unique))
            }
        }
    }

    pub fn constr_name(&self, ty: &Ident, cols: &[impl ToString], method: &str) -> String {
        let mut constr_name = String::new();
        constr_name += &ty.to_string().to_lowercase();
        constr_name += "_";
        constr_name += method;
        constr_name += "_";
        constr_name += &self.field_name.to_string();

        for col in cols.iter() {
            constr_name += "_";

            constr_name += &col.to_string();
        }
        constr_name
    }
}

impl Parse for Unique {
    fn parse(input: parse::ParseStream) -> Result<Self> {
        let mut out = Unique::default();
        let content;
        if input.is_empty() {
        } else {
            let _paren = parenthesized!(content in input);
            while !content.is_empty() {
                out.columns.push(content.parse().unwrap());
                if !content.is_empty() {
                    content.parse::<Token![,]>().unwrap();
                }
            }
        }
        Ok(out)
    }
}

pub struct ForeignKey {
    foreign_table: Path,
    column: Ident,
    on_delete: Option<LitStr>,
    on_update: Option<LitStr>,
}

impl std::fmt::Debug for ForeignKey {
    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Ok(())
    }
}
impl Parse for ForeignKey {
    fn parse(input: parse::ParseStream) -> Result<Self> {
        let content;
        let _paren = parenthesized!(content in input);

        let foreign_table = content.parse::<Path>()?;
        content.parse::<Token![.]>()?;
        let mut on_delete = None;
        let mut on_update = None;
        let column = content.parse::<Ident>()?;
        while content.parse::<Token![,]>().is_ok() {
            let ident: Ident = content.parse()?;
            if ident == "on_delete" {
                content.parse::<Token![=]>()?;
                if on_delete.is_some() {
                    return Err(Error::new(ident.span(), "Expected a single `on_delete`."));
                }
                on_delete = Some(content.parse()?);
            } else if ident == "on_update" {
                content.parse::<Token![=]>()?;
                if on_update.is_some() {
                    return Err(Error::new(ident.span(), "Expected a single `on_update`."));
                }
                on_update = Some(content.parse()?);
            } else {
                return Err(Error::new(
                    ident.span(),
                    "Expected `on_delete` or `on_update`.",
                ));
            }
        }
        Ok(ForeignKey {
            foreign_table,
            column,
            on_delete,
            on_update,
        })
    }
}

impl Constraints {
    pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
        let mut out = vec![];
        for attr in attrs {
            let tokens = attr.tokens.clone().into();
            if attr.path.is_ident("foreign_key") {
                out.push(Constraint::ForeignKey(parse(tokens)?));
            } else if attr.path.is_ident("unique") {
                out.push(Constraint::Unique(parse(tokens)?));
            } else if attr.path.is_ident("primary_key") {
                out.push(Constraint::Primary(parse(tokens)?));
            }
        }
        Ok(Constraints(out))
    }
}

impl Constraint {
    pub fn column_names(&self) -> Vec<Ident> {
        match &self {
            Constraint::Primary(primary) => primary.columns.to_vec(),
            Constraint::ForeignKey(foreign) => vec![foreign.column.clone()],
            Constraint::Unique(unique) => unique.columns.to_vec(),
        }
    }
    pub fn method(&self) -> TokenStream2 {
        match self {
            Constraint::Primary(_) => {
                quote!(primary)
            }
            Constraint::ForeignKey(_) => {
                quote!(foreign_key)
            }
            Constraint::Unique(_) => {
                quote!(unique)
            }
        }
    }
}

#[test]
fn func() {
    let x = "asd";
    println!("{}", quote!(#x));
}