type_lib_derive/lib.rs
1//! Derive macro for [`type-lib`](https://docs.rs/type-lib).
2//!
3//! This crate provides the [`Validated`] derive. It is re-exported from
4//! `type-lib` behind the `derive` feature; depend on `type-lib` with that feature
5//! rather than on this crate directly:
6//!
7//! ```toml
8//! type-lib = { version = "0.9", features = ["derive"] }
9//! ```
10
11#![forbid(unsafe_code)]
12#![deny(missing_docs)]
13#![deny(unused_must_use)]
14#![deny(clippy::unwrap_used)]
15#![deny(clippy::expect_used)]
16#![deny(clippy::todo)]
17#![deny(clippy::unimplemented)]
18
19use proc_macro::TokenStream;
20use quote::quote;
21use syn::{parse_macro_input, Data, DeriveInput, Fields, Type};
22
23/// Turns a single-field newtype into a validated parse-dont-validate type.
24///
25/// Apply `#[derive(Validated)]` to a one-field tuple struct and annotate it with
26/// `#[valid(<Validator>)]`, where `<Validator>` is any type implementing
27/// [`Validator`](https://docs.rs/type-lib/latest/type_lib/trait.Validator.html)
28/// for the field's type — a built-in rule, a combinator, or your own.
29///
30/// The derive generates, on the struct:
31///
32/// - `fn new(value: T) -> Result<Self, <V as Validator<T>>::Error>` — validates
33/// `value` and wraps it, or returns the validator's error.
34/// - `fn get(&self) -> &T` and `fn into_inner(self) -> T`.
35/// - `Deref<Target = T>` and `AsRef<T>`.
36///
37/// The inner field stays private, so the only way to build the type from outside
38/// its module is through `new` — which is what makes the invariant trustworthy.
39/// Add ordinary derives (`Debug`, `Clone`, `PartialEq`, …) alongside as usual.
40///
41/// # Example
42///
43/// ```ignore
44/// use type_lib::combinator::And;
45/// use type_lib::rules::{LenRange, Trimmed};
46/// use type_lib::Validated;
47///
48/// #[derive(Validated)]
49/// #[valid(And<Trimmed, LenRange<3, 16>>)]
50/// pub struct Username(String);
51///
52/// let user = Username::new("alice".to_owned()).unwrap();
53/// assert_eq!(user.get(), "alice");
54/// assert!(Username::new(" ".to_owned()).is_err());
55/// ```
56///
57/// (The example is `ignore`d here because this crate cannot depend on `type-lib`;
58/// the compiled version runs from `type-lib` itself.)
59///
60/// # Compile errors
61///
62/// The derive reports a clear error when applied to anything other than a
63/// single-field tuple struct, when generics are present, or when the
64/// `#[valid(...)]` attribute is missing or duplicated.
65#[proc_macro_derive(Validated, attributes(valid))]
66pub fn derive_validated(input: TokenStream) -> TokenStream {
67 let input = parse_macro_input!(input as DeriveInput);
68 expand(&input)
69 .unwrap_or_else(syn::Error::into_compile_error)
70 .into()
71}
72
73fn expand(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
74 let name = &input.ident;
75
76 if !input.generics.params.is_empty() {
77 return Err(syn::Error::new_spanned(
78 &input.generics,
79 "#[derive(Validated)] does not support generic types; use a concrete newtype",
80 ));
81 }
82
83 let field_ty = single_field_type(input)?;
84 let validator = validator_type(input)?;
85
86 Ok(quote! {
87 impl #name {
88 /// Validates `value` against the configured rule and, on success,
89 /// wraps it.
90 ///
91 /// # Errors
92 ///
93 /// Returns the validator's error when `value` does not satisfy the
94 /// rule.
95 pub fn new(
96 value: #field_ty,
97 ) -> ::core::result::Result<
98 Self,
99 <#validator as ::type_lib::Validator<#field_ty>>::Error,
100 > {
101 <#validator as ::type_lib::Validator<#field_ty>>::validate(&value)?;
102 ::core::result::Result::Ok(#name(value))
103 }
104
105 /// Borrows the validated inner value.
106 #[must_use]
107 pub fn get(&self) -> &#field_ty {
108 &self.0
109 }
110
111 /// Consumes the wrapper and returns the inner value.
112 #[must_use]
113 pub fn into_inner(self) -> #field_ty {
114 self.0
115 }
116 }
117
118 impl ::core::ops::Deref for #name {
119 type Target = #field_ty;
120
121 fn deref(&self) -> &Self::Target {
122 &self.0
123 }
124 }
125
126 impl ::core::convert::AsRef<#field_ty> for #name {
127 fn as_ref(&self) -> &#field_ty {
128 &self.0
129 }
130 }
131 })
132}
133
134/// Extracts the type of the single tuple field, or errors.
135fn single_field_type(input: &DeriveInput) -> syn::Result<Type> {
136 match &input.data {
137 Data::Struct(data) => match &data.fields {
138 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
139 Ok(fields.unnamed[0].ty.clone())
140 }
141 _ => Err(syn::Error::new_spanned(
142 &input.ident,
143 "#[derive(Validated)] requires a tuple struct with exactly one field, \
144 e.g. `struct Name(String);`",
145 )),
146 },
147 _ => Err(syn::Error::new_spanned(
148 &input.ident,
149 "#[derive(Validated)] can only be applied to structs",
150 )),
151 }
152}
153
154/// Parses the validator type from the `#[valid(...)]` attribute, or errors.
155fn validator_type(input: &DeriveInput) -> syn::Result<Type> {
156 let mut found: Option<Type> = None;
157 for attr in &input.attrs {
158 if attr.path().is_ident("valid") {
159 if found.is_some() {
160 return Err(syn::Error::new_spanned(
161 attr,
162 "duplicate #[valid(...)] attribute; specify exactly one",
163 ));
164 }
165 found = Some(attr.parse_args::<Type>()?);
166 }
167 }
168 found.ok_or_else(|| {
169 syn::Error::new_spanned(
170 &input.ident,
171 "#[derive(Validated)] requires a #[valid(<Validator>)] attribute, \
172 e.g. `#[valid(NonEmpty)]`",
173 )
174 })
175}