1#![allow(missing_docs)]
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{Data, DeriveInput, Fields, parse_macro_input};
5
6#[proc_macro_derive(StringNewtype)]
25pub fn derive_string_newtype(input: TokenStream) -> TokenStream {
26 let input = parse_macro_input!(input as DeriveInput);
27 impl_string_newtype(&input).unwrap_or_else(|err| err.to_compile_error().into())
28}
29
30fn impl_string_newtype(input: &DeriveInput) -> syn::Result<TokenStream> {
31 let name = &input.ident;
32
33 let field_type = match &input.data {
35 Data::Struct(data) => match &data.fields {
36 Fields::Unnamed(fields) => {
37 if fields.unnamed.len() != 1 {
38 return Err(syn::Error::new_spanned(
39 name,
40 "StringNewtype requires a tuple struct with exactly one field",
41 ));
42 }
43 let field = fields.unnamed.first().unwrap();
44 &field.ty
45 }
46 _ => {
47 return Err(syn::Error::new_spanned(
48 name,
49 "StringNewtype can only be derived for tuple structs",
50 ));
51 }
52 },
53 _ => {
54 return Err(syn::Error::new_spanned(
55 name,
56 "StringNewtype can only be derived for structs",
57 ));
58 }
59 };
60
61 if !is_string_type(field_type) {
63 return Err(syn::Error::new_spanned(
64 field_type,
65 "StringNewtype requires the inner type to be String",
66 ));
67 }
68
69 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
70
71 let output = quote! {
72 impl #impl_generics #name #ty_generics #where_clause {
73 pub fn new(value: impl Into<String>) -> Self {
75 Self(value.into())
76 }
77
78 pub fn as_str(&self) -> &str {
80 &self.0
81 }
82
83 pub fn into_inner(self) -> String {
85 self.0
86 }
87 }
88
89 impl #impl_generics std::ops::Deref for #name #ty_generics #where_clause {
90 type Target = str;
91
92 fn deref(&self) -> &Self::Target {
93 &self.0
94 }
95 }
96
97 impl #impl_generics std::borrow::Borrow<str> for #name #ty_generics #where_clause {
98 fn borrow(&self) -> &str {
99 &self.0
100 }
101 }
102
103 impl #impl_generics AsRef<str> for #name #ty_generics #where_clause {
104 fn as_ref(&self) -> &str {
105 &self.0
106 }
107 }
108
109 impl #impl_generics std::fmt::Display for #name #ty_generics #where_clause {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 self.0.fmt(f)
112 }
113 }
114
115 impl #impl_generics From<String> for #name #ty_generics #where_clause {
116 fn from(value: String) -> Self {
117 Self(value)
118 }
119 }
120
121 impl #impl_generics From<&str> for #name #ty_generics #where_clause {
122 fn from(value: &str) -> Self {
123 Self(value.to_string())
124 }
125 }
126
127 impl #impl_generics From<#name #ty_generics> for String #where_clause {
128 fn from(value: #name #ty_generics) -> Self {
129 value.0
130 }
131 }
132 };
133
134 Ok(output.into())
135}
136
137fn is_string_type(ty: &syn::Type) -> bool {
138 if let syn::Type::Path(type_path) = ty
139 && type_path.qself.is_none()
140 && type_path.path.segments.len() == 1
141 {
142 return type_path.path.segments[0].ident == "String";
143 }
144 false
145}