kvapi_macros_internals/api/
headers.rs

1use super::common::Separator;
2use proc_macro2::TokenStream;
3use quote::quote;
4use syn::{
5    braced, bracketed,
6    parse::{Parse, ParseStream},
7    parse_str, Expr, Ident, LitStr, Token,
8};
9
10/// Collection of all headers for a HTTP client.
11///
12/// ```rust
13/// {
14///    "Header Name": "Header Value"
15///    "AnotherHeader": &std::env::var("ENV_HEADER")?
16/// }
17/// ```
18#[derive(Clone, Debug)]
19pub struct Headers {
20    pub client: Vec<TokenStream>,
21    pub query: Vec<TokenStream>,
22}
23
24impl Parse for Headers {
25    fn parse(input: ParseStream) -> syn::Result<Self> {
26        let mut headers: Vec<Header> = vec![];
27        let mut client_headers: Vec<TokenStream> = vec![];
28        let mut query_headers: Vec<TokenStream> = vec![];
29
30        // parse
31        let args;
32        braced!(args in input);
33        while !args.is_empty() {
34            let header: Header = args.parse()?;
35            if !headers.contains(&header) {
36                headers.push(header);
37            }
38            args.parse::<Option<Token![,]>>()?;
39        }
40
41        // transformed directly to final TokenStream output (can just be expanded out easily)
42        let _ = headers
43            .iter()
44            .map(|header| {
45                let key = &header.key;
46                let value = parse_str::<Expr>(&header.value).expect("expected Header");
47
48                // match the header to the correct TokenStream
49                if header.is_query == true {
50                    query_headers.push(quote! {
51                        // url query
52                        .header(#key, #value)
53                    });
54                } else {
55                    client_headers.push(quote! {
56                        // client query
57                        let value = #value;
58                        headers.insert(#key, kvapi::HeaderValue::from_str(value).unwrap());
59                    });
60                }
61            })
62            .collect::<Vec<_>>();
63
64        Ok(Self {
65            client: client_headers,
66            query: query_headers,
67        })
68    }
69}
70
71/// A single header entry for a HTTP client.
72///
73/// "Header Name": "Header Value"
74#[derive(Hash, Eq, PartialEq)] // HashSet used for efficient uniqueness
75pub struct Header {
76    pub key: String,
77    pub value: String, // this includes Exprs (function calls, etc.)
78    pub is_query: bool,
79}
80
81impl Parse for Header {
82    fn parse(input: ParseStream) -> syn::Result<Self> {
83        let mut is_query = false;
84
85        // attr
86        if input.peek(Token![#]) {
87            input.parse::<Token![#]>()?;
88            let attrs;
89            bracketed!(attrs in input);
90            let _ = attrs
91                .parse_terminated(Attr::parse, Token![,])?
92                .into_iter()
93                .map(|attr| match attr.key.to_string().as_str() {
94                    "query" => is_query = true,
95                    "client" => {}
96                    _ => panic!("unexpected header attribute"),
97                })
98                .collect::<Vec<()>>();
99        }
100
101        // header entry
102        let key: String = input.parse::<LitStr>()?.value();
103        input.parse::<Separator>()?;
104        let value: Expr = input.parse()?;
105        let value = quote!( #value ).to_string();
106
107        Ok(Self {
108            key,
109            value,
110            is_query,
111        })
112    }
113}
114
115/// #\[query\]
116/// "Custom-Header": my_function(self.url)
117///
118/// This would signify a header that is dependent on the query, and
119/// so is added at the time of the query being built/sent, rather then
120/// when the client is built.
121pub struct Attr {
122    pub key: Ident,
123}
124
125impl Parse for Attr {
126    fn parse(input: ParseStream) -> syn::Result<Self> {
127        let key = input.parse::<Ident>()?;
128        Ok(Self { key })
129    }
130}