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 #item_as_stream
49
50 #(#impls)*
51 }.into()
52}
53
54fn 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
69fn string_type_impl_nonstring(name: &Ident, ftype: &Field) -> TokenStream2 {
72 quote!{
73 impl StringType for #name {
74 type Inner = #ftype;
75
76 fn as_str(&self) -> &str { self.0.as_str() }
78
79 fn as_inner(&self) -> &Self::Inner { &self.0 }
81
82 fn to_inner(self) -> Self::Inner { self.0 }
84 }
85 }
86}
87
88fn string_type_impl_string(name: &Ident, ftype: &Field) -> TokenStream2 {
90 quote!{
91 impl StringType for #name {
92 type Inner = #ftype;
93
94 fn as_str(&self) -> &str { &self.0 }
96
97 fn as_inner(&self) -> &Self::Inner { &self.0 }
99
100 fn to_inner(self) -> Self::Inner { self.0 }
102 }
103 }
104}
105
106fn 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
115fn 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
129fn 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
144fn 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
160fn 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#[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#[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}