serialize_to_javascript_impl/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4
5use proc_macro2::TokenStream as TokenStream2;
6use quote::{quote, TokenStreamExt};
7use syn::{parse_macro_input, spanned::Spanned};
8
9/// Checks if the passed type implements the passed trait.
10fn trait_check<'l, L>(lifetimes: L, type_: syn::Type, trait_: TokenStream2) -> TokenStream2
11where
12    L: Iterator<Item = &'l syn::LifetimeParam>,
13{
14    quote!(
15      const _: fn() = || {
16        fn declare_lifetime<#(#lifetimes),*>() {
17          fn assert_impl_all<T: ?Sized + #trait_>() {}
18          assert_impl_all::<#type_>();
19        }
20      };
21    )
22}
23
24/// Automatically derive `Template` from a struct with valid input fields.
25///
26/// ```no_run,no_compile
27/// #[derive(Template)]
28/// struct MyTemplate {
29///     serializable_field: usize,
30///
31///     #[raw]
32///     raw_field: &'static str
33/// }
34/// ```
35#[proc_macro_derive(Template, attributes(raw))]
36pub fn derive_template(item: TokenStream) -> TokenStream {
37    let item = parse_macro_input!(item as syn::DeriveInput);
38    let item_span = item.span();
39    let name = item.ident;
40    match item.data {
41        syn::Data::Struct(data) => {
42            let (impl_generics, ty_generics, _) = item.generics.split_for_impl();
43            let mut replacements = TokenStream2::new();
44            for field in data.fields {
45                match field.ident {
46                    Some(ident) => {
47                        let templated_field_name;
48                        let lifetimes = item.generics.lifetimes();
49
50                        // we expect self, template, and options bindings to exist
51                        let data = if field.attrs.iter().any(|attr| attr.path().is_ident("raw")) {
52                            templated_field_name = format!("__RAW_{}__", ident);
53                            let trait_check = trait_check(lifetimes,field.ty, quote!(::std::fmt::Display));
54                            quote!(
55                                #trait_check
56                                let data: String = self.#ident.to_string();
57                            )
58                        } else {
59                            templated_field_name = format!("__TEMPLATE_{}__", ident);
60                            let trait_check = trait_check(lifetimes, field.ty, quote!(::serialize_to_javascript::private::Serialize));
61                            quote!(
62                                #trait_check
63
64                                use ::std::convert::TryInto;
65                                use ::serialize_to_javascript::{
66                                    private::{NotYetSerialized, SerializedOnce},
67                                    Serialized
68                                };
69
70                                let data: SerializedOnce = NotYetSerialized(&self.#ident).try_into()?;
71                                let data: Serialized = data.into_javascript_string_literal(options);
72                                let data: String = data.into_string();
73                            )
74                        };
75
76                        replacements.append_all(quote!(
77                            let template = {
78                                #data
79                                template.replace(
80                                    #templated_field_name,
81                                    &data
82                                )
83                            };
84                        ));
85                    }
86                    None => {
87                        return syn::Error::new(
88                            field.span(),
89                            "Template fields must all have names",
90                        )
91                            .to_compile_error()
92                            .into();
93                    }
94                }
95            }
96            quote!(
97                impl #impl_generics ::serialize_to_javascript::private::Sealed for #name #ty_generics {}
98                impl #impl_generics ::serialize_to_javascript::Template for #name #ty_generics {
99                    fn render(&self, template: &str, options: &::serialize_to_javascript::Options) -> ::serialize_to_javascript::Result<::serialize_to_javascript::Serialized> {
100                        #replacements
101                        Ok(unsafe {
102                            ::serialize_to_javascript::Serialized::from_string_unchecked(template.into())
103                        })
104                    }
105                }
106            )
107        }
108        _ => {
109            return syn::Error::new(
110                item_span,
111                "`Template` currently only supports data structs",
112            )
113                .to_compile_error()
114                .into();
115        }
116    }
117        .into()
118}
119
120/// Automatically derive `DefaultTemplate` for a `Template` from the passed path.
121///
122/// ```no_run,no_compile
123/// #[default_template("path/to/my_javascript_file.js")]
124/// ```
125#[proc_macro_attribute]
126pub fn default_template(attr: TokenStream, item: TokenStream) -> TokenStream {
127    let path = parse_macro_input!(attr as syn::LitStr);
128    let item = parse_macro_input!(item as syn::DeriveInput);
129    let name = item.ident.clone();
130    let (impl_generics, ty_generics, _) = item.generics.split_for_impl();
131    quote!(
132        #item
133        impl #impl_generics ::serialize_to_javascript::DefaultTemplate for #name #ty_generics {
134            const RAW_TEMPLATE: &'static str = include_str!(#path);
135        }
136    )
137    .into()
138}