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