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 Some(field) = fields.unnamed.first() else {
44 return Err(syn::Error::new_spanned(
45 name,
46 "StringNewtype requires a tuple struct with exactly one field",
47 ));
48 };
49 &field.ty
50 }
51 _ => {
52 return Err(syn::Error::new_spanned(name, "StringNewtype can only be derived for tuple structs"));
53 }
54 },
55 _ => {
56 return Err(syn::Error::new_spanned(name, "StringNewtype can only be derived for structs"));
57 }
58 };
59
60 if !is_string_type(field_type) {
62 return Err(syn::Error::new_spanned(field_type, "StringNewtype requires the inner type to be String"));
63 }
64
65 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
66
67 let output = quote! {
68 impl #impl_generics #name #ty_generics #where_clause {
69 pub fn new(value: impl Into<String>) -> Self {
71 Self(value.into())
72 }
73
74 pub fn as_str(&self) -> &str {
76 &self.0
77 }
78
79 pub fn into_inner(self) -> String {
81 self.0
82 }
83 }
84
85 impl #impl_generics std::ops::Deref for #name #ty_generics #where_clause {
86 type Target = str;
87
88 fn deref(&self) -> &Self::Target {
89 &self.0
90 }
91 }
92
93 impl #impl_generics std::borrow::Borrow<str> for #name #ty_generics #where_clause {
94 fn borrow(&self) -> &str {
95 &self.0
96 }
97 }
98
99 impl #impl_generics AsRef<str> for #name #ty_generics #where_clause {
100 fn as_ref(&self) -> &str {
101 &self.0
102 }
103 }
104
105 impl #impl_generics std::fmt::Display for #name #ty_generics #where_clause {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 self.0.fmt(f)
108 }
109 }
110
111 impl #impl_generics From<String> for #name #ty_generics #where_clause {
112 fn from(value: String) -> Self {
113 Self(value)
114 }
115 }
116
117 impl #impl_generics From<&str> for #name #ty_generics #where_clause {
118 fn from(value: &str) -> Self {
119 Self(value.to_string())
120 }
121 }
122
123 impl #impl_generics From<#name #ty_generics> for String #where_clause {
124 fn from(value: #name #ty_generics) -> Self {
125 value.0
126 }
127 }
128 };
129
130 Ok(output.into())
131}
132
133fn is_string_type(ty: &syn::Type) -> bool {
134 if let syn::Type::Path(type_path) = ty
135 && type_path.qself.is_none()
136 && type_path.path.segments.len() == 1
137 {
138 return type_path.path.segments[0].ident == "String";
139 }
140 false
141}