2022-01-15 02:01:59 +00:00
|
|
|
// Copyright 2022 Oxide Computer Company
|
2021-10-17 17:40:22 +00:00
|
|
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
|
|
|
use openapiv3::OpenAPI;
|
|
|
|
use proc_macro::TokenStream;
|
|
|
|
use progenitor_impl::Generator;
|
2021-11-17 02:00:55 +00:00
|
|
|
use quote::{quote, ToTokens};
|
2022-01-15 02:01:59 +00:00
|
|
|
use serde::Deserialize;
|
|
|
|
use serde_tokenstream::ParseWrapper;
|
|
|
|
use syn::LitStr;
|
2021-10-17 17:40:22 +00:00
|
|
|
|
|
|
|
#[proc_macro]
|
|
|
|
pub fn generate_api(item: TokenStream) -> TokenStream {
|
|
|
|
match do_generate_api(item) {
|
|
|
|
Err(err) => err.to_compile_error().into(),
|
|
|
|
Ok(out) => out,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-15 02:01:59 +00:00
|
|
|
#[derive(Deserialize)]
|
2021-10-29 14:16:39 +00:00
|
|
|
struct Settings {
|
2022-01-15 02:01:59 +00:00
|
|
|
spec: ParseWrapper<LitStr>,
|
|
|
|
inner_type: Option<ParseWrapper<syn::Type>>,
|
|
|
|
pre_hook: Option<ParseWrapper<ClosureOrPath>>,
|
|
|
|
post_hook: Option<ParseWrapper<ClosureOrPath>>,
|
|
|
|
#[serde(default)]
|
|
|
|
derives: Vec<ParseWrapper<syn::Path>>,
|
2021-10-29 14:16:39 +00:00
|
|
|
}
|
|
|
|
|
2022-01-15 02:01:59 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct ClosureOrPath(proc_macro2::TokenStream);
|
2021-10-29 14:16:39 +00:00
|
|
|
|
2022-01-15 02:01:59 +00:00
|
|
|
impl syn::parse::Parse for ClosureOrPath {
|
|
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
|
let lookahead = input.lookahead1();
|
2021-10-29 14:16:39 +00:00
|
|
|
|
2022-01-15 02:01:59 +00:00
|
|
|
if lookahead.peek(syn::token::Paren) {
|
|
|
|
let group: proc_macro2::Group = input.parse()?;
|
|
|
|
return syn::parse2::<Self>(group.stream());
|
|
|
|
}
|
2021-10-29 14:16:39 +00:00
|
|
|
|
2022-01-15 02:01:59 +00:00
|
|
|
if let Ok(closure) = input.parse::<syn::ExprClosure>() {
|
|
|
|
return Ok(Self(closure.to_token_stream()));
|
|
|
|
}
|
2021-10-29 14:16:39 +00:00
|
|
|
|
2022-01-15 02:01:59 +00:00
|
|
|
input
|
|
|
|
.parse::<syn::Path>()
|
|
|
|
.map(|path| Self(path.to_token_stream()))
|
2021-10-29 14:16:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-17 17:40:22 +00:00
|
|
|
fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
|
2022-01-15 02:01:59 +00:00
|
|
|
let (spec, inner_type, pre_hook, post_hook, derives) =
|
|
|
|
if let Ok(spec) = syn::parse::<LitStr>(item.clone()) {
|
|
|
|
(spec, None, None, None, Vec::new())
|
|
|
|
} else {
|
|
|
|
let Settings {
|
|
|
|
spec,
|
|
|
|
inner_type,
|
|
|
|
pre_hook,
|
|
|
|
post_hook,
|
|
|
|
derives,
|
|
|
|
} = serde_tokenstream::from_tokenstream(&item.into())?;
|
|
|
|
(
|
|
|
|
spec.into_inner(),
|
|
|
|
inner_type.map(|x| x.into_inner()),
|
|
|
|
pre_hook.map(|x| x.into_inner()),
|
|
|
|
post_hook.map(|x| x.into_inner()),
|
|
|
|
derives.into_iter().map(ParseWrapper::into_inner).collect(),
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
2021-10-17 17:40:22 +00:00
|
|
|
let dir = std::env::var("CARGO_MANIFEST_DIR").map_or_else(
|
|
|
|
|_| std::env::current_dir().unwrap(),
|
|
|
|
|s| Path::new(&s).to_path_buf(),
|
|
|
|
);
|
|
|
|
|
2022-01-15 02:01:59 +00:00
|
|
|
let path = dir.join(spec.value());
|
2021-11-17 02:00:55 +00:00
|
|
|
let path_str = path.to_string_lossy();
|
2021-10-17 17:40:22 +00:00
|
|
|
|
2022-01-15 02:01:59 +00:00
|
|
|
let oapi: OpenAPI =
|
2021-11-17 02:00:55 +00:00
|
|
|
serde_json::from_reader(std::fs::File::open(&path).map_err(|e| {
|
|
|
|
syn::Error::new(
|
2022-01-15 02:01:59 +00:00
|
|
|
spec.span(),
|
2021-11-17 02:00:55 +00:00
|
|
|
format!("couldn't read file {}: {}", path_str, e.to_string()),
|
|
|
|
)
|
|
|
|
})?)
|
|
|
|
.map_err(|e| {
|
|
|
|
syn::Error::new(
|
2022-01-15 02:01:59 +00:00
|
|
|
spec.span(),
|
2021-11-17 02:00:55 +00:00
|
|
|
format!("failed to parse {}: {}", path_str, e.to_string()),
|
|
|
|
)
|
|
|
|
})?;
|
2021-10-17 17:40:22 +00:00
|
|
|
|
|
|
|
let mut builder = Generator::new();
|
2022-01-15 02:01:59 +00:00
|
|
|
inner_type.map(|inner_type| {
|
|
|
|
builder.with_inner_type(inner_type.to_token_stream())
|
|
|
|
});
|
|
|
|
pre_hook.map(|pre_hook| builder.with_pre_hook(pre_hook.0));
|
|
|
|
post_hook.map(|post_hook| builder.with_post_hook(post_hook.0));
|
|
|
|
|
|
|
|
derives.into_iter().for_each(|derive| {
|
|
|
|
builder.with_derive(derive.to_token_stream());
|
|
|
|
});
|
|
|
|
|
|
|
|
let code = builder.generate_tokens(&oapi).map_err(|e| {
|
2021-10-17 17:40:22 +00:00
|
|
|
syn::Error::new(
|
2022-01-15 02:01:59 +00:00
|
|
|
spec.span(),
|
|
|
|
format!("generation error for {}: {}", spec.value(), e.to_string()),
|
2021-10-17 17:40:22 +00:00
|
|
|
)
|
|
|
|
})?;
|
|
|
|
|
2021-11-17 02:00:55 +00:00
|
|
|
let output = quote! {
|
|
|
|
#code
|
|
|
|
|
|
|
|
// Force a rebuild when the given file is modified.
|
|
|
|
const _: &str = include_str!(#path_str);
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(output.into())
|
2021-10-17 17:40:22 +00:00
|
|
|
}
|