cgp_component_macro_lib/derive_component/
component_spec.rs

1use alloc::format;
2
3use proc_macro2::Span;
4use quote::ToTokens;
5use syn::parse::{Parse, ParseStream};
6use syn::punctuated::Punctuated;
7use syn::token::{Comma, Gt, Lt};
8use syn::{Error, Ident};
9
10use crate::derive_component::entry::Entries;
11
12pub struct ComponentSpec {
13    pub provider_name: Ident,
14    pub context_type: Ident,
15    pub component_name: Ident,
16    pub component_params: Punctuated<Ident, Comma>,
17}
18
19pub struct ComponentNameSpec {
20    pub component_name: Ident,
21    pub component_params: Punctuated<Ident, Comma>,
22}
23
24static VALID_KEYS: [&str; 3] = ["context", "provider", "name"];
25
26impl Parse for ComponentSpec {
27    fn parse(input: ParseStream) -> syn::Result<Self> {
28        let Entries { entries } = input.parse()?;
29
30        for key in entries.keys() {
31            if !VALID_KEYS.iter().any(|valid| valid == key) {
32                return Err(syn::Error::new(
33                    Span::call_site(),
34                    format!(
35                        r#"invalid key in component spec: {key}. the following keys are valid: "context", "provider", "name"."#
36                    ),
37                ));
38            }
39        }
40
41        let context_type: Ident = {
42            let raw_context_type = entries.get("context");
43
44            if let Some(context_type) = raw_context_type {
45                syn::parse2(context_type.to_token_stream())?
46            } else {
47                Ident::new("Context", Span::call_site())
48            }
49        };
50
51        let provider_name: Ident = {
52            let raw_provider_name = entries
53                .get("provider")
54                .ok_or_else(|| Error::new(input.span(), "expect provider name to be given"))?;
55
56            syn::parse2(raw_provider_name.to_token_stream())?
57        };
58
59        let (component_name, component_params) = {
60            let raw_component_name = entries.get("name");
61
62            if let Some(raw_component_name) = raw_component_name {
63                let ComponentNameSpec {
64                    component_name,
65                    component_params,
66                } = syn::parse2(raw_component_name.to_token_stream())?;
67                (component_name, component_params)
68            } else {
69                (
70                    Ident::new(&format!("{}Component", provider_name), provider_name.span()),
71                    Punctuated::default(),
72                )
73            }
74        };
75
76        Ok(ComponentSpec {
77            component_name,
78            provider_name,
79            context_type,
80            component_params,
81        })
82    }
83}
84
85impl Parse for ComponentNameSpec {
86    fn parse(input: ParseStream) -> syn::Result<Self> {
87        let component_name: Ident = input.parse()?;
88
89        let component_params = if input.peek(Lt) {
90            let _: Lt = input.parse()?;
91
92            let component_params: Punctuated<Ident, Comma> =
93                Punctuated::parse_separated_nonempty(input)?;
94
95            let _: Gt = input.parse()?;
96
97            component_params
98        } else {
99            Punctuated::default()
100        };
101
102        Ok(Self {
103            component_name,
104            component_params,
105        })
106    }
107}