string_types_macro/
lib.rs

1//! Define macros that simplify the creation of `StringType` structs, by handling most of the
2//! boilerplate.
3//!
4//! `#[string_type]` supplies the following implementations:
5//!
6//! - `StringType`
7//! - `From<Type>` for `String`
8//! - `From<Type>` for _Inner_ if not a `String`.
9//!
10//! The `FromStr` derive macro supplies a reasonable implementation depending on the `EnsureValid`
11//! trait.
12use proc_macro::TokenStream;
13use proc_macro2::{Span, TokenStream as TokenStream2};
14use quote::quote;
15use syn::{self,
16    Ident,
17    parse_macro_input,
18    Data, DeriveInput, Field, Fields,
19};
20
21/// Generate the base implementations for a `StringType` including the `StringType` trait and
22/// the appropriate `From<T>` implementations.
23#[proc_macro_attribute]
24pub fn string_type(_attr: TokenStream, item: TokenStream) -> TokenStream {
25    let item_as_stream: TokenStream2 = item.clone().into();
26
27    let ast = parse_macro_input!(item as DeriveInput);
28    let name = ast.ident;
29
30    let ftype = match retrieve_field(&ast.data) {
31        Ok(field) => field,
32        Err(err) => return syn::Error::new(Span::call_site(), err).to_compile_error().into(),
33    };
34
35    let impls = if is_string_field(&ftype.ty) {
36        vec![
37            string_type_impl_string(&name, &ftype),
38            from_impl_string(&name),
39        ]
40    }
41    else {
42        vec![
43            string_type_impl_nonstring(&name, &ftype),
44            from_impl_nonstring(&name),
45        ]
46    };
47    quote!{
48        #item_as_stream
49
50        #(#impls)*
51    }.into()
52}
53
54// Examine the supplied type to determine if it is a `String`.
55fn is_string_field(ty: &syn::Type) -> bool {
56    if let syn::Type::Path(tp) = ty {
57        let segments = &tp.path.segments;
58        if segments.len() == 1 && &segments[0].ident == "String" {
59            return true;
60        }
61        if segments.len() == 3 && &segments[2].ident == "String" {
62            return &segments[0].ident == "std" && &segments[1].ident == "string";
63        }
64    }
65
66    false
67}
68
69// Generate the default `StringType` implementation for a struct with an inner type of
70// `StringType`.
71fn string_type_impl_nonstring(name: &Ident, ftype: &Field) -> TokenStream2 {
72    quote!{
73        impl StringType for #name {
74            type Inner = #ftype;
75
76            /// Return a string slice referencing the [`StringType`] inner value.
77            fn as_str(&self) -> &str { self.0.as_str() }
78
79            /// Return the inner value
80            fn to_inner(self) -> Self::Inner { self.0 }
81        }
82    }
83}
84
85// Generate the default `StringType` implementation for a struct with an inner type of `String`.
86fn string_type_impl_string(name: &Ident, ftype: &Field) -> TokenStream2 {
87    quote!{
88        impl StringType for #name {
89            type Inner = #ftype;
90
91            /// Return a string slice referencing the [`StringType`] inner value.
92            fn as_str(&self) -> &str { &self.0 }
93
94            /// Return the inner value
95            fn to_inner(self) -> Self::Inner { self.0 }
96        }
97    }
98}
99
100// Generate a `From<T>` implementation to convert to a `String` if the inner type is a `String`.
101fn from_impl_string(name: &Ident) -> TokenStream2 {
102    quote!{
103        impl From<#name> for String {
104            fn from(value: #name) -> String { value.to_inner() }
105        }
106    }
107}
108
109// Generate a `From<T>` implementation to convert to a `String` if the inner type is a
110// `StringType` and a `From<T>` to the inner type as well.
111fn from_impl_nonstring(name: &Ident) -> TokenStream2 {
112    quote!{
113        impl From<#name> for String {
114            fn from(value: #name) -> String { value.to_inner().into() }
115        }
116
117        impl From<#name> for <#name as StringType>::Inner {
118            fn from(value: #name) -> <#name as StringType>::Inner { value.to_inner() }
119        }
120    }
121}
122
123// Generarte an appropriate `FromStr` implementation for the type if the inner type is a
124// `String`
125fn from_str_impl_string(name: &Ident) -> TokenStream2 {
126    quote!{
127        impl std::str::FromStr for #name {
128            type Err = <Self as EnsureValid>::ParseErr;
129
130            fn from_str(s: &str) -> Result<Self, Self::Err> {
131                Self::ensure_valid(s)?;
132                Ok(Self(s.to_string()))
133            }
134        }
135    }
136}
137
138// Generarte an appropriate `FromStr` implementation for the type if the inner type is a
139// `StringType`
140fn from_str_impl_nonstring(name: &Ident) -> TokenStream2 {
141    quote!{
142        impl std::str::FromStr for #name {
143            type Err = <Self as EnsureValid>::ParseErr;
144
145            fn from_str(s: &str) -> Result<Self, Self::Err> {
146                let inner = s.parse::<<Self as StringType>::Inner>()?;
147                Self::ensure_valid(inner.as_str())?;
148                Ok(Self(inner))
149            }
150        }
151    }
152}
153
154// Given the data portion of a type definition, and return the inner field/type if appropriate.
155//
156// - Fails to compile if the type is not a struct
157// - Fails to compile if the struct is not a tuple struct
158// - Fails to compile if the struct does not have exactly 1 unnamed field.
159fn retrieve_field(data: &syn::Data) -> Result<&Field, &str> {
160    match data {
161        Data::Struct(data_struct) => {
162            match &data_struct.fields {
163                Fields::Named(_) => {
164                    return Err("No support for named fields in StringType");
165                }
166                Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
167                    Ok(fields.unnamed.first().unwrap())
168                },
169                Fields::Unnamed(fields) if fields.unnamed.is_empty() => {
170                    return Err("No support for unit struct in StringType");
171                },
172                Fields::Unit => {
173                    return Err("No support for unit struct in StringType");
174                },
175                Fields::Unnamed(_) => {
176                    return Err("No support for multiple unnamed fields in StringType");
177                },
178            }
179        },
180        Data::Enum(_) => return Err("No support for enum StringType"),
181        Data::Union(_) => return Err("No support for union StringType"),
182    }
183}
184
185/// Implement Derive macro for `FromStr` implementation
186/// Handles two variations: inner String and inner other-type
187#[proc_macro_derive(FromStr)]
188pub fn from_str(item: proc_macro::TokenStream) -> TokenStream {
189    let ast = parse_macro_input!(item as DeriveInput);
190    let name = ast.ident;
191    let ftype = match retrieve_field(&ast.data) {
192        Ok(field) => field,
193        Err(err) => return syn::Error::new(Span::call_site(), err).to_compile_error().into(),
194    };
195    if is_string_field(&ftype.ty) {
196        from_str_impl_string(&name).into()
197    }
198    else {
199        from_str_impl_nonstring(&name).into()
200    }
201}
202
203/// Implement Derive macro for `Display` implementation
204#[proc_macro_derive(Display)]
205pub fn display(item: proc_macro::TokenStream) -> TokenStream {
206    let ast = parse_macro_input!(item as DeriveInput);
207    let name = ast.ident;
208    quote!{
209        impl std::fmt::Display for #name {
210            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211                write!(f, "{}", self.as_str())
212            }
213        }
214    }.into()
215}