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        #[derive(Debug, Clone)]
49        #item_as_stream
50
51        #(#impls)*
52    }.into()
53}
54
55// Examine the supplied type to determine if it is a `String`.
56fn is_string_field(ty: &syn::Type) -> bool {
57    if let syn::Type::Path(tp) = ty {
58        let segments = &tp.path.segments;
59        if segments.len() == 1 && &segments[0].ident == "String" {
60            return true;
61        }
62        if segments.len() == 3 && &segments[2].ident == "String" {
63            return &segments[0].ident == "std" && &segments[1].ident == "string";
64        }
65    }
66
67    false
68}
69
70// Generate the default `StringType` implementation for a struct with an inner type of
71// `StringType`.
72fn string_type_impl_nonstring(name: &Ident, ftype: &Field) -> TokenStream2 {
73    quote!{
74        impl StringType for #name {
75            type Inner = #ftype;
76
77            /// Return a string slice referencing the [`StringType`] inner value.
78            fn as_str(&self) -> &str { self.0.as_str() }
79
80            /// Return an immutable reference to the `Inner` value.
81            fn as_inner(&self) -> &Self::Inner { &self.0 }
82
83            /// Return the inner value
84            fn to_inner(self) -> Self::Inner { self.0 }
85        }
86    }
87}
88
89// Generate the default `StringType` implementation for a struct with an inner type of `String`.
90fn string_type_impl_string(name: &Ident, ftype: &Field) -> TokenStream2 {
91    quote!{
92        impl StringType for #name {
93            type Inner = #ftype;
94
95            /// Return a string slice referencing the [`StringType`] inner value.
96            fn as_str(&self) -> &str { &self.0 }
97
98            /// Return an immutable reference to the `Inner` value.
99            fn as_inner(&self) -> &Self::Inner { &self.0 }
100
101            /// Return the inner value
102            fn to_inner(self) -> Self::Inner { self.0 }
103        }
104    }
105}
106
107// Generate a `From<T>` implementation to convert to a `String` if the inner type is a `String`.
108fn from_impl_string(name: &Ident) -> TokenStream2 {
109    quote!{
110        impl From<#name> for String {
111            fn from(value: #name) -> String { value.to_inner() }
112        }
113    }
114}
115
116// Generate a `From<T>` implementation to convert to a `String` if the inner type is a
117// `StringType` and a `From<T>` to the inner type as well.
118fn from_impl_nonstring(name: &Ident) -> TokenStream2 {
119    quote!{
120        impl From<#name> for String {
121            fn from(value: #name) -> String { value.to_inner().into() }
122        }
123
124        impl From<#name> for <#name as StringType>::Inner {
125            fn from(value: #name) -> <#name as StringType>::Inner { value.to_inner() }
126        }
127    }
128}
129
130// Generarte an appropriate `FromStr` implementation for the type if the inner type is a
131// `String`
132fn from_str_impl_string(name: &Ident) -> TokenStream2 {
133    quote!{
134        impl std::str::FromStr for #name {
135            type Err = <Self as EnsureValid>::ParseErr;
136
137            fn from_str(s: &str) -> Result<Self, Self::Err> {
138                Self::ensure_valid(s)?;
139                Ok(Self(s.to_string()))
140            }
141        }
142    }
143}
144
145// Generarte an appropriate `FromStr` implementation for the type if the inner type is a
146// `StringType`
147fn from_str_impl_nonstring(name: &Ident) -> TokenStream2 {
148    quote!{
149        impl std::str::FromStr for #name {
150            type Err = <Self as EnsureValid>::ParseErr;
151
152            fn from_str(s: &str) -> Result<Self, Self::Err> {
153                let inner = s.parse::<<Self as StringType>::Inner>()?;
154                Self::ensure_valid(inner.as_str())?;
155                Ok(Self(inner))
156            }
157        }
158    }
159}
160
161// Given the data portion of a type definition, and return the inner field/type if appropriate.
162//
163// - Fails to compile if the type is not a struct
164// - Fails to compile if the struct is not a tuple struct
165// - Fails to compile if the struct does not have exactly 1 unnamed field.
166fn retrieve_field(data: &syn::Data) -> Result<&Field, &str> {
167    match data {
168        Data::Struct(data_struct) => {
169            match &data_struct.fields {
170                Fields::Named(_) => {
171                    Err("No support for named fields in StringType")
172                }
173                Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
174                    Ok(fields.unnamed.first().unwrap())
175                },
176                Fields::Unnamed(fields) if fields.unnamed.is_empty() => {
177                    Err("No support for unit struct in StringType")
178                },
179                Fields::Unit => {
180                    Err("No support for unit struct in StringType")
181                },
182                Fields::Unnamed(_) => {
183                    Err("No support for multiple unnamed fields in StringType")
184                },
185            }
186        },
187        Data::Enum(_) => Err("No support for enum StringType"),
188        Data::Union(_) => Err("No support for union StringType"),
189    }
190}
191
192/// Implement Derive macro for `FromStr` implementation
193/// Handles two variations: inner String and inner other-type
194#[proc_macro_derive(FromStr)]
195pub fn from_str(item: proc_macro::TokenStream) -> TokenStream {
196    let ast = parse_macro_input!(item as DeriveInput);
197    let name = ast.ident;
198    let ftype = match retrieve_field(&ast.data) {
199        Ok(field) => field,
200        Err(err) => return syn::Error::new(Span::call_site(), err).to_compile_error().into(),
201    };
202    if is_string_field(&ftype.ty) {
203        from_str_impl_string(&name).into()
204    }
205    else {
206        from_str_impl_nonstring(&name).into()
207    }
208}
209
210/// Implement Derive macro for `Display` implementation
211#[proc_macro_derive(Display)]
212pub fn display(item: proc_macro::TokenStream) -> TokenStream {
213    let ast = parse_macro_input!(item as DeriveInput);
214    let name = ast.ident;
215    quote!{
216        impl std::fmt::Display for #name {
217            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218                write!(f, "{}", self.as_str())
219            }
220        }
221    }.into()
222}