bluejay_typegen_codegen/
input.rs

1use quote::{ToTokens, TokenStreamExt};
2use syn::parse::Parse;
3
4mod kw {
5    syn::custom_keyword!(borrow);
6    syn::custom_keyword!(enums_as_str);
7}
8
9pub enum DocumentInput {
10    Path(syn::LitStr),
11    Dsl {
12        bracket: syn::token::Bracket,
13        contents: proc_macro2::TokenStream,
14    },
15}
16
17impl Parse for DocumentInput {
18    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
19        if input.peek(syn::token::Bracket) {
20            let contents;
21            Ok(Self::Dsl {
22                bracket: syn::bracketed!(contents in input),
23                contents: contents.parse()?,
24            })
25        } else {
26            input.parse().map(Self::Path)
27        }
28    }
29}
30
31impl ToTokens for DocumentInput {
32    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
33        match self {
34            Self::Path(path) => tokens.append(path.token()),
35            DocumentInput::Dsl { bracket, contents } => {
36                bracket.surround(tokens, |tokens| tokens.extend(contents.clone()))
37            }
38        }
39    }
40}
41
42impl DocumentInput {
43    pub(crate) fn read_to_string_and_path(&self) -> syn::Result<(String, Option<String>)> {
44        match self {
45            Self::Path(path) => {
46                Self::read_file(path).map(|contents| (contents, Some(path.value())))
47            }
48            Self::Dsl { contents, .. } => Ok((contents.to_string(), None)),
49        }
50    }
51
52    fn read_file(filename: &syn::LitStr) -> syn::Result<String> {
53        let cargo_manifest_dir =
54            std::env::var("CARGO_MANIFEST_DIR").map_err(|_| syn::Error::new(filename.span(), "Environment variable CARGO_MANIFEST_DIR was not set but is needed to resolve relative paths"))?;
55        let base_path = std::path::PathBuf::from(cargo_manifest_dir);
56
57        let file_path = base_path.join(filename.value());
58
59        std::fs::read_to_string(file_path)
60            .map_err(|err| syn::Error::new(filename.span(), format!("{}", err)))
61    }
62}
63
64pub struct Input {
65    pub(crate) schema: DocumentInput,
66    pub borrow: Option<syn::LitBool>,
67    pub enums_as_str: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]>,
68}
69
70impl Parse for Input {
71    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
72        let schema: DocumentInput = input.parse()?;
73
74        let mut borrow: Option<syn::LitBool> = None;
75        let mut enums_as_str = None;
76
77        while !input.is_empty() {
78            input.parse::<syn::Token![,]>()?;
79            let lookahead = input.lookahead1();
80            if lookahead.peek(kw::borrow) {
81                parse_key_value(input, &mut borrow)?;
82            } else if lookahead.peek(kw::enums_as_str) {
83                parse_key_value_with(input, &mut enums_as_str, |input| {
84                    let content;
85                    syn::bracketed!(content in input);
86                    syn::punctuated::Punctuated::parse_separated_nonempty(&content)
87                })?;
88            } else {
89                return Err(lookahead.error());
90            }
91        }
92
93        let enums_as_str = enums_as_str.unwrap_or_default();
94
95        Ok(Self {
96            schema,
97            borrow,
98            enums_as_str,
99        })
100    }
101}
102
103fn parse_key_value<V: syn::parse::Parse>(
104    input: syn::parse::ParseStream,
105    value: &mut Option<V>,
106) -> syn::Result<()> {
107    parse_key_value_with(input, value, syn::parse::Parse::parse)
108}
109
110pub(crate) fn parse_key_value_with<V>(
111    input: syn::parse::ParseStream,
112    value: &mut Option<V>,
113    parser: fn(syn::parse::ParseStream<'_>) -> syn::Result<V>,
114) -> syn::Result<()> {
115    let key: syn::Ident = input.parse()?;
116
117    if value.is_some() {
118        return Err(syn::Error::new(
119            key.span(),
120            format!("Duplicate entry for `{}`", key),
121        ));
122    }
123
124    input.parse::<syn::Token![=]>()?;
125    *value = Some(parser(input)?);
126    Ok(())
127}