serialize_to_javascript_impl/
lib.rs1extern 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
9fn 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#[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 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#[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}