reactive_graph_type_system_derive/
lib.rs

1#[macro_use]
2extern crate darling;
3extern crate proc_macro;
4
5use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7
8use quote::format_ident;
9use quote::quote;
10use syn::DeriveInput;
11use syn::Ident;
12use syn::parse_macro_input;
13
14use crate::darling::FromDeriveInput;
15
16#[derive(FromDeriveInput)]
17#[darling(attributes(type_provider))]
18struct TypeProviderConfig {
19    tys: syn::Type,
20    path: String,
21    component_alias: Option<bool>,
22}
23
24uses_type_params!(TypeProviderConfig, tys);
25
26#[proc_macro_derive(TypeProvider, attributes(type_provider))]
27pub fn type_provider(input: TokenStream) -> TokenStream {
28    let input: DeriveInput = parse_macro_input!(input);
29
30    let ident: Ident = input.ident.clone();
31    let ident_assets = format_ident!("{}Assets", ident);
32
33    let type_provider_config = match TypeProviderConfig::from_derive_input(&input) {
34        Ok(type_provider_config) => type_provider_config,
35        Err(e) => {
36            return e.write_errors().into();
37        }
38    };
39    let tys = type_provider_config.tys;
40    let path = type_provider_config.path;
41    let type_provider_id = ident.to_string();
42
43    let component_alias = if type_provider_config.component_alias.unwrap_or(true) {
44        quote! {
45            #[reactive_graph_type_system_api::springtime_di::component_alias]
46        }
47    } else {
48        TokenStream2::new()
49    };
50
51    #[cfg(feature = "json")]
52    let json = {
53        quote! {
54            match reactive_graph_type_system_api::serde_json::from_str(asset_str) {
55                Ok(parsed_entry) => {
56                    let entry: <#tys as reactive_graph_graph::NamespacedTypeContainer>::Type = parsed_entry;
57                    reactive_graph_graph::NamespacedTypeContainer::push(&entries, entry);
58                }
59                Err(e) => log::error!("Error in parsing JSON file {filename}: {e}"),
60            }
61        }
62    };
63    #[cfg(not(feature = "json"))]
64    let json = {
65        quote! {
66            log::error!("Failed to read type definition from {filename}: JSON is not a supported file format!");
67        }
68    };
69
70    #[cfg(feature = "json5")]
71    let json5 = {
72        quote! {
73            match reactive_graph_type_system_api::json5::from_str(asset_str) {
74                Ok(parsed_entry) => {
75                    let entry: <#tys as reactive_graph_graph::NamespacedTypeContainer>::Type = parsed_entry;
76                    reactive_graph_graph::NamespacedTypeContainer::push(&entries, entry);
77                }
78                Err(e) => log::error!("Error in parsing JSON5 file {filename}: {e}"),
79            }
80        }
81    };
82    #[cfg(not(feature = "json5"))]
83    let json5 = {
84        quote! {
85            log::error!("Failed to read type definition from {filename}: JSON5 is not a supported file format!");
86        }
87    };
88
89    #[cfg(feature = "toml")]
90    let toml = {
91        quote! {
92            match reactive_graph_type_system_api::toml::from_str(asset_str) {
93                Ok(parsed_entry) => {
94                    let entry: <#tys as reactive_graph_graph::NamespacedTypeContainer>::Type = parsed_entry;
95                    reactive_graph_graph::NamespacedTypeContainer::push(&entries, entry);
96                }
97                Err(e) => log::error!("Error in parsing TOML file {filename}: {e}"),
98            }
99        }
100    };
101    #[cfg(not(feature = "toml"))]
102    let toml = {
103        quote! {
104            log::error!("Failed to read type definition from {filename}: TOML is not a supported file format!");
105        }
106    };
107
108    let expanded = quote! {
109        #[derive(rust_embed::RustEmbed)]
110        #[folder = #path]
111        struct #ident_assets;
112
113        #[automatically_derived]
114        #component_alias
115        impl reactive_graph_type_system_api::TypeProvider<#tys> for #ident {
116            fn id<'a>(&self) -> &'a str {
117                #type_provider_id
118            }
119            fn get_types(&self) -> #tys {
120                let mut entries = <#tys as reactive_graph_graph::NamespacedTypeContainer>::new();
121                for file in #ident_assets::iter() {
122                    let filename = file.as_ref();
123                    if filename.starts_with(".") {
124                        // do nothing
125                        continue;
126                    }
127                    log::debug!("Loading resource {}", filename);
128                    match #ident_assets::get(filename) {
129                        Some(asset) => match std::str::from_utf8(asset.data.as_ref()) {
130                            Ok(asset_str) => {
131                                if filename.ends_with(".json") {
132                                    #json
133                                } else if filename.ends_with(".json5") {
134                                    #json5
135                                } else if filename.ends_with(".toml") {
136                                    #toml
137                                } else {
138                                    log::error!("Can't read type definition {}: Only JSON, JSON5 and TOML are supported.", filename);
139                                }
140                            }
141                            Err(e) => log::error!("Error in decoding file to UTF-8 {}: {}", filename, e),
142                        },
143                        None => {}
144                    }
145                }
146                entries
147            }
148        }
149    };
150    TokenStream::from(expanded)
151}