1use std::ops::Deref;
2
3use syn::{Meta, Path};
4
5use crate::ast::NestedMeta;
6use crate::{Error, FromMeta, Result};
7
8use 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)]
22pub struct PathList(Vec<Path>);
23
24impl PathList {
25 /// Create a new list.
26 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.
31 pub fn to_strings(&self) -> Vec<String> {
32 self.0.iter().map(path_to_string).collect()
33 }
34}
35
36impl Deref for PathList {
37 type Target = Vec<Path>;
38
39 fn deref(&self) -> &Self::Target {
40 &self.0
41 }
42}
43
44impl From<Vec<Path>> for PathList {
45 fn from(v: Vec<Path>) -> Self {
46 PathList(v)
47 }
48}
49
50impl FromMeta for PathList {
51 fn from_list(v: &[NestedMeta]) -> Result<Self> {
52 let mut paths: Vec = Vec::with_capacity(v.len());
53 for nmi: &NestedMeta in v {
54 if let NestedMeta::Meta(Meta::Path(ref path: &Path)) = *nmi {
55 paths.push(path.clone());
56 } else {
57 return Err(Error::unexpected_type("non-word").with_span(node:nmi));
58 }
59 }
60
61 Ok(PathList(paths))
62 }
63}
64
65#[cfg(test)]
66mod 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.
74 fn pm(tokens: TokenStream) -> ::std::result::Result<Meta, String> {
75 let attribute: Attribute = parse_quote!(#[#tokens]);
76 Ok(attribute.meta)
77 }
78
79 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]
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]
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