use quote::quote;
use std::default::Default;
use std::ops::Deref;
use syn::{
bracketed, parenthesized,
parse::{Parse, ParseBuffer},
punctuated::Punctuated,
Attribute, Expr, Field, Ident, LitStr, Token
};
use std::convert::TryFrom;
type SynResult<T> = Result<T, syn::Error>;
pub struct FieldAttribute<'a> {
field: &'a Ident,
attribute: GuzzleAttribute,
}
impl<'a> FieldAttribute<'a> {
pub fn get_arm_parts(&self) -> Vec<(&Ident, &LitStr, &Option<Expr>)> {
self.attribute.keyed_attribute()
.map(|keyed_attr| {
keyed_attr.keys
.iter()
.map(|matcher| (self.field, matcher, &keyed_attr.parser))
.collect()
})
.unwrap_or_default()
}
pub fn get_recursion(&self) -> Option<&Ident> {
self.attribute.recurse_attribute()
}
}
impl<'a> TryFrom<&'a Field> for FieldAttribute<'a> {
type Error = syn::Error;
fn try_from(field: &'a Field) -> SynResult<Self> {
let name_ident = field.ident.clone().unwrap();
let mut attribute = GuzzleAttribute::from_ident(&name_ident);
for attr in &field.attrs {
if let Some(new_attribute) = raw_attr_to_guzzle_attr(&name_ident, &attr)? {
attribute = new_attribute;
break;
}
}
let field = field.ident.as_ref().unwrap();
Ok(FieldAttribute { field, attribute })
}
}
fn raw_attr_to_guzzle_attr(ident: &Ident, attribute: &Attribute) -> SynResult<Option<GuzzleAttribute>> {
let path = &attribute.path;
let attr = match quote!(#path).to_string().as_ref() {
"guzzle" => {
let tokens = attribute.tokens.clone();
let mut keyed_attr: GuzzleKeyedAttribute = syn::parse2(tokens)?;
if keyed_attr.keys.is_empty() {
keyed_attr.keys = Keys::from_ident(ident);
}
Some(GuzzleAttribute::KeyedAttribute(keyed_attr))
}
"deep_guzzle" => Some(GuzzleAttribute::RecurseAttribute(ident.clone())),
"no_guzzle" => Some(GuzzleAttribute::NoGuzzle),
_ => None,
};
Ok(attr)
}
pub enum GuzzleAttribute {
KeyedAttribute(GuzzleKeyedAttribute),
RecurseAttribute(Ident),
NoGuzzle,
}
impl GuzzleAttribute {
fn from_ident(ident: &Ident) -> Self {
GuzzleAttribute::KeyedAttribute(
GuzzleKeyedAttribute::from_ident(ident)
)
}
pub fn keyed_attribute(&self) -> Option<&GuzzleKeyedAttribute> {
match self {
GuzzleAttribute::KeyedAttribute(attribute) => Some(attribute),
_ => None,
}
}
pub fn recurse_attribute(&self) -> Option<&Ident> {
match self {
GuzzleAttribute::RecurseAttribute(ident) => Some(ident),
_ => None,
}
}
}
#[derive(Default)]
pub struct GuzzleKeyedAttribute {
pub keys: Keys,
pub parser: Option<Expr>,
}
impl GuzzleKeyedAttribute {
pub fn from_ident(ident: &Ident) -> GuzzleKeyedAttribute {
GuzzleKeyedAttribute {
keys: Keys::from_ident(ident),
parser: None,
}
}
}
impl Parse for GuzzleKeyedAttribute {
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
let mut guzzle_attributes = GuzzleKeyedAttribute::default();
if input.peek(syn::token::Paren) {
let content;
parenthesized!(content in input);
let punctuated_attrs: Punctuated<RawGuzzleKeyedAttribute, Token![,]> =
content.parse_terminated(RawGuzzleKeyedAttribute::parse)?;
punctuated_attrs.into_iter().for_each(|attr| match attr {
RawGuzzleKeyedAttribute::Keys(keys) => guzzle_attributes.keys = keys,
RawGuzzleKeyedAttribute::Parser(parser) => guzzle_attributes.parser = Some(parser),
});
}
Ok(guzzle_attributes)
}
}
pub enum RawGuzzleKeyedAttribute {
Keys(Keys),
Parser(Expr),
}
impl Parse for RawGuzzleKeyedAttribute {
fn parse(input: &ParseBuffer) -> SynResult<Self> {
let name: Ident = input.parse()?;
let name_str = name.to_string();
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
match name_str.as_ref() {
"keys" => Ok(RawGuzzleKeyedAttribute::Keys(input.parse()?)),
"parser" => Ok(RawGuzzleKeyedAttribute::Parser(input.parse()?)),
_ => Err(input.error(format!("Unknown key: {}", name_str))),
}
} else {
Err(input.error("Attributes must be listed as `key = value`"))
}
}
}
#[derive(Default)]
pub struct Keys(Vec<LitStr>);
impl Keys {
pub fn from_ident(ident: &Ident) -> Keys {
Keys(vec![LitStr::new(ident.to_string().as_str(), ident.span())])
}
}
impl Parse for Keys {
fn parse(input: &ParseBuffer) -> Result<Self, syn::Error> {
let content;
bracketed!(content in input);
let parser = Punctuated::<LitStr, Token![,]>::parse_separated_nonempty;
let parsed_keys = parser(&content)?;
Ok(Keys(parsed_keys.into_iter().collect()))
}
}
impl Deref for Keys {
type Target = Vec<LitStr>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attr::{RawGuzzleKeyedAttribute, GuzzleKeyedAttribute};
use quote::quote;
use syn::{parse::Parser, parse2, punctuated::Punctuated, LitStr, Token};
#[test]
fn parse_lit_str() {
let token_stream = quote! { "single-key" };
let lit_str: LitStr = parse2(token_stream).unwrap();
assert_eq!("single-key".to_string(), lit_str.value());
}
#[test]
fn parse_separated_lit_str() {
let token_stream = quote! { "key1", "key2" };
let parser = Punctuated::<LitStr, Token![,]>::parse_separated_nonempty;
let punctuated_lit_str = parser.parse2(token_stream).unwrap();
let mut iter = punctuated_lit_str.iter();
assert_eq!("key1", iter.next().unwrap().value());
assert_eq!("key2", iter.next().unwrap().value());
assert!(iter.next().is_none());
}
#[test]
fn parse_slice_lit_str() -> Result<(), syn::Error> {
let token_stream = quote! { ["key1", "key2"] };
let keys: Keys = parse2(token_stream).unwrap();
let mut iter = keys.iter();
assert_eq!("key1", &iter.next().unwrap().value());
assert_eq!("key2", &iter.next().unwrap().value());
assert!(iter.next().is_none());
Ok(())
}
#[test]
fn parse_named_slice() -> Result<(), syn::Error> {
let token_stream = quote! { keys = ["key1", "key2"] };
let attribute: RawGuzzleKeyedAttribute = parse2(token_stream).unwrap();
let mut iter = match &attribute {
RawGuzzleKeyedAttribute::Keys(keys) => keys.iter(),
_ => panic!("attribute was not 'keys'"),
};
assert_eq!("key1", &iter.next().unwrap().value());
assert_eq!("key2", &iter.next().unwrap().value());
assert!(iter.next().is_none());
Ok(())
}
#[test]
fn parse_named_slices() -> Result<(), syn::Error> {
let token_stream = quote! { ( keys = ["key1", "key2"], keys = ["key3", "key4"] ) };
let attributes: GuzzleKeyedAttribute = parse2(token_stream).unwrap();
let mut iter = attributes.keys.iter();
assert_eq!("key3", &iter.next().unwrap().value());
assert_eq!("key4", &iter.next().unwrap().value());
assert!(iter.next().is_none());
Ok(())
}
fn test_parser(s: String) -> String {
s
}
#[test]
fn parse_parser() -> Result<(), syn::Error> {
let token_stream = quote! { parser = test_parser };
let _attribute: RawGuzzleKeyedAttribute = parse2(token_stream)?;
Ok(())
}
}