ploidy_codegen_rust/
client.rs1use ploidy_core::codegen::IntoCode;
2use proc_macro2::TokenStream;
3use quote::{ToTokens, TokenStreamExt, quote};
4
5use super::{
6 cfg::CfgFeature,
7 graph::CodegenGraph,
8 naming::{CodegenIdentUsage, ResourceGroup},
9};
10
11#[derive(Debug)]
13pub struct CodegenClientModule<'a> {
14 graph: &'a CodegenGraph<'a>,
15 resources: &'a [ResourceGroup<'a>],
16}
17
18impl<'a> CodegenClientModule<'a> {
19 pub fn new(graph: &'a CodegenGraph<'a>, resources: &'a [ResourceGroup<'a>]) -> Self {
20 Self { graph, resources }
21 }
22}
23
24impl ToTokens for CodegenClientModule<'_> {
25 fn to_tokens(&self, tokens: &mut TokenStream) {
26 let client_doc = self.graph.info().label().map(|label| {
27 let doc = match label.version {
28 Some(version) => format!("API client for {} (version {version})", label.title),
29 None => format!("API client for {}", label.title),
30 };
31 quote! { #[doc = #doc] }
32 });
33
34 let mods = ResourceModules(self.resources);
35
36 tokens.append_all(quote! {
37 #client_doc
38 #[derive(Clone, Debug)]
39 pub struct Client {
40 client: ::ploidy_util::reqwest::Client,
41 headers: ::ploidy_util::http::HeaderMap,
42 base_url: ::ploidy_util::url::Url,
43 }
44
45 impl Client {
46 pub fn new(base_url: impl AsRef<str>) -> Result<Self, crate::error::Error> {
48 Ok(Self::with_reqwest_client(
49 ::ploidy_util::reqwest::Client::new(),
50 base_url.as_ref().parse()?,
51 ))
52 }
53
54 pub fn with_reqwest_client(
55 client: crate::util::reqwest::Client,
56 base_url: crate::util::url::Url,
57 ) -> Self {
58 Self {
59 client,
60 headers: ::ploidy_util::http::HeaderMap::new(),
61 base_url,
62 }
63 }
64
65 pub fn with_header<K, V>(mut self, name: K, value: V) -> Result<Self, crate::error::Error>
67 where
68 K: TryInto<crate::util::http::HeaderName>,
69 V: TryInto<crate::util::http::HeaderValue>,
70 K::Error: Into<crate::util::http::Error>,
71 V::Error: Into<crate::util::http::Error>,
72 {
73 let name = name
74 .try_into()
75 .map_err(crate::error::Error::bad_header_name)?;
76 let value = value
77 .try_into()
78 .map_err(|err| crate::error::Error::bad_header_value(name.clone(), err))?;
79 self.headers.insert(name, value);
80 Ok(Self {
81 client: self.client,
82 headers: self.headers,
83 base_url: self.base_url,
84 })
85 }
86
87 pub fn with_sensitive_header<K, V>(self, name: K, value: V) -> Result<Self, crate::error::Error>
100 where
101 K: TryInto<crate::util::http::HeaderName>,
102 V: TryInto<crate::util::http::HeaderValue>,
103 K::Error: Into<crate::util::http::Error>,
104 V::Error: Into<crate::util::http::Error>,
105 {
106 let name = name
107 .try_into()
108 .map_err(crate::error::Error::bad_header_name)?;
109 let mut value: ::ploidy_util::http::HeaderValue = value
110 .try_into()
111 .map_err(|err| crate::error::Error::bad_header_value(name.clone(), err))?;
112 value.set_sensitive(true);
113 self.with_header(name, value)
114 }
115
116 pub fn with_user_agent<V>(self, value: V) -> Result<Self, crate::error::Error>
117 where
118 V: TryInto<crate::util::http::HeaderValue>,
119 V::Error: Into<crate::util::http::Error>,
120 {
121 self.with_header(::ploidy_util::http::header::USER_AGENT, value)
122 }
123
124 pub fn request(
147 &self,
148 method: crate::util::reqwest::Method,
149 path_and_query: &str,
150 ) -> Result<crate::util::reqwest::RequestBuilder, crate::error::Error> {
151 let url = ::ploidy_util::url::UrlExt::with_path_and_query(
152 self.base_url.clone(),
153 path_and_query,
154 )?;
155 Ok(self.client
156 .request(method, url)
157 .headers(self.headers.clone()))
158 }
159 }
160
161 #mods
162 });
163 }
164}
165
166impl IntoCode for CodegenClientModule<'_> {
167 type Code = (&'static str, TokenStream);
168
169 fn into_code(self) -> Self::Code {
170 ("src/client/mod.rs", self.into_token_stream())
171 }
172}
173
174#[derive(Debug)]
175struct ResourceModules<'a>(&'a [ResourceGroup<'a>]);
176
177impl ToTokens for ResourceModules<'_> {
178 fn to_tokens(&self, tokens: &mut TokenStream) {
179 tokens.append_all(self.0.iter().map(|ident| match ident {
180 &ResourceGroup::Named(name) => {
181 let cfg = CfgFeature::Single(name);
182 let mod_name = CodegenIdentUsage::Module(name);
183 quote! {
184 #cfg
185 pub mod #mod_name;
186 }
187 }
188 ResourceGroup::Default => quote!(
189 pub mod default;
190 ),
191 }));
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 use ploidy_core::arena::Arena;
200 use pretty_assertions::assert_eq;
201 use syn::parse_quote;
202
203 use crate::naming::UniqueIdents;
204
205 #[test]
206 fn test_resource_modules_gates_named_resources_and_keeps_default_ungated() {
207 let arena = Arena::new();
208 let mut scope = UniqueIdents::new(&arena);
209 let resources = [
210 ResourceGroup::Default,
211 ResourceGroup::Named(scope.claim("customer_profiles")),
212 ];
213 let modules = ResourceModules(&resources);
214
215 let actual: syn::File = parse_quote!(#modules);
216 let expected: syn::File = parse_quote! {
217 pub mod default;
218
219 #[cfg(feature = "customer-profiles")]
220 pub mod customer_profiles;
221 };
222 assert_eq!(actual, expected);
223 }
224}