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. |
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 | |
36 | impl Deref for PathList { |
37 | type Target = Vec<Path>; |
38 | |
39 | fn deref(&self) -> &Self::Target { |
40 | &self.0 |
41 | } |
42 | } |
43 | |
44 | impl From<Vec<Path>> for PathList { |
45 | fn from(v: Vec<Path>) -> Self { |
46 | PathList(v) |
47 | } |
48 | } |
49 | |
50 | impl 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)] |
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. |
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 | |