mod args;
mod collector;
mod cron_impl;
mod error;
mod file_utils;
mod garde_emit;
mod http;
mod macro_storage;
mod metadata;
mod method;
mod openapi_generator;
mod multipart_impl;
mod parser;
mod route;
mod route_impl;
mod router_codegen;
mod schema_assertions;
mod schema_impl;
mod schema_macro;
mod vespera_impl;
use proc_macro::TokenStream;
use crate::{
router_codegen::{AutoRouterInput, ExportAppInput, process_vespera_input},
vespera_impl::{process_export_app, process_vespera_macro},
};
#[cfg(not(tarpaulin_include))]
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
match route_impl::process_route_attribute(attr.into(), item.into()) {
Ok(tokens) => tokens.into(),
Err(e) => e.to_compile_error().into(),
}
}
#[cfg(not(tarpaulin_include))]
#[proc_macro_attribute]
pub fn cron(attr: TokenStream, item: TokenStream) -> TokenStream {
match cron_impl::process_cron_attribute(attr.into(), item.into()) {
Ok(tokens) => tokens.into(),
Err(e) => e.to_compile_error().into(),
}
}
#[cfg(not(tarpaulin_include))]
#[proc_macro_derive(Schema, attributes(schema, serde))]
pub fn derive_schema(input: TokenStream) -> TokenStream {
schema_macro::file_cache::bump_epoch();
let input = syn::parse_macro_input!(input as syn::DeriveInput);
let (metadata, expanded) = schema_impl::process_derive_schema(&input);
let Some(metadata) = metadata else {
return TokenStream::from(expanded);
};
let name = metadata.name.clone();
if schema_impl::register_schema(name.clone(), metadata).is_err() {
let span = input.ident.span();
let msg = format!(
"duplicate vespera Schema name `{name}` -- two different struct \
definitions both register under the same OpenAPI schema name. \
The later definition would silently overwrite the earlier one \
in the generated `openapi.json`. Rename one of the structs, or \
annotate one with `#[schema(name = \"OtherName\")]` to give \
them distinct OpenAPI names."
);
let err = syn::Error::new(span, msg).to_compile_error();
return TokenStream::from(err);
}
TokenStream::from(expanded)
}
#[cfg(not(tarpaulin_include))]
#[proc_macro_derive(Multipart, attributes(serde, form_data, try_from_multipart))]
pub fn derive_multipart(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::DeriveInput);
TokenStream::from(multipart_impl::process_derive(&input))
}
#[cfg(not(tarpaulin_include))]
#[proc_macro]
pub fn schema(input: TokenStream) -> TokenStream {
schema_macro::file_cache::bump_epoch();
let input = syn::parse_macro_input!(input as schema_macro::SchemaInput);
let storage = schema_impl::current_crate_schemas();
match schema_macro::generate_schema_code(&input, &storage) {
Ok(tokens) => TokenStream::from(tokens),
Err(e) => e.to_compile_error().into(),
}
}
#[cfg(not(tarpaulin_include))]
#[proc_macro]
pub fn schema_type(input: TokenStream) -> TokenStream {
schema_macro::file_cache::bump_epoch();
let input = syn::parse_macro_input!(input as schema_macro::SchemaTypeInput);
let ignore_schema = input.ignore_schema;
let (tokens, generated_metadata) = {
let storage = schema_impl::current_crate_schemas();
match schema_macro::generate_schema_type_code(&input, &storage) {
Ok(result) => result,
Err(e) => return e.to_compile_error().into(),
}
};
if ignore_schema && let Some(metadata) = generated_metadata {
let name = metadata.name.clone();
schema_impl::insert_schema(name, metadata);
}
TokenStream::from(tokens)
}
#[cfg(not(tarpaulin_include))]
#[proc_macro]
pub fn vespera(input: TokenStream) -> TokenStream {
schema_macro::file_cache::bump_epoch();
let input = syn::parse_macro_input!(input as AutoRouterInput);
let folder_span = input
.dir
.as_ref()
.map_or_else(proc_macro2::Span::call_site, syn::LitStr::span);
let processed = process_vespera_input(input);
let schema_storage = schema_impl::current_crate_schemas();
let route_storage = route_impl::current_crate_routes();
match process_vespera_macro(&processed, &schema_storage, &route_storage, folder_span) {
Ok(tokens) => tokens.into(),
Err(e) => e.to_compile_error().into(),
}
}
#[cfg(not(tarpaulin_include))]
#[proc_macro]
pub fn export_app(input: TokenStream) -> TokenStream {
schema_macro::file_cache::bump_epoch();
let ExportAppInput { name, dir, prefix } = syn::parse_macro_input!(input as ExportAppInput);
let folder_span = dir
.as_ref()
.map_or_else(proc_macro2::Span::call_site, syn::LitStr::span);
let folder_name = dir
.map(|d| d.value())
.or_else(|| std::env::var("VESPERA_DIR").ok())
.unwrap_or_else(|| "routes".to_string());
let prefix = prefix.map_or_else(String::new, |value| value.value());
let schema_storage = schema_impl::current_crate_schemas();
let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") else {
return syn::Error::new(proc_macro2::Span::call_site(), "export_app! macro: CARGO_MANIFEST_DIR is not set. This macro must be used within a cargo build.").to_compile_error().into();
};
let route_storage = route_impl::current_crate_routes();
match process_export_app(
&name,
&folder_name,
&schema_storage,
&manifest_dir,
&route_storage,
&prefix,
folder_span,
) {
Ok(tokens) => tokens.into(),
Err(e) => e.to_compile_error().into(),
}
}