use std::borrow::Cow;
use std::ops::Deref;
use proc_macro_crate::{FoundCrate, crate_name};
use proc_macro2::{Delimiter, Group, Punct, Span, TokenStream};
use proc_macro2_diagnostics::Diagnostic;
use quote::{ToTokens, TokenStreamExt, quote};
use regex::Regex;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{
Attribute, ExprPath, FnArg, Ident, Lit, LitStr, Member, PatType, Receiver, Token, Type,
TypePath,
};
use crate::feature::attributes;
use crate::{RenameRule, SerdeContainer, SerdeValue, parse_utils};
#[allow(dead_code)]
pub(crate) enum InputType<'a> {
Request(&'a PatType),
Depot(&'a PatType),
Response(&'a PatType),
FlowCtrl(&'a PatType),
Unknown,
Receiver(&'a Receiver),
NoReference(&'a PatType),
}
pub(crate) fn oapi_crate() -> syn::Ident {
match crate_name("salvo-oapi") {
Ok(oapi) => match oapi {
FoundCrate::Itself => syn::Ident::new("salvo_oapi", Span::call_site()),
FoundCrate::Name(name) => syn::Ident::new(&name, Span::call_site()),
},
Err(_) => Ident::new("salvo", Span::call_site()),
}
}
pub(crate) fn salvo_crate() -> syn::Ident {
match crate_name("salvo") {
Ok(salvo) => match salvo {
FoundCrate::Itself => Ident::new("salvo", Span::call_site()),
FoundCrate::Name(name) => Ident::new(&name, Span::call_site()),
},
Err(_) => match crate_name("salvo_core") {
Ok(salvo) => match salvo {
FoundCrate::Itself => Ident::new("salvo_core", Span::call_site()),
FoundCrate::Name(name) => Ident::new(&name, Span::call_site()),
},
Err(_) => Ident::new("salvo", Span::call_site()),
},
}
}
pub(crate) fn parse_input_type(input: &FnArg) -> InputType<'_> {
if let FnArg::Typed(p) = input {
if let Type::Reference(ty) = &*p.ty {
if let syn::Type::Path(nty) = &*ty.elem {
let ident = &nty
.path
.segments
.last()
.expect("path segments is empty")
.ident;
if ident == "Request" {
InputType::Request(p)
} else if ident == "Response" {
InputType::Response(p)
} else if ident == "Depot" {
InputType::Depot(p)
} else if ident == "FlowCtrl" {
InputType::FlowCtrl(p)
} else {
InputType::Unknown
}
} else {
InputType::Unknown
}
} else {
InputType::NoReference(p)
}
} else if let FnArg::Receiver(r) = input {
InputType::Receiver(r)
} else {
InputType::Unknown
}
}
pub(crate) fn omit_type_path_lifetimes(ty_path: &TypePath) -> TypePath {
let reg = Regex::new(r"'\w+").expect("invalid regex");
let ty_path = ty_path.into_token_stream().to_string();
let ty_path = reg.replace_all(&ty_path, "'_");
syn::parse_str(ty_path.as_ref()).expect("failed to parse type path")
}
pub(crate) trait TryToTokens {
fn try_to_tokens(&self, tokens: &mut TokenStream) -> DiagResult<()>;
fn try_to_token_stream(&self) -> DiagResult<TokenStream> {
let mut tokens = TokenStream::new();
match self.try_to_tokens(&mut tokens) {
Ok(()) => Ok(tokens),
Err(diag) => Err(diag),
}
}
}
pub(crate) type DiagResult<T> = Result<T, Diagnostic>;
#[inline]
pub(crate) fn is_default(
container_rules: Option<&SerdeContainer>,
field_rule: Option<&SerdeValue>,
) -> bool {
container_rules.map(|rule| rule.is_default).unwrap_or(false)
|| field_rule.map(|rule| rule.is_default).unwrap_or(false)
}
pub(crate) fn get_deprecated(attributes: &[Attribute]) -> Option<crate::Deprecated> {
if attributes
.iter()
.any(|attr| attr.path().is_ident("deprecated"))
{
Some(Deprecated::True)
} else {
None
}
}
pub(crate) fn is_required(
field_rule: Option<&SerdeValue>,
container_rules: Option<&SerdeContainer>,
) -> bool {
!field_rule
.map(|rule| rule.skip_serializing_if)
.unwrap_or(false)
&& !field_rule.map(|rule| rule.double_option).unwrap_or(false)
&& !is_default(container_rules, field_rule)
}
#[derive(Debug)]
pub(crate) enum Array<'a, T>
where
T: Sized + ToTokens,
{
Owned(Vec<T>),
#[allow(dead_code)]
Borrowed(&'a [T]),
}
impl<T> Array<'_, T> where T: ToTokens + Sized {}
impl<V> FromIterator<V> for Array<'_, V>
where
V: Sized + ToTokens,
{
fn from_iter<T: IntoIterator<Item = V>>(iter: T) -> Self {
Self::Owned(iter.into_iter().collect())
}
}
impl<T> Deref for Array<'_, T>
where
T: Sized + ToTokens,
{
type Target = [T];
fn deref(&self) -> &Self::Target {
match self {
Self::Owned(vec) => vec.as_slice(),
Self::Borrowed(slice) => slice,
}
}
}
impl<T> ToTokens for Array<'_, T>
where
T: Sized + ToTokens,
{
fn to_tokens(&self, tokens: &mut TokenStream) {
let values = match self {
Self::Owned(values) => values.iter(),
Self::Borrowed(values) => values.iter(),
};
tokens.append(Group::new(
Delimiter::Bracket,
values
.fold(Punctuated::new(), |mut punctuated, item| {
punctuated.push_value(item);
punctuated.push_punct(Punct::new(',', proc_macro2::Spacing::Alone));
punctuated
})
.to_token_stream(),
));
}
}
#[derive(Debug)]
pub(crate) enum Deprecated {
True,
False,
}
impl From<bool> for Deprecated {
fn from(bool: bool) -> Self {
if bool { Self::True } else { Self::False }
}
}
impl ToTokens for Deprecated {
fn to_tokens(&self, tokens: &mut TokenStream) {
let oapi = crate::oapi_crate();
tokens.extend(match self {
Self::False => quote! { #oapi::oapi::Deprecated::False },
Self::True => quote! { #oapi::oapi::Deprecated::True },
})
}
}
#[derive(PartialEq, Eq, Debug)]
pub(crate) enum Required {
True,
False,
}
impl From<bool> for Required {
fn from(bool: bool) -> Self {
if bool { Self::True } else { Self::False }
}
}
impl From<attributes::Required> for Required {
fn from(value: attributes::Required) -> Self {
let attributes::Required(required) = value;
Self::from(required)
}
}
impl ToTokens for Required {
fn to_tokens(&self, stream: &mut TokenStream) {
let oapi = crate::oapi_crate();
stream.extend(match self {
Self::False => quote! { #oapi::oapi::Required::False },
Self::True => quote! { #oapi::oapi::Required::True },
})
}
}
#[allow(dead_code)]
#[derive(Default, Debug)]
pub(crate) struct ExternalDocs {
url: String,
description: Option<String>,
}
impl Parse for ExternalDocs {
fn parse(input: ParseStream) -> syn::Result<Self> {
const EXPECTED_ATTRIBUTE: &str = "unexpected attribute, expected any of: url, description";
let mut external_docs = Self::default();
while !input.is_empty() {
let ident = input.parse::<Ident>().map_err(|error| {
syn::Error::new(error.span(), format!("{EXPECTED_ATTRIBUTE}, {error}"))
})?;
let attr_name = &*ident.to_string();
match attr_name {
"url" => {
external_docs.url = parse_utils::parse_next_lit_str(input)?;
}
"description" => {
external_docs.description = Some(parse_utils::parse_next_lit_str(input)?);
}
_ => return Err(syn::Error::new(ident.span(), EXPECTED_ATTRIBUTE)),
}
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
}
Ok(external_docs)
}
}
impl ToTokens for ExternalDocs {
fn to_tokens(&self, tokens: &mut TokenStream) {
let oapi = crate::oapi_crate();
let url = &self.url;
tokens.extend(quote! {
#oapi::oapi::external_docs::ExternalDocsBuilder::new()
.url(#url)
});
if let Some(ref description) = self.description {
tokens.extend(quote! {
.description(#description)
});
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum AnyValue {
String(TokenStream),
Json(TokenStream),
DefaultTrait {
struct_ident: Ident,
field_ident: Member,
},
}
impl AnyValue {
pub(crate) fn parse_json(input: ParseStream) -> syn::Result<Self> {
parse_utils::parse_json_token_stream(input).map(AnyValue::Json)
}
pub(crate) fn parse_any(input: ParseStream) -> syn::Result<Self> {
if input.peek(Token![-]) || input.peek(Lit) {
let punct = input.parse::<Option<Token![-]>>()?;
let lit = input
.parse::<Lit>()
.map_err(|e| syn::Error::new(e.span(), "expected a literal value after `-`"))?;
Ok(Self::Json(quote! { #punct #lit}))
} else {
let fork = input.fork();
let is_json = if fork.peek(syn::Ident) && fork.peek2(Token![!]) {
let ident = fork.parse::<Ident>().expect("parse `Ident` failed");
ident == "json"
} else {
false
};
if is_json {
let json = parse_utils::parse_json_token_stream(input)?;
Ok(Self::Json(json))
} else {
let method = input.parse::<ExprPath>().map_err(|error| {
syn::Error::new(
error.span(),
"expected literal value, json!(...) or method reference",
)
})?;
Ok(Self::Json(quote! { #method() }))
}
}
}
pub(crate) fn parse_lit_str_or_json(input: ParseStream) -> syn::Result<Self> {
if input.peek(LitStr) {
Ok(Self::String(
input
.parse::<LitStr>()
.expect("parse_lit_str_or_json: parse `LitStr` failed")
.to_token_stream(),
))
} else {
Ok(Self::Json(parse_utils::parse_json_token_stream(input)?))
}
}
pub(crate) fn new_default_trait(struct_ident: Ident, field_ident: Member) -> Self {
Self::DefaultTrait {
struct_ident,
field_ident,
}
}
}
impl ToTokens for AnyValue {
fn to_tokens(&self, tokens: &mut TokenStream) {
let oapi = crate::oapi_crate();
match self {
Self::Json(json) => tokens.extend(quote! {
#oapi::oapi::__private::serde_json::json!(#json)
}),
Self::String(string) => string.to_tokens(tokens),
Self::DefaultTrait {
struct_ident,
field_ident,
} => tokens.extend(quote! {
#oapi::oapi::__private::serde_json::to_value(#struct_ident::default().#field_ident).unwrap()
}),
}
}
}
pub(crate) trait Rename {
fn rename(rule: RenameRule, value: &str) -> String;
}
pub(crate) fn rename<'r, R: Rename>(
value: &'r str,
to: Option<Cow<'r, str>>,
container_rule: Option<RenameRule>,
) -> Option<Cow<'r, str>> {
let rename = to.and_then(|to| if !to.is_empty() { Some(to) } else { None });
rename.or_else(|| {
container_rule.map(|container_rule| Cow::Owned(R::rename(container_rule, value)))
})
}
pub(crate) struct VariantRename;
impl Rename for VariantRename {
fn rename(rule: RenameRule, value: &str) -> String {
rule.apply_to_variant(value)
}
}
pub(crate) struct FieldRename;
impl Rename for FieldRename {
fn rename(rule: RenameRule, value: &str) -> String {
rule.apply_to_field(value)
}
}