#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![deny(unused_must_use)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::todo)]
#![deny(clippy::unimplemented)]
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields, Type};
#[proc_macro_derive(Validated, attributes(valid))]
pub fn derive_validated(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand(&input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
fn expand(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let name = &input.ident;
if !input.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
&input.generics,
"#[derive(Validated)] does not support generic types; use a concrete newtype",
));
}
let field_ty = single_field_type(input)?;
let validator = validator_type(input)?;
Ok(quote! {
impl #name {
pub fn new(
value: #field_ty,
) -> ::core::result::Result<
Self,
<#validator as ::type_lib::Validator<#field_ty>>::Error,
> {
<#validator as ::type_lib::Validator<#field_ty>>::validate(&value)?;
::core::result::Result::Ok(#name(value))
}
#[must_use]
pub fn get(&self) -> &#field_ty {
&self.0
}
#[must_use]
pub fn into_inner(self) -> #field_ty {
self.0
}
}
impl ::core::ops::Deref for #name {
type Target = #field_ty;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl ::core::convert::AsRef<#field_ty> for #name {
fn as_ref(&self) -> &#field_ty {
&self.0
}
}
})
}
fn single_field_type(input: &DeriveInput) -> syn::Result<Type> {
match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
Ok(fields.unnamed[0].ty.clone())
}
_ => Err(syn::Error::new_spanned(
&input.ident,
"#[derive(Validated)] requires a tuple struct with exactly one field, \
e.g. `struct Name(String);`",
)),
},
_ => Err(syn::Error::new_spanned(
&input.ident,
"#[derive(Validated)] can only be applied to structs",
)),
}
}
fn validator_type(input: &DeriveInput) -> syn::Result<Type> {
let mut found: Option<Type> = None;
for attr in &input.attrs {
if attr.path().is_ident("valid") {
if found.is_some() {
return Err(syn::Error::new_spanned(
attr,
"duplicate #[valid(...)] attribute; specify exactly one",
));
}
found = Some(attr.parse_args::<Type>()?);
}
}
found.ok_or_else(|| {
syn::Error::new_spanned(
&input.ident,
"#[derive(Validated)] requires a #[valid(<Validator>)] attribute, \
e.g. `#[valid(NonEmpty)]`",
)
})
}