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