use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{ItemImpl, Path, Token, parse::Parse};
use crate::app::extract_app_meta;
use crate::http::{self, HttpArgs, ServeArgs};
use crate::strip_first_impl;
#[derive(Default)]
pub(crate) struct ServerArgs {
pub prefix: Option<String>,
pub openapi: Option<bool>,
pub health: Option<String>,
pub name: Option<String>,
pub description: Option<String>,
pub version: Option<String>,
pub homepage: Option<String>,
pub config_ty: Option<Path>,
pub config_cmd_name: Option<String>,
pub config_cmd: bool,
}
impl Parse for ServerArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut args = ServerArgs::default();
while !input.is_empty() {
let ident: syn::Ident = input.parse()?;
match ident.to_string().as_str() {
"prefix" => {
input.parse::<Token![=]>()?;
let lit: syn::LitStr = input.parse()?;
args.prefix = Some(lit.value());
}
"openapi" => {
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
let lit: syn::LitBool = input.parse()?;
args.openapi = Some(lit.value());
} else {
args.openapi = Some(true);
}
}
"health" => {
input.parse::<Token![=]>()?;
let lit: syn::LitStr = input.parse()?;
args.health = Some(lit.value());
}
"name" => {
input.parse::<Token![=]>()?;
let lit: syn::LitStr = input.parse()?;
args.name = Some(lit.value());
}
"description" => {
input.parse::<Token![=]>()?;
let lit: syn::LitStr = input.parse()?;
args.description = Some(lit.value());
}
"version" => {
input.parse::<Token![=]>()?;
let lit: syn::LitStr = input.parse()?;
args.version = Some(lit.value());
}
"homepage" => {
input.parse::<Token![=]>()?;
let lit: syn::LitStr = input.parse()?;
args.homepage = Some(lit.value());
}
"config" => {
input.parse::<Token![=]>()?;
let path: Path = input.parse()?;
args.config_ty = Some(path);
args.config_cmd = true;
}
"config_cmd" => {
input.parse::<Token![=]>()?;
if input.peek(syn::LitBool) {
let lit: syn::LitBool = input.parse()?;
args.config_cmd = lit.value();
} else {
let lit: syn::LitStr = input.parse()?;
args.config_cmd_name = Some(lit.value());
args.config_cmd = true;
}
}
other => {
const VALID: &[&str] = &[
"prefix", "openapi", "health", "name", "description", "version",
"homepage", "config", "config_cmd",
];
let suggestion = crate::did_you_mean(other, VALID)
.map(|s| format!(" — did you mean `{s}`?"))
.unwrap_or_default();
return Err(syn::Error::new(
ident.span(),
format!(
"unknown argument `{other}`{suggestion}\n\
Valid arguments: prefix, openapi, health, name, description, version, homepage, config, config_cmd"
),
));
}
}
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
}
Ok(args)
}
}
pub(crate) fn expand_server(args: ServerArgs, mut impl_block: ItemImpl) -> syn::Result<TokenStream2> {
let app_meta = extract_app_meta(&mut impl_block.attrs);
let name = args.name.or(app_meta.name);
let description = args.description.or(app_meta.description);
let version = args.version.or_else(|| app_meta.version.into_explicit());
let homepage = args.homepage.or(app_meta.homepage);
let http_args = HttpArgs {
prefix: args.prefix,
openapi: args.openapi,
debug: false,
trace: false,
name: name.clone(),
description: description.clone(),
version: version.clone(),
homepage: homepage.clone(),
};
let http_tokens = http::expand_http(http_args, impl_block.clone())?;
let serve_args = ServeArgs {
protocols: vec!["http".into()],
health_path: args.health,
openapi: args.openapi,
name: name.clone(),
description,
version,
homepage,
};
let serve_tokens = strip_first_impl(http::expand_serve(serve_args, impl_block.clone())?);
#[cfg(feature = "config")]
let config_methods = if args.config_cmd {
if let Some(ref config_ty) = args.config_ty {
let self_ty = &impl_block.self_ty;
let cmd_name = args.config_cmd_name.as_deref().unwrap_or("config");
let app_name = name.as_deref().unwrap_or("app");
let (methods, _subcommand_addition, _dispatch_arm) =
crate::config_cmd::generate_all(self_ty, config_ty, cmd_name, app_name);
methods
} else {
quote! {}
}
} else {
quote! {}
};
#[cfg(not(feature = "config"))]
let config_methods = quote! {};
Ok(quote! { #http_tokens #serve_tokens #config_methods })
}