kvapi_macros_internals/api/
node.rs

1use super::headers::Headers;
2use convert_case::{Case, Casing};
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote};
5use std::collections::HashSet;
6use syn::Ident;
7
8/// Nodes are the segments of the key in `"key": EndpointType` in a Dict Entry
9///
10/// e.g.,
11///     "path/to/endpoint": MyType,
12///
13/// path: Path                              <- root
14///         |_ to: To                       <- child of `path`
15///                 |_ endpoint: Endpoint   <- leaf & child of `to`
16///
17///                    impl Endpoint {
18///                         async fn get() -> Result<MyType> { ... }
19///                    }
20///
21/// The struct build of this would result in:
22/// `SomeApiName.path.to.endpoint.get()`
23///
24/// note 1:  `endpoint`, being a leaf node, gets access to the `get()` function.
25/// note 2:  each struct will have a `new()` impl.
26#[derive(Clone, Debug)]
27pub struct Node {
28    pub root: bool,                   // add to the Root fields
29    pub de_type: Option<TokenStream>, // type of the `get()` result; if none, no `get()` needed
30    pub children: HashSet<String>,    // determines `new()` tokens
31    pub endpoint: Option<TokenStream>, // if leaf node, remember the original endpoint for `url()`
32                                      // (and any additional query)
33}
34
35impl Node {
36    // used in in building the dictionary
37    pub(crate) fn new() -> Self {
38        Self {
39            root: false,
40            de_type: None,
41            children: HashSet::new(),
42            endpoint: None,
43        }
44    }
45
46    // remake children nodes in field TokenStream
47    pub(crate) fn children_fields(&self, api_name: Ident) -> Vec<TokenStream> {
48        self.children
49            .iter()
50            .map(|child| {
51                let (snake, pascal) = (
52                    format_ident!("{}", child.to_case(Case::Snake)),
53                    format_ident!("{}{}", api_name, child.to_case(Case::Pascal)),
54                );
55                quote! { #snake: #pascal }
56            })
57            .collect()
58    }
59
60    // check if the node is an HTTP node
61    pub(crate) fn is_http(&self) -> bool {
62        if self.de_type.is_some() {
63            true
64        } else {
65            false
66        }
67    }
68
69    // build the url, combining with a base URl, if there is one
70    pub(crate) fn build_url(&self, base: Option<TokenStream>) -> TokenStream {
71        let url = self.endpoint.as_ref().unwrap();
72        if let Some(base) = base {
73            quote! {
74                #url
75                let base = #base;
76                let url = format!("{}{}", base, url);
77            }
78        } else {
79            url.clone()
80        }
81    }
82
83    // build the HTTP functions
84    pub(crate) fn build_http(&self, url: TokenStream, headers: Option<Headers>) -> TokenStream {
85        let de_type = self.de_type.clone().unwrap();
86        let headers = headers.unwrap_or(Headers {
87            client: vec![],
88            query: vec![],
89        });
90        let client_headers = headers.client;
91        let query_headers = headers.query;
92
93        let http_methods = quote! {
94            fn build_client() -> kvapi::Result<kvapi::Client> {
95                let mut headers = kvapi::HeaderMap::new();
96                #( #client_headers )*
97                let client = kvapi::ClientBuilder::new()
98                    .default_headers(headers)
99                    .build()?;
100                Ok(client)
101            }
102
103            fn build_url() -> String {
104                #url
105                url
106            }
107
108            fn url(&self) -> &String {
109                &self.url
110            }
111
112            fn client(&self) -> &kvapi::Client {
113                &self.client
114            }
115
116            pub async fn get(&self) -> kvapi::Result<#de_type> {
117                let response: #de_type = self
118                    .client()
119                    .get(self.url())
120                    #( #query_headers )*
121                    .send()
122                    .await?
123                    .json()
124                    .await?;
125                Ok(response)
126            }
127
128            pub async fn post(&self, json: kvapi::Value) -> kvapi::Result<#de_type> {
129                let response: #de_type = self
130                    .client()
131                    .post(self.url())
132                    .json(&json)
133                    .send()
134                    .await?
135                    .json()
136                    .await?;
137                Ok(response)
138            }
139        };
140
141        http_methods
142    }
143
144    // check if the node is a root node
145    pub(crate) fn is_root(&self) -> bool {
146        self.root
147    }
148}