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