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 to_inner(self) -> Self::Inner { self.0 }
81 }
82 }
83}
84
85fn string_type_impl_string(name: &Ident, ftype: &Field) -> TokenStream2 {
87 quote!{
88 impl StringType for #name {
89 type Inner = #ftype;
90
91 fn as_str(&self) -> &str { &self.0 }
93
94 fn to_inner(self) -> Self::Inner { self.0 }
96 }
97 }
98}
99
100fn 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
109fn 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
123fn 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
138fn 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
154fn 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#[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#[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}