1#![deny(missing_docs)]
6
7use std::{collections::HashMap, fs::File, path::PathBuf};
8
9use openapiv3::OpenAPI;
10use proc_macro::TokenStream;
11use progenitor_impl::{
12 CrateVers, GenerationSettings, Generator, InterfaceStyle, TagStyle, TypePatch, UnknownPolicy,
13};
14use quote::{ToTokens, quote};
15use schemars::schema::SchemaObject;
16use serde::Deserialize;
17use serde_tokenstream::{OrderedMap, ParseWrapper};
18use syn::LitStr;
19use token_utils::TypeAndImpls;
20
21mod token_utils;
22
23#[derive(Debug, Clone, Copy, Deserialize)]
25enum RelativeTo {
26 ManifestDir,
28 OutDir,
30}
31
32#[derive(Debug)]
34struct SpecSource {
35 path: LitStr,
37 relative_to: RelativeTo,
39}
40
41impl syn::parse::Parse for SpecSource {
42 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
43 #[derive(Deserialize)]
45 struct SpecSourceStruct {
46 path: ParseWrapper<LitStr>,
47 relative_to: RelativeTo,
48 }
49
50 let lookahead = input.lookahead1();
51 if lookahead.peek(LitStr) {
52 let path: LitStr = input.parse()?;
54 Ok(SpecSource {
55 path,
56 relative_to: RelativeTo::ManifestDir,
57 })
58 } else if lookahead.peek(syn::token::Brace) {
59 let content;
61 let brace_token = syn::braced!(content in input);
62 let stream: proc_macro2::TokenStream = content.parse()?;
63 let helper: SpecSourceStruct =
64 serde_tokenstream::from_tokenstream_spanned(&brace_token.span, &stream)?;
65 Ok(SpecSource {
66 path: helper.path.into_inner(),
67 relative_to: helper.relative_to,
68 })
69 } else {
70 Err(lookahead.error())
71 }
72 }
73}
74
75#[proc_macro]
180pub fn generate_api(item: TokenStream) -> TokenStream {
181 match do_generate_api(item) {
182 Err(err) => err.to_compile_error().into(),
183 Ok(out) => out,
184 }
185}
186
187#[derive(Deserialize)]
188struct MacroSettings {
189 spec: ParseWrapper<SpecSource>,
190 #[serde(default)]
191 interface: InterfaceStyle,
192 #[serde(default)]
193 tags: TagStyle,
194
195 inner_type: Option<ParseWrapper<syn::Type>>,
196 pre_hook: Option<ParseWrapper<ClosureOrPath>>,
197 pre_hook_async: Option<ParseWrapper<ClosureOrPath>>,
198 post_hook: Option<ParseWrapper<ClosureOrPath>>,
199 post_hook_async: Option<ParseWrapper<ClosureOrPath>>,
200
201 map_type: Option<ParseWrapper<syn::Type>>,
202
203 #[serde(default)]
204 derives: Vec<ParseWrapper<syn::Path>>,
205
206 #[serde(default)]
207 unknown_crates: UnknownPolicy,
208 #[serde(default)]
209 crates: HashMap<CrateName, MacroCrateSpec>,
210
211 #[serde(default)]
212 patch: HashMap<ParseWrapper<syn::Ident>, MacroPatch>,
213 #[serde(default)]
214 replace: HashMap<ParseWrapper<syn::Ident>, ParseWrapper<TypeAndImpls>>,
215 #[serde(default)]
216 convert: OrderedMap<SchemaObject, ParseWrapper<TypeAndImpls>>,
217 timeout: Option<u64>,
218}
219
220#[derive(Deserialize)]
221struct MacroPatch {
222 #[serde(default)]
223 rename: Option<String>,
224 #[serde(default)]
225 derives: Vec<ParseWrapper<syn::Path>>,
226}
227
228impl From<MacroPatch> for TypePatch {
229 fn from(a: MacroPatch) -> Self {
230 let mut s = Self::default();
231 a.rename.iter().for_each(|rename| {
232 s.with_rename(rename);
233 });
234 a.derives.iter().for_each(|derive| {
235 s.with_derive(derive.to_token_stream().to_string());
236 });
237 s
238 }
239}
240
241#[derive(Debug)]
242struct ClosureOrPath(proc_macro2::TokenStream);
243
244impl syn::parse::Parse for ClosureOrPath {
245 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
246 let lookahead = input.lookahead1();
247
248 if lookahead.peek(syn::token::Paren) {
249 let group: proc_macro2::Group = input.parse()?;
250 return syn::parse2::<Self>(group.stream());
251 }
252
253 if let Ok(closure) = input.parse::<syn::ExprClosure>() {
254 return Ok(Self(closure.to_token_stream()));
255 }
256
257 input
258 .parse::<syn::Path>()
259 .map(|path| Self(path.to_token_stream()))
260 }
261}
262
263struct MacroCrateSpec {
264 original: Option<String>,
265 version: CrateVers,
266}
267
268impl<'de> Deserialize<'de> for MacroCrateSpec {
269 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
270 where
271 D: serde::Deserializer<'de>,
272 {
273 let ss = String::deserialize(deserializer)?;
274
275 let (original, vers_str) = if let Some(ii) = ss.find('@') {
276 let original_str = &ss[..ii];
277 let rest = &ss[ii + 1..];
278 if !is_crate(original_str) {
279 return Err(<D::Error as serde::de::Error>::invalid_value(
280 serde::de::Unexpected::Str(&ss),
281 &"valid crate name",
282 ));
283 }
284
285 (Some(original_str.to_string()), rest)
286 } else {
287 (None, ss.as_ref())
288 };
289
290 let Some(version) = CrateVers::parse(vers_str) else {
291 return Err(<D::Error as serde::de::Error>::invalid_value(
292 serde::de::Unexpected::Str(&ss),
293 &"valid version",
294 ));
295 };
296
297 Ok(Self { original, version })
298 }
299}
300
301#[derive(Hash, PartialEq, Eq)]
302struct CrateName(String);
303impl<'de> Deserialize<'de> for CrateName {
304 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
305 where
306 D: serde::Deserializer<'de>,
307 {
308 let ss = String::deserialize(deserializer)?;
309
310 if is_crate(&ss) {
311 Ok(Self(ss))
312 } else {
313 Err(<D::Error as serde::de::Error>::invalid_value(
314 serde::de::Unexpected::Str(&ss),
315 &"valid crate name",
316 ))
317 }
318 }
319}
320
321fn is_crate(s: &str) -> bool {
322 !s.contains(|cc: char| !cc.is_alphanumeric() && cc != '_' && cc != '-')
323}
324
325fn open_file(path: PathBuf, span: proc_macro2::Span) -> Result<File, syn::Error> {
326 File::open(path.clone()).map_err(|e| {
327 let path_str = path.to_string_lossy();
328 syn::Error::new(span, format!("couldn't read file {}: {}", path_str, e))
329 })
330}
331
332fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
333 let (spec_source, settings) = if let Ok(spec) = syn::parse::<LitStr>(item.clone()) {
334 let spec_source = SpecSource {
335 path: spec,
336 relative_to: RelativeTo::ManifestDir,
337 };
338 (spec_source, GenerationSettings::default())
339 } else {
340 let MacroSettings {
341 spec,
342 interface,
343 tags,
344 inner_type,
345 pre_hook,
346 pre_hook_async,
347 post_hook,
348 post_hook_async,
349 map_type,
350 unknown_crates,
351 crates,
352 derives,
353 patch,
354 replace,
355 convert,
356 timeout,
357 } = serde_tokenstream::from_tokenstream(&item.into())?;
358
359 let spec = spec.into_inner();
360
361 let mut settings = GenerationSettings::default();
362 settings.with_interface(interface);
363 settings.with_tag(tags);
364 inner_type.map(|inner_type| settings.with_inner_type(inner_type.to_token_stream()));
365 pre_hook.map(|pre_hook| settings.with_pre_hook(pre_hook.into_inner().0));
366 pre_hook_async
367 .map(|pre_hook_async| settings.with_pre_hook_async(pre_hook_async.into_inner().0));
368 post_hook.map(|post_hook| settings.with_post_hook(post_hook.into_inner().0));
369 post_hook_async
370 .map(|post_hook_async| settings.with_post_hook_async(post_hook_async.into_inner().0));
371 map_type.map(|map_type| settings.with_map_type(map_type.to_token_stream()));
372
373 settings.with_unknown_crates(unknown_crates);
374 crates.into_iter().for_each(
375 |(CrateName(crate_name), MacroCrateSpec { original, version })| {
376 if let Some(original_crate) = original {
377 settings.with_crate(original_crate, version, Some(&crate_name));
378 } else {
379 settings.with_crate(crate_name, version, None);
380 }
381 },
382 );
383
384 derives.into_iter().for_each(|derive| {
385 settings.with_derive(derive.to_token_stream());
386 });
387 patch.into_iter().for_each(|(type_name, patch)| {
388 settings.with_patch(type_name.to_token_stream().to_string(), &patch.into());
389 });
390 replace.into_iter().for_each(|(type_name, type_and_impls)| {
391 let type_name = type_name.to_token_stream();
392 let (replace_name, impls) = type_and_impls.into_inner().into_name_and_impls();
393 settings.with_replacement(type_name, replace_name, impls);
394 });
395 convert.into_iter().for_each(|(schema, type_and_impls)| {
396 let (type_name, impls) = type_and_impls.into_inner().into_name_and_impls();
397 settings.with_conversion(schema, type_name, impls);
398 });
399 if let Some(timeout) = timeout {
400 settings.with_timeout(timeout);
401 }
402 (spec, settings)
403 };
404
405 let spec_path = spec_source.path;
406 let base_dir = match spec_source.relative_to {
407 RelativeTo::ManifestDir => std::env::var("CARGO_MANIFEST_DIR")
408 .map_or_else(|_| std::env::current_dir().unwrap(), PathBuf::from),
409 RelativeTo::OutDir => {
410 let out_dir = std::env::var("OUT_DIR").map_err(|_| {
411 syn::Error::new(
412 spec_path.span(),
413 "relative_to = OutDir requires OUT_DIR to be set \
414 (are you using this from a build script?)",
415 )
416 })?;
417 PathBuf::from(out_dir)
418 }
419 };
420
421 let path = base_dir.join(spec_path.value());
422 let path_str = path.to_string_lossy();
423
424 let mut f = open_file(path.clone(), spec_path.span())?;
425 let oapi: OpenAPI = match serde_json::from_reader(f) {
426 Ok(json_value) => json_value,
427 _ => {
428 f = open_file(path.clone(), spec_path.span())?;
429 serde_yaml::from_reader(f).map_err(|e| {
430 syn::Error::new(
431 spec_path.span(),
432 format!("failed to parse {}: {}", path_str, e),
433 )
434 })?
435 }
436 };
437
438 let mut builder = Generator::new(&settings);
439
440 let code = builder.generate_tokens(&oapi).map_err(|e| {
441 syn::Error::new(
442 spec_path.span(),
443 format!("generation error for {}: {}", spec_path.value(), e),
444 )
445 })?;
446
447 let output = quote! {
448 use progenitor::progenitor_client;
451
452 #code
453
454 const _: &str = include_str!(#path_str);
456 };
457
458 Ok(output.into())
459}