1 | use std::ops::Deref; |
2 | |
3 | use syn::{Meta, NestedMeta, Path}; |
4 | |
5 | use crate::{Error, FromMeta, Result}; |
6 | |
7 | use 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)] |
21 | pub struct PathList(Vec<Path>); |
22 | |
23 | impl 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 | |
35 | impl Deref for PathList { |
36 | type Target = Vec<Path>; |
37 | |
38 | fn deref(&self) -> &Self::Target { |
39 | &self.0 |
40 | } |
41 | } |
42 | |
43 | impl From<Vec<Path>> for PathList { |
44 | fn from(v: Vec<Path>) -> Self { |
45 | PathList(v) |
46 | } |
47 | } |
48 | |
49 | impl 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)] |
65 | mod 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 | |