use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{Ident, LitStr, Path, Token, Type, bracketed};
#[derive(Default)]
pub struct OpenApiArgs {
pub components: Vec<Type>,
pub server: Option<String>,
pub paths: Vec<Path>,
}
impl OpenApiArgs {
pub fn empty() -> Self { Self::default() }
}
impl Parse for OpenApiArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut components = Vec::new();
let mut server = None;
let mut paths = Vec::new();
while !input.is_empty() {
let ident: Ident = input.parse()?;
let ident_str = ident.to_string();
match ident_str.as_str() {
"components" => {
components = parse_components(input, ident.span())?;
}
"server" => {
server = Some(parse_string_value(input, ident.span(), "server")?);
}
"paths" => {
paths = parse_path_list(input, ident.span())?;
}
_ => {
return Err(syn::Error::new_spanned(
&ident,
format!(
"unknown openapi option `{}`. Expected `components`, `server`, or \
`paths`",
ident
),
));
}
}
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
}
Ok(OpenApiArgs { components, server, paths })
}
}
fn parse_components(
input: ParseStream,
components_span: proc_macro2::Span,
) -> syn::Result<Vec<Type>> {
if !input.peek(Token![=]) {
return Err(syn::Error::new(components_span, "expected `=` after `components`"));
}
input.parse::<Token![=]>()?;
if !input.peek(syn::token::Bracket) {
return Err(syn::Error::new(input.span(), "expected `[...]` after `components =`"));
}
let content;
bracketed!(content in input);
let types: Punctuated<Type, Token![,]> = content.parse_terminated(Type::parse, Token![,])?;
Ok(types.into_iter().collect())
}
fn parse_string_value(
input: ParseStream,
keyword_span: proc_macro2::Span,
keyword: &str,
) -> syn::Result<String> {
if !input.peek(Token![=]) {
return Err(syn::Error::new(keyword_span, format!("expected `=` after `{keyword}`")));
}
input.parse::<Token![=]>()?;
let lit: LitStr = input.parse().map_err(|_| {
syn::Error::new(input.span(), format!("expected string literal after `{keyword} =`"))
})?;
Ok(lit.value())
}
fn parse_path_list(input: ParseStream, paths_span: proc_macro2::Span) -> syn::Result<Vec<Path>> {
if !input.peek(Token![=]) {
return Err(syn::Error::new(paths_span, "expected `=` after `paths`"));
}
input.parse::<Token![=]>()?;
if !input.peek(syn::token::Bracket) {
return Err(syn::Error::new(input.span(), "expected `[...]` after `paths =`"));
}
let content;
bracketed!(content in input);
let paths: Punctuated<Path, Token![,]> = content.parse_terminated(Path::parse, Token![,])?;
Ok(paths.into_iter().collect())
}