string_types_macro/
lib.rs1use 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#[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
55fn 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
70fn string_type_impl_nonstring(name: &Ident, ftype: &Field) -> TokenStream2 {
73 quote!{
74 impl StringType for #name {
75 type Inner = #ftype;
76
77 fn as_str(&self) -> &str { self.0.as_str() }
79
80 fn as_inner(&self) -> &Self::Inner { &self.0 }
82
83 fn to_inner(self) -> Self::Inner { self.0 }
85 }
86 }
87}
88
89fn string_type_impl_string(name: &Ident, ftype: &Field) -> TokenStream2 {
91 quote!{
92 impl StringType for #name {
93 type Inner = #ftype;
94
95 fn as_str(&self) -> &str { &self.0 }
97
98 fn as_inner(&self) -> &Self::Inner { &self.0 }
100
101 fn to_inner(self) -> Self::Inner { self.0 }
103 }
104 }
105}
106
107fn 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
116fn 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
130fn 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
145fn 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
161fn 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#[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#[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}