1 use std::ops::Deref; 2 3 use syn::{Meta, Path}; 4 5 use crate::ast::NestedMeta; 6 use crate::{Error, FromMeta, Result}; 7 8 use super::path_to_string; 9 10 /// A list of `syn::Path` instances. This type is used to extract a list of paths from an 11 /// attribute. 12 /// 13 /// # Usage 14 /// An `PathList` field on a struct implementing `FromMeta` will turn `#[builder(derive(serde::Debug, Clone))]` into: 15 /// 16 /// ```rust,ignore 17 /// StructOptions { 18 /// derive: PathList(vec![syn::Path::new("serde::Debug"), syn::Path::new("Clone")]) 19 /// } 20 /// ``` 21 #[derive(Debug, Default, Clone, PartialEq, Eq)] 22 pub struct PathList(Vec<Path>); 23 24 impl PathList { 25 /// Create a new list. new<T: Into<Path>>(vals: Vec<T>) -> Self26 pub fn new<T: Into<Path>>(vals: Vec<T>) -> Self { 27 PathList(vals.into_iter().map(T::into).collect()) 28 } 29 30 /// Create a new `Vec` containing the string representation of each path. to_strings(&self) -> Vec<String>31 pub fn to_strings(&self) -> Vec<String> { 32 self.0.iter().map(path_to_string).collect() 33 } 34 } 35 36 impl Deref for PathList { 37 type Target = Vec<Path>; 38 deref(&self) -> &Self::Target39 fn deref(&self) -> &Self::Target { 40 &self.0 41 } 42 } 43 44 impl From<Vec<Path>> for PathList { from(v: Vec<Path>) -> Self45 fn from(v: Vec<Path>) -> Self { 46 PathList(v) 47 } 48 } 49 50 impl FromMeta for PathList { from_list(v: &[NestedMeta]) -> Result<Self>51 fn from_list(v: &[NestedMeta]) -> Result<Self> { 52 let mut paths = Vec::with_capacity(v.len()); 53 for nmi in v { 54 if let NestedMeta::Meta(Meta::Path(ref path)) = *nmi { 55 paths.push(path.clone()); 56 } else { 57 return Err(Error::unexpected_type("non-word").with_span(nmi)); 58 } 59 } 60 61 Ok(PathList(paths)) 62 } 63 } 64 65 #[cfg(test)] 66 mod tests { 67 use super::PathList; 68 use crate::FromMeta; 69 use proc_macro2::TokenStream; 70 use quote::quote; 71 use syn::{parse_quote, Attribute, Meta}; 72 73 /// parse a string as a syn::Meta instance. pm(tokens: TokenStream) -> ::std::result::Result<Meta, String>74 fn pm(tokens: TokenStream) -> ::std::result::Result<Meta, String> { 75 let attribute: Attribute = parse_quote!(#[#tokens]); 76 Ok(attribute.meta) 77 } 78 fm<T: FromMeta>(tokens: TokenStream) -> T79 fn fm<T: FromMeta>(tokens: TokenStream) -> T { 80 FromMeta::from_meta(&pm(tokens).expect("Tests should pass well-formed input")) 81 .expect("Tests should pass valid input") 82 } 83 84 #[test] succeeds()85 fn succeeds() { 86 let paths = fm::<PathList>(quote!(ignore(Debug, Clone, Eq))); 87 assert_eq!( 88 paths.to_strings(), 89 vec![ 90 String::from("Debug"), 91 String::from("Clone"), 92 String::from("Eq") 93 ] 94 ); 95 } 96 97 /// Check that the parser rejects non-word members of the list, and that the error 98 /// has an associated span. 99 #[test] fails_non_word()100 fn fails_non_word() { 101 let input = PathList::from_meta(&pm(quote!(ignore(Debug, Clone = false))).unwrap()); 102 let err = input.unwrap_err(); 103 assert!(err.has_span()); 104 } 105 } 106