mod args;
mod collector;
mod cron_impl;
mod error;
mod file_utils;
mod garde_emit;
mod http;
mod metadata;
mod method;
mod openapi_generator;
mod multipart_impl;
mod parser;
mod route;
mod route_impl;
mod router_codegen;
mod schema_impl;
mod schema_macro;
mod vespera_impl;
pub(crate) use cron_impl::CRON_STORAGE;
use proc_macro::TokenStream;
pub(crate) use route_impl::ROUTE_STORAGE;
pub(crate) use schema_impl::SCHEMA_STORAGE;
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 {
let input = syn::parse_macro_input!(input as syn::DeriveInput);
let (metadata, expanded) = schema_impl::process_derive_schema(&input);
let name = metadata.name.clone();
let mut storage = SCHEMA_STORAGE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(existing) = storage.get(&name)
&& existing.definition != metadata.definition
{
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);
}
storage.insert(name, metadata);
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 {
let input = syn::parse_macro_input!(input as schema_macro::SchemaInput);
let storage = SCHEMA_STORAGE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 {
let input = syn::parse_macro_input!(input as schema_macro::SchemaTypeInput);
let ignore_schema = input.ignore_schema;
let (tokens, generated_metadata) = {
let storage = SCHEMA_STORAGE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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_STORAGE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.insert(name, metadata);
}
TokenStream::from(tokens)
}
#[cfg(not(tarpaulin_include))]
#[proc_macro]
pub fn vespera(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as AutoRouterInput);
let processed = process_vespera_input(input);
let schema_storage = SCHEMA_STORAGE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let route_storage = ROUTE_STORAGE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match process_vespera_macro(&processed, &schema_storage, &route_storage) {
Ok(tokens) => tokens.into(),
Err(e) => e.to_compile_error().into(),
}
}
#[cfg(not(tarpaulin_include))]
#[proc_macro]
pub fn export_app(input: TokenStream) -> TokenStream {
let ExportAppInput { name, dir } = syn::parse_macro_input!(input as ExportAppInput);
let folder_name = dir
.map(|d| d.value())
.or_else(|| std::env::var("VESPERA_DIR").ok())
.unwrap_or_else(|| "routes".to_string());
let schema_storage = SCHEMA_STORAGE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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_STORAGE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match process_export_app(
&name,
&folder_name,
&schema_storage,
&manifest_dir,
&route_storage,
) {
Ok(tokens) => tokens.into(),
Err(e) => e.to_compile_error().into(),
}
}