use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{Attribute, Ident, LitStr, Result, Token, Type, Visibility, braced};
use crate::model::{DefineRouteEntry, DefineRouteInput, HeadSkip};
pub(super) struct RouteConfig {
pub static_response: bool,
pub max_body: Option<syn::Expr>,
pub head_skip: HeadSkip,
}
impl Parse for DefineRouteInput {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let vis = input.parse::<Visibility>()?;
let name = input.parse::<Ident>()?;
input.parse::<Token![:]>()?;
let state_ty = input.parse::<Type>()?;
input.parse::<Token![=>]>()?;
let body;
braced!(body in input);
let entries = parse_define_route_entries(&body)?;
Ok(Self {
vis,
name,
state_ty,
entries,
})
}
}
fn parse_define_route_entries(input: ParseStream<'_>) -> Result<Vec<DefineRouteEntry>> {
let mut out = Vec::new();
while !input.is_empty() {
out.push(parse_define_route_entry(input)?);
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
}
Ok(out)
}
fn parse_define_route_entry(input: ParseStream<'_>) -> Result<DefineRouteEntry> {
if input.peek(syn::Ident)
&& input
.fork()
.parse::<Ident>()
.map(|i| i == "scope")
.unwrap_or(false)
{
let _ = input.parse::<Ident>()?;
let prefix = input.parse::<LitStr>()?;
let mut wraps = Vec::new();
if input.peek(syn::Ident)
&& input
.fork()
.parse::<Ident>()
.map(|i| i == "with")
.unwrap_or(false)
{
input.parse::<Ident>()?;
let wrap_body;
syn::parenthesized!(wrap_body in input);
let punctuated: syn::punctuated::Punctuated<syn::TypePath, Token![,]> =
Punctuated::parse_terminated(&wrap_body)?;
wraps = punctuated.into_iter().collect();
}
input.parse::<Token![=>]>()?;
let children = if input.peek(syn::token::Bracket) {
let body;
syn::bracketed!(body in input);
let mut services = Vec::new();
while !body.is_empty() {
let method = body.parse::<Ident>()?;
let path = body.parse::<LitStr>()?;
body.parse::<Token![=>]>()?;
let ty = parse_route_type(&body)?;
services.push(DefineRouteEntry::Service { method, path, ty });
if body.peek(Token![,]) {
body.parse::<Token![,]>()?;
}
}
services
} else if input.peek(syn::token::Brace) {
let body;
braced!(body in input);
parse_define_route_entries(&body)?
} else {
return Err(input.error("expected `[ ... ]` or `{ ... }` after scope `=>`"));
};
Ok(DefineRouteEntry::Scope {
prefix,
wraps,
children,
})
} else {
let method = input.parse::<Ident>()?;
let path = input.parse::<LitStr>()?;
input.parse::<Token![=>]>()?;
let ty = parse_route_type(input)?;
Ok(DefineRouteEntry::Service { method, path, ty })
}
}
fn parse_route_type(input: ParseStream<'_>) -> Result<syn::TypePath> {
let marker = if input.peek(Token![async]) {
input.parse::<Token![async]>()?;
Some("__SarkAsyncRoute")
} else if input.peek(Ident)
&& input
.fork()
.parse::<Ident>()
.map(|ident| ident == "stream")
.unwrap_or(false)
{
input.parse::<Ident>()?;
Some("__SarkStreamRoute")
} else {
None
};
let Some(marker) = marker else {
return input.parse();
};
if !input.peek(syn::token::Paren) {
return Err(input.error("async and stream routes require `(capacity = N)`"));
}
let options;
syn::parenthesized!(options in input);
let key = options.parse::<Ident>()?;
if key != "capacity" {
return Err(syn::Error::new(key.span(), "expected `capacity`"));
}
options.parse::<Token![=]>()?;
let capacity = options.parse::<syn::Expr>()?;
if !options.is_empty() {
return Err(options.error("trailing tokens after `capacity = N`"));
}
let route = input.parse::<syn::TypePath>()?;
let marker = Ident::new(marker, proc_macro2::Span::call_site());
Ok(syn::parse_quote!(#marker<#route, { #capacity }>))
}
pub(super) fn take_route_config(attrs: &mut Vec<Attribute>) -> Result<RouteConfig> {
let mut static_response = false;
let mut max_body: Option<syn::Expr> = None;
let mut head_skip = HeadSkip::default();
let mut kept = Vec::with_capacity(attrs.len());
for attr in attrs.drain(..) {
if attr.path().is_ident("static_response") {
static_response = true;
} else if attr.path().is_ident("max_body") {
if max_body.is_some() {
return Err(syn::Error::new_spanned(attr, "duplicate #[max_body(...)]"));
}
max_body = Some(attr.parse_args::<syn::Expr>()?);
} else if attr.path().is_ident("skip") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("date") {
head_skip.date = true;
} else if meta.path.is_ident("server") {
head_skip.server = true;
} else {
return Err(
meta.error("unknown #[skip(...)] target; expected `date` | `server`")
);
}
Ok(())
})?;
} else {
kept.push(attr);
}
}
*attrs = kept;
Ok(RouteConfig {
static_response,
max_body,
head_skip,
})
}