Code Monkey home page Code Monkey logo

Comments (1)

djc avatar djc commented on August 23, 2024

In this case, are the values needed by the subject a subset of the values needed by the body? If so, I'd just use one type for the former and a second type for the latter, which includes the former in one of its fields.

At work, we use a procedural macro to duplicate a type for emails that we send with both plaintext and HTML bodies.

Usage:

#[email(template = "contact-email")]
pub struct ContactEmail<'a> {
    pub site_name: &'a str,
    pub sender: &'a str,
    pub message: &'a str,
    pub inbox_url: &'a str,
}

Macro:

#[proc_macro_attribute]
pub fn email(meta: TokenStream, item: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(item as syn::DeriveInput);
    let meta = parse_macro_input!(meta as EmailMeta);

    // Introduce an `'email` lifetime for the reference to outer type.
    // We set up the generics such that all lifetimes used on the item should outlive the
    // `'email` lifetime, which is necessary to make some of the impls below work.

    let mut email_generics = ast.generics.clone();
    let email_lifetime = syn::LifetimeParam::new(syn::Lifetime::new("'email", Span::call_site()));
    for lt in email_generics.lifetimes_mut() {
        lt.bounds.push(email_lifetime.lifetime.clone());
    }
    email_generics
        .params
        .push(syn::GenericParam::Lifetime(email_lifetime));

    // Split the generics for use in impls and type definitions, below.

    let (_, inner_ty_generics, _) = ast.generics.split_for_impl();
    let (impl_generics, ty_generics, where_clause) = email_generics.split_for_impl();

    // Set up some bindings for use in the quote!() call below.

    let visibility = &ast.vis;
    let name = &ast.ident;
    let text_type = Ident::new(&format!("{name}Text"), Span::call_site());
    let text_template = format!("{}.txt", &meta.template);
    let html_type = Ident::new(&format!("{name}Html"), Span::call_site());
    let html_template = format!("{}.html", &meta.template);

    quote!(
        #ast

        impl #impl_generics email::BodyTemplates<'email> for #name #inner_ty_generics #where_clause {
            type Text = #text_type #ty_generics;
            type Html = #html_type #ty_generics;
        }

        #[derive(askama::Template)]
        #[template(path = #text_template)]
        #visibility struct #text_type #ty_generics(&'email #name #inner_ty_generics) #where_clause;

        impl #impl_generics From<&'email #name #inner_ty_generics> for #text_type #ty_generics #where_clause {
            fn from(email: &'email #name #inner_ty_generics) -> Self {
                Self(email)
            }
        }

        impl #impl_generics std::ops::Deref for #text_type #ty_generics {
            type Target = &'email #name #inner_ty_generics;

            fn deref(&self) -> &Self::Target {
                &self.0
            }
        }

        #[derive(askama::Template)]
        #[template(path = #html_template)]
        #visibility struct #html_type #ty_generics(&'email #name #inner_ty_generics) #where_clause;

        impl #impl_generics std::ops::Deref for #html_type #ty_generics  {
            type Target = &'email #name #inner_ty_generics;

            fn deref(&self) -> &Self::Target {
                &self.0
            }
        }

        impl #impl_generics From<&'email #name #inner_ty_generics> for #html_type #ty_generics #where_clause {
            fn from(email: &'email #name #inner_ty_generics) -> Self {
                Self(email)
            }
        }
    ).into()
}

struct EmailMeta {
    template: String,
}

impl Parse for EmailMeta {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        for field in Punctuated::<syn::MetaNameValue, Comma>::parse_terminated(input)? {
            if field.path.is_ident("template") {
                if let syn::Expr::Lit(lit) = &field.value {
                    if let syn::Lit::Str(lit) = &lit.lit {
                        return Ok(Self {
                            template: lit.value(),
                        });
                    }
                }
            }
        }

        panic!("require template key for email macro");
    }
}

from askama.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.