Skip to main content

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}