use proc_macro2::Span;
use syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
Ident, Lit, Path, Result, Token,
};
#[derive(Clone)]
pub enum AttributeValue {
Lit(Lit),
Path(Path),
}
impl AttributeValue {
fn span(&self) -> Span {
match self {
Self::Lit(lit) => lit.span(),
Self::Path(path) => path.span(),
}
}
}
impl Parse for AttributeValue {
fn parse(input: ParseStream) -> Result<Self> {
Ok(match input.peek(Lit) {
true => Self::Lit(input.parse()?),
false => Self::Path(input.parse()?),
})
}
}
#[derive(Clone)]
pub struct Attribute {
pub root_ident: Ident,
pub key: Ident,
value: Option<AttributeValue>,
}
impl Attribute {
pub fn value_span(&self) -> Span {
match &self.value {
Some(v) => v.span(),
None => self.key.span(),
}
}
pub fn parse_string(&self) -> Result<String> {
match &self.value {
Some(AttributeValue::Lit(Lit::Str(string))) => Ok(string.value()),
_ => Err(syn::Error::new(
self.value_span(),
"specta: expected string literal. Eg. `\"somestring\"`",
)),
}
}
pub fn parse_bool(&self) -> Result<bool> {
match &self.value {
Some(AttributeValue::Lit(Lit::Bool(b))) => Ok(b.value()),
_ => Err(syn::Error::new(
self.value_span(),
"specta: expected boolean literal. Eg. `true` or `false`",
)),
}
}
pub fn parse_path(&self) -> Result<Path> {
match &self.value {
Some(AttributeValue::Path(path)) => Ok(path.clone()),
_ => Err(syn::Error::new(
self.value_span(),
"specta: expected path. Eg. `String` or `std::string::String`",
)),
}
}
pub fn parse_inflection(&self) -> Result<Inflection> {
match &self.value {
Some(AttributeValue::Lit(Lit::Str(lit))) => {
Ok(match lit.value().to_lowercase().replace('_', "").as_str() {
"lowercase" => Inflection::Lower,
"uppercase" => Inflection::Upper,
"camelcase" => Inflection::Camel,
"snakecase" => Inflection::Snake,
"pascalcase" => Inflection::Pascal,
"screamingsnakecase" => Inflection::ScreamingSnake,
_ => {
return Err(syn::Error::new_spanned(
lit,
"specta: found string literal containing an unsupported inflection",
))
}
})
}
_ => Err(syn::Error::new(
self.value_span(),
"specta: expected string literal containing an inflection",
)),
}
}
}
impl Parse for Attribute {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
root_ident: Ident::new("TEMP", input.span()),
key: input.call(Ident::parse_any)?,
value: match input.peek(Token![=]) {
true => {
input.parse::<Token![=]>()?;
Some(input.parse()?)
}
false => None,
},
})
}
}
pub fn parse_attrs(attrs: &[syn::Attribute]) -> syn::Result<Vec<Attribute>> {
Ok(attrs
.iter()
.map(|attr| {
let ident = attr
.path
.get_ident()
.expect("Attribute path must be an ident")
.clone();
if !(ident == "specta" || ident == "serde" || ident == "doc") {
return Ok(vec![]);
}
if ident == "doc" {
let meta = attr.parse_meta()?;
return match meta {
syn::Meta::NameValue(value) => Ok(vec![Attribute {
root_ident: ident.clone(),
key: ident,
value: Some(AttributeValue::Lit(value.lit)),
}]),
_ => Err(syn::Error::new(meta.span(), "specta: invalid doc comment")),
};
}
Ok(attr
.parse_args_with(Punctuated::<Attribute, Token![,]>::parse_terminated)?
.into_iter()
.map(|a| Attribute {
root_ident: ident.clone(),
..a
})
.collect::<Vec<_>>())
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.flatten()
.collect())
}
macro_rules! impl_parse {
($i:ident ($attr_parser:ident, $out:ident) { $($k:pat => $e:expr),* $(,)? }) => {
impl $i {
fn try_from_attrs(
ident: &'static str,
attrs: &mut Vec<crate::utils::Attribute>,
$out: &mut Self,
) -> syn::Result<()> {
use itertools::{Either, Itertools};
let (filtered_attrs, mut rest): (Vec<_>, Vec<_>) = std::mem::take(attrs)
.into_iter()
.partition_map(|attr| match attr.root_ident == ident {
true => Either::Left(attr),
false => Either::Right(attr),
});
let mut new_attrs = filtered_attrs
.into_iter()
.map(|$attr_parser| {
let mut was_passed_by_user = true;
match $attr_parser.key.to_string().as_str() {
$($k => $e,)*
#[allow(unreachable_patterns)]
_ => {
was_passed_by_user = false;
}
}
Ok(($attr_parser, was_passed_by_user))
})
.collect::<syn::Result<Vec<(Attribute, bool)>>>()?
.into_iter()
.filter_map(|(attr, was_passed_by_user)| {
if was_passed_by_user {
None
} else {
Some(attr)
}
})
.collect::<Vec<Attribute>>();
new_attrs.append(&mut rest);
let _ = std::mem::replace(attrs, new_attrs);
Ok(())
}
}
};
}
pub fn unraw_raw_ident(ident: &Ident) -> String {
let ident = ident.to_string();
if ident.starts_with("r#") {
ident.trim_start_matches("r#").to_owned()
} else {
ident
}
}
#[derive(Copy, Clone)]
pub enum Inflection {
Lower,
Upper,
Camel,
Snake,
Pascal,
ScreamingSnake,
}
impl Inflection {
pub fn apply(self, string: &str) -> String {
use inflector::Inflector;
match self {
Inflection::Lower => string.to_lowercase(),
Inflection::Upper => string.to_uppercase(),
Inflection::Camel => string.to_camel_case(),
Inflection::Snake => string.to_snake_case(),
Inflection::Pascal => string.to_pascal_case(),
Inflection::ScreamingSnake => string.to_screaming_snake_case(),
}
}
}
#[cfg(feature = "functions")]
pub fn format_fn_wrapper(function: &Ident) -> Ident {
quote::format_ident!("__specta__fn__{}", function)
}