use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::{Expr, Meta, Token, punctuated::Punctuated};
use crate::utils::{meta_to_meta_list, meta_to_meta_name_value};
static SECURITY_SCHEME_ATTRIBUTE_NAME: &str = "security_scheme";
static SECURITY_SCHEME_NAME_ATTRIBUTE_NAME: &str = "name";
static SECURITY_SCHEME_SCOPES_ATTRIBUTE_NAME: &str = "scopes";
#[derive(Default, Debug, PartialEq)]
pub struct Security {
schemes: Vec<SecurityScheme>,
}
#[derive(Default, Debug, PartialEq)]
struct SecurityScheme {
name: String,
scopes: Vec<String>,
}
impl FromMeta for Security {
fn from_meta(meta: &Meta) -> Result<Self, darling::Error> {
let meta_list = meta_to_meta_list(meta)?;
let mut this = Self::default();
for meta in meta_list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)? {
let meta_ident = meta
.path()
.get_ident()
.ok_or_else(|| darling::Error::custom("Should have Ident").with_span(&meta))?;
match meta_ident {
_ if meta_ident == SECURITY_SCHEME_ATTRIBUTE_NAME => {
this.schemes.push(SecurityScheme::from_meta(&meta)?)
}
_ => {
return Err(darling::Error::custom("Unsupported type of parameter")
.with_span(meta_ident));
}
}
}
Ok(this)
}
}
impl ToTokens for Security {
fn to_tokens(&self, tokens: &mut TokenStream) {
let schemes = &self.schemes;
tokens.extend(quote! {
vec![#(
{
let mut val = okapi::openapi3::SecurityRequirement::new();
let (sch_key, sch_val) = #schemes;
val.insert(sch_key, sch_val);
val
}
),*]
});
}
}
impl FromMeta for SecurityScheme {
fn from_meta(meta: &Meta) -> Result<Self, darling::Error> {
let meta_list = meta_to_meta_list(meta)?;
let mut this = Self::default();
for meta in meta_list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)? {
let meta = meta_to_meta_name_value(&meta)?;
let meta_ident = meta
.path
.get_ident()
.ok_or_else(|| darling::Error::custom("Should have Ident").with_span(meta))?;
match meta_ident {
_ if meta_ident == SECURITY_SCHEME_NAME_ATTRIBUTE_NAME => {
let Expr::Lit(ref lit) = &meta.value else {
return Err(darling::Error::custom(
"Security scheme name should be string literal",
)
.with_span(meta_ident));
};
this.name = String::from_value(&lit.lit)?;
}
_ if meta_ident == SECURITY_SCHEME_SCOPES_ATTRIBUTE_NAME => {
let Expr::Lit(ref lit) = &meta.value else {
return Err(darling::Error::custom(
"Security scheme scope should be string literal",
)
.with_span(meta_ident));
};
let val = String::from_value(&lit.lit)?;
this.scopes = val.split(',').map(|v| v.to_owned()).collect();
}
_ => {
return Err(darling::Error::custom("Unsupported type of parameter")
.with_span(meta_ident));
}
}
}
if this.name.is_empty() {
return Err(darling::Error::custom(format!(
"Required attribute '{}' is missing",
SECURITY_SCHEME_NAME_ATTRIBUTE_NAME
))
.with_span(meta));
}
Ok(this)
}
}
impl ToTokens for SecurityScheme {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = &self.name;
let scopes = &self.scopes;
tokens.extend(quote! {
(
std::borrow::ToOwned::to_owned(#name),
vec![#(std::borrow::ToOwned::to_owned(#scopes)),*],
)
});
}
}
#[cfg(test)]
mod tests {
use syn::{Meta, parse_quote};
use super::*;
#[test]
fn parse_security_scheme() {
let name = "test_name".to_string();
let scopes = "scope1,scope2,scope3".to_string();
let input: Meta = parse_quote! { security_scheme(name = #name, scopes = #scopes) };
assert_eq!(
SecurityScheme::from_meta(&input).expect("Successfullt parsed"),
SecurityScheme {
name,
scopes: scopes.split(',').map(Into::into).collect()
}
);
}
#[test]
fn parse_security() {
let name1 = "test_name1".to_string();
let name2 = "test_name2".to_string();
let scopes = "scope1,scope2,scope3".to_string();
let input: Meta = parse_quote! { security(
security_scheme(name = #name1, scopes = #scopes),
security_scheme(name = #name2, scopes = #scopes)
) };
assert_eq!(
Security::from_meta(&input).expect("Failed to parse"),
Security {
schemes: vec![
SecurityScheme {
name: name1,
scopes: scopes.split(',').map(Into::into).collect()
},
SecurityScheme {
name: name2,
scopes: scopes.split(',').map(Into::into).collect()
}
]
}
);
}
}