ploidy_codegen_rust/
client.rs

1use itertools::Itertools;
2use ploidy_core::codegen::IntoCode;
3use proc_macro2::TokenStream;
4use quote::{ToTokens, TokenStreamExt, quote};
5
6use super::{
7    graph::CodegenGraph,
8    naming::{CodegenIdent, CodegenIdentUsage},
9};
10
11/// Generates the `client/mod.rs` source file.
12#[derive(Clone, Copy, Debug)]
13pub struct CodegenClientModule<'a> {
14    graph: &'a CodegenGraph<'a>,
15    resources: &'a [&'a str],
16}
17
18impl<'a> CodegenClientModule<'a> {
19    pub fn new(graph: &'a CodegenGraph<'a>, resources: &'a [&'a str]) -> Self {
20        Self { graph, resources }
21    }
22}
23
24impl ToTokens for CodegenClientModule<'_> {
25    fn to_tokens(&self, tokens: &mut TokenStream) {
26        let mods = self
27            .resources
28            .iter()
29            .map(|resource| {
30                let ident = CodegenIdent::new(resource);
31                let mod_name = CodegenIdentUsage::Module(&ident);
32                quote! {
33                    #[cfg(feature = #resource)]
34                    pub mod #mod_name;
35                }
36            })
37            .collect_vec();
38
39        let client_doc = {
40            let info = self.graph.spec().info;
41            format!("API client for {} (version {})", info.title, info.version)
42        };
43
44        tokens.append_all(quote! {
45            #[doc = #client_doc]
46            #[derive(Clone, Debug)]
47            pub struct Client {
48                client: ::reqwest::Client,
49                headers: ::http::HeaderMap,
50                base_url: ::url::Url,
51            }
52
53            impl Client {
54                /// Create a new client.
55                pub fn new(base_url: impl AsRef<str>) -> Result<Self, crate::error::Error> {
56                    Ok(Self::with_reqwest_client(
57                        ::reqwest::Client::new(),
58                        base_url.as_ref().parse()?,
59                    ))
60                }
61
62                pub fn with_reqwest_client(client: ::reqwest::Client, base_url: ::url::Url) -> Self {
63                    Self {
64                        client,
65                        headers: ::http::HeaderMap::new(),
66                        base_url,
67                    }
68                }
69
70                /// Adds a header to each request.
71                pub fn with_header<K, V>(mut self, name: K, value: V) -> Result<Self, crate::error::Error>
72                where
73                    K: TryInto<::http::HeaderName>,
74                    V: TryInto<::http::HeaderValue>,
75                    K::Error: Into<::http::Error>,
76                    V::Error: Into<::http::Error>,
77                {
78                    let name = name
79                        .try_into()
80                        .map_err(|err| crate::error::Error::BadHeaderName(err.into()))?;
81                    let value = value
82                        .try_into()
83                        .map_err(|err| crate::error::Error::BadHeaderValue(name.clone(), err.into()))?;
84                    self.headers.insert(name, value);
85                    Ok(Self {
86                        client: self.client,
87                        headers: self.headers,
88                        base_url: self.base_url,
89                    })
90                }
91
92                /// Adds a sensitive header to each request, like a password or a bearer token.
93                /// Sensitive headers won't appear in `Debug` output, and may be treated specially
94                /// by the underlying HTTP stack.
95                ///
96                /// # Example
97                ///
98                /// ```rust,ignore
99                /// use reqwest::header::AUTHORIZATION;
100                ///
101                /// let client = Client::new("https://api.example.com")?
102                ///     .with_sensitive_header(AUTHORIZATION, "Bearer decafbadcafed00d")?;
103                /// ```
104                pub fn with_sensitive_header<K, V>(self, name: K, value: V) -> Result<Self, crate::error::Error>
105                where
106                    K: TryInto<::http::HeaderName>,
107                    V: TryInto<::http::HeaderValue>,
108                    K::Error: Into<::http::Error>,
109                    V::Error: Into<::http::Error>,
110                {
111                    let name = name
112                        .try_into()
113                        .map_err(|err| crate::error::Error::BadHeaderName(err.into()))?;
114                    let mut value: ::http::HeaderValue = value
115                        .try_into()
116                        .map_err(|err| crate::error::Error::BadHeaderValue(name.clone(), err.into()))?;
117                    value.set_sensitive(true);
118                    self.with_header(name, value)
119                }
120
121                pub fn with_user_agent<V>(self, value: V) -> Result<Self, crate::error::Error>
122                where
123                    V: TryInto<::http::HeaderValue>,
124                    V::Error: Into<::http::Error>,
125                {
126                    self.with_header(::http::header::USER_AGENT, value)
127                }
128            }
129
130            #(#mods)*
131        });
132    }
133}
134
135impl IntoCode for CodegenClientModule<'_> {
136    type Code = (&'static str, TokenStream);
137
138    fn into_code(self) -> Self::Code {
139        ("src/client/mod.rs", self.into_token_stream())
140    }
141}