kenzu 0.1.3

Procedural macros for builder pattern generation and validation
Documentation
use super::prelude::*;

pub fn generate_method(input: &DeriveInput, field: &Field) -> TokenStream {
    let field_ident: &Ident = field.ident.as_ref().expect("field name must be set");
    let Opt {
        name,
        err,
        pattern,
        default,
        err_max,
        err_min,
        max,
        min,
    } = get_opt(&field.attrs);
    let ty = &field.ty;
    let struct_name = get_struct_name(input);
    let type_name_ts: TokenStream = get_type_name_ts(&struct_name, field_ident, name);

    if is_string(ty) || is_str_ref(ty) {
        let re_ident = Ident::new(
            &format!("RE_{}_{}", struct_name, field_ident),
            Span::call_site(),
        );
        let get_re_fn = Ident::new(
            &format!("get_re_{}_{}", struct_name, field_ident),
            Span::call_site(),
        );
        let pattern = pattern.unwrap_or(".*".to_string());
        let err = err.unwrap_or(format!("Regex pattern: {pattern} invalid {}", type_name_ts));
        let default_expr_tokens = if let Some(d) = default {
            quote! { #d }
        } else {
            let struct_ident = struct_name;
            quote! { #struct_ident::default().#field_ident }
        };
        quote! {
            static #re_ident: ::std::sync::OnceLock<::regex::Regex> = ::std::sync::OnceLock::new();
                fn #get_re_fn() -> &'static ::regex::Regex {
                    #re_ident.get_or_init(|| {
                        ::regex::Regex::new(#pattern)
                            .expect("invalid regex literal")
                    })
                }

            impl ::std::str::FromStr for #type_name_ts {
                 type Err = String;
                fn from_str(s: &str) -> Result<Self, Self::Err> {
                    if #get_re_fn().is_match(s) {
                        Ok(#type_name_ts(s.to_string()))
                    } else {
                        Err(#err.to_string())
                    }
                }
            }

            impl #type_name_ts {
                pub fn default() -> Self {
                    let s: &str = &#default_expr_tokens;
                    ::std::str::FromStr::from_str(s)
                        .expect("invalid default value for field")
                }
                pub fn new<S: AsRef<str>>(s: S) -> Result<Self, String> {
                    ::std::str::FromStr::from_str(s.as_ref())
                }
            }

        }
    } else if is_numeric(ty) {
        let err_msg = err.unwrap_or(format!("Invalid number for {}", type_name_ts));
        let err_min_msg =
            err_min.unwrap_or(format!("value for {} is less than minimum", type_name_ts));
        let err_max_msg = err_max.unwrap_or(format!(
            "value for {} is greater than maximum",
            type_name_ts
        ));
        let default_expr_tokens = if let Some(d) = default {
            quote! { #d }
        } else {
            let struct_ident = struct_name;
            quote! { #struct_ident::default().#field_ident }
        };
        let min_check = if let Some(min_ts) = &min {
            quote! {
                if v < (#min_ts) {
                    return Err(#err_min_msg.to_string());
                }
            }
        } else {
            quote! {}
        };

        let max_check = if let Some(max_ts) = &max {
            quote! {
                if v > (#max_ts) {
                    return Err(#err_max_msg.to_string());
                }
            }
        } else {
            quote! {}
        };

        let default_min_check = if let Some(min_ts) = &min {
            quote! {
                if v < (#min_ts) {
                    panic!("{}: {}", #err_min_msg, #min_ts);
                }
            }
        } else {
            quote! {}
        };

        let default_max_check = if let Some(max_ts) = &max {
            quote! {
                if v > (#max_ts) {
                    panic!("{}: {}", #err_max_msg, #max_ts);
                }
            }
        } else {
            quote! {}
        };

        quote! {
            impl ::std::str::FromStr for #type_name_ts {
                type Err = String;
                fn from_str(s: &str) -> Result<Self, Self::Err> {
                    let v: #ty = s.parse().map_err(|e| format!("{}({})", #err_msg, e))?;
                    #min_check
                    #max_check
                    Ok(#type_name_ts(v))
                }
            }

            impl #type_name_ts {
                pub fn default() -> Self {
                    let v: #ty = #default_expr_tokens;
                    #default_min_check
                    #default_max_check
                    #type_name_ts(v)
                }

                pub fn new(v: #ty) -> Result<Self, String> {
                    #min_check
                    #max_check
                    Ok(#type_name_ts(v))
                }
            }
        }
    } else {
        let default_expr_tokens = if let Some(d) = default {
            quote! { #d }
        } else {
            let struct_ident = struct_name;
            quote! { #struct_ident::default().#field_ident }
        };
        quote! {
           impl #type_name_ts {
                pub fn default() -> Self {
                    let v: #ty = #default_expr_tokens;
                    #type_name_ts(v)
                }

                pub fn new(v: #ty) -> Result<Self, String> {
                    Ok(#type_name_ts(v))
                }
            }
        }
    }
}