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(|err| crate::error::Error::BadHeaderName(err.into()))?;
76 let value = value
77 .try_into()
78 .map_err(|err| crate::error::Error::BadHeaderValue(name.clone(), err.into()))?;
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(|err| crate::error::Error::BadHeaderName(err.into()))?;
109 let mut value: ::ploidy_util::http::HeaderValue = value
110 .try_into()
111 .map_err(|err| crate::error::Error::BadHeaderValue(name.clone(), err.into()))?;
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(
139 &self,
140 method: crate::util::reqwest::Method,
141 path_and_query: &str,
142 ) -> Result<crate::util::reqwest::RequestBuilder, crate::error::Error> {
143 let parts: ::ploidy_util::http::uri::PathAndQuery = path_and_query.parse()?;
144 let mut url = self.base_url.clone();
145 let _ = url.path_segments_mut().map(|mut segments| {
146 let path = parts.path();
147 if path != "/" {
148 let path = path
149 .strip_prefix('/') .unwrap_or(path);
151 segments
152 .pop_if_empty() .extend(path.split('/'));
154 }
155 });
156 if let Some(query) = parts.query() {
157 url.query_pairs_mut()
158 .extend_pairs(::ploidy_util::url::form_urlencoded::parse(query.as_bytes()));
159 }
160 Ok(self.client
161 .request(method, url)
162 .headers(self.headers.clone()))
163 }
164 }
165
166 #mods
167 });
168 }
169}
170
171impl IntoCode for CodegenClientModule<'_> {
172 type Code = (&'static str, TokenStream);
173
174 fn into_code(self) -> Self::Code {
175 ("src/client/mod.rs", self.into_token_stream())
176 }
177}
178
179#[derive(Debug)]
180struct ResourceModules<'a>(&'a [ResourceGroup<'a>]);
181
182impl ToTokens for ResourceModules<'_> {
183 fn to_tokens(&self, tokens: &mut TokenStream) {
184 tokens.append_all(self.0.iter().map(|ident| match ident {
185 ResourceGroup::Named(name) => {
186 let cfg = CfgFeature::Single(name);
187 let mod_name = CodegenIdentUsage::Module(name);
188 quote! {
189 #cfg
190 pub mod #mod_name;
191 }
192 }
193 ResourceGroup::Default => quote!(
194 pub mod default;
195 ),
196 }));
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 use ploidy_core::arena::Arena;
205 use pretty_assertions::assert_eq;
206 use syn::parse_quote;
207
208 use crate::naming::UniqueIdents;
209
210 #[test]
211 fn test_resource_modules_gates_named_resources_and_keeps_default_ungated() {
212 let arena = Arena::new();
213 let mut scope = UniqueIdents::new(&arena);
214 let resources = [
215 ResourceGroup::Default,
216 ResourceGroup::Named(scope.ident("customer_profiles")),
217 ];
218 let modules = ResourceModules(&resources);
219
220 let actual: syn::File = parse_quote!(#modules);
221 let expected: syn::File = parse_quote! {
222 pub mod default;
223
224 #[cfg(feature = "customer-profiles")]
225 pub mod customer_profiles;
226 };
227 assert_eq!(actual, expected);
228 }
229}