Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start implementing support of derive(FromStr) for generic newtypes #149

Merged
merged 2 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 68 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dummy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ once_cell = "*"
lazy_static = "*"
ron = "0.8.1"
arbitrary = "1.3.2"
num = "0.4.3"
23 changes: 4 additions & 19 deletions dummy/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
use nutype::nutype;
use std::cmp::Ord;

#[nutype(
sanitize(with = |mut v| { v.sort(); v }),
validate(predicate = |vec| !vec.is_empty()),
derive(Debug, Deserialize, Serialize),
validate(predicate = |n| n.is_even()),
derive(Debug, FromStr),
)]
struct SortedNotEmptyVec<T: Ord>(Vec<T>);
struct Even<T: ::num::Integer>(T);

fn main() {
{
// Not empty vec is fine
let json = "[3, 1, 5, 2]";
let sv = serde_json::from_str::<SortedNotEmptyVec<i32>>(json).unwrap();
assert_eq!(sv.into_inner(), vec![1, 2, 3, 5]);
}
{
// Empty vec is not allowed
let json = "[]";
let result = serde_json::from_str::<SortedNotEmptyVec<i32>>(json);
assert!(result.is_err());
}
}
fn main() {}
2 changes: 1 addition & 1 deletion nutype_macros/src/any/gen/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ fn gen_implemented_traits(
AnyIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, generics, inner_type)),
AnyIrregularTrait::Borrow => Ok(gen_impl_trait_borrow(type_name, generics, inner_type)),
AnyIrregularTrait::FromStr => Ok(
gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref())
gen_impl_trait_from_str(type_name, generics, inner_type, maybe_error_type_name.as_ref())
),
AnyIrregularTrait::TryFrom => Ok(
gen_impl_trait_try_from(type_name, generics, inner_type, maybe_error_type_name.as_ref())
Expand Down
47 changes: 31 additions & 16 deletions nutype_macros/src/common/gen/parse_error.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use cfg_if::cfg_if;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::Generics;

use crate::common::models::{ErrorTypeName, InnerType, ParseErrorTypeName, TypeName};
use crate::common::{
gen::{add_bound_to_all_type_params, strip_trait_bounds_on_generics},
models::{ErrorTypeName, InnerType, ParseErrorTypeName, TypeName},
};

/// Generate a name for the error which is used for FromStr trait implementation.
pub fn gen_parse_error_name(type_name: &TypeName) -> ParseErrorTypeName {
Expand All @@ -13,26 +17,33 @@ pub fn gen_parse_error_name(type_name: &TypeName) -> ParseErrorTypeName {
/// Generate an error which is used for FromStr trait implementation of non-string types (e.g.
/// floats or integers)
pub fn gen_def_parse_error(
inner_type: impl Into<InnerType>,
type_name: &TypeName,
generics: &Generics,
inner_type: impl Into<InnerType>,
maybe_error_type_name: Option<&ErrorTypeName>,
parse_error_type_name: &ParseErrorTypeName,
) -> TokenStream {
let inner_type: InnerType = inner_type.into();
let type_name_str = type_name.to_string();

let generics_without_bounds = strip_trait_bounds_on_generics(generics);
let generics_with_fromstr_bound = add_bound_to_all_type_params(
&generics_without_bounds,
syn::parse_quote!(::core::str::FromStr<Err: ::core::fmt::Debug>),
);

let definition = if let Some(error_type_name) = maybe_error_type_name {
quote! {
#[derive(Debug)]
pub enum #parse_error_type_name {
Parse(<#inner_type as ::core::str::FromStr>::Err),
Validate(#error_type_name),
}
#[derive(Debug)] // #[derive(Debug)]
pub enum #parse_error_type_name #generics_with_fromstr_bound { // pub enum ParseErrorFoo<T: ::core::str::FromStr<Err: ::core::fmt::Debug>> {
Parse(<#inner_type as ::core::str::FromStr>::Err), // Parse(<Foo as ::core::str::FromStr>::Err),
Validate(#error_type_name), // Validate(ErrorFoo),
} // }

impl ::core::fmt::Display for #parse_error_type_name {
impl #generics_with_fromstr_bound ::core::fmt::Display for #parse_error_type_name #generics_without_bounds {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self {
#parse_error_type_name::Parse(err) => write!(f, "Failed to parse {}: {}", #type_name_str, err),
#parse_error_type_name::Parse(err) => write!(f, "Failed to parse {}: {:?}", #type_name_str, err),
#parse_error_type_name::Validate(err) => write!(f, "Failed to parse {}: {}", #type_name_str, err),
}

Expand All @@ -41,15 +52,15 @@ pub fn gen_def_parse_error(
}
} else {
quote! {
#[derive(Debug)]
pub enum #parse_error_type_name {
Parse(<#inner_type as ::core::str::FromStr>::Err),
}
#[derive(Debug)] // #[derive(Debug)
pub enum #parse_error_type_name #generics_with_fromstr_bound { // pub enum ParseErrorFoo<T: ::core::str::FromStr<Err: ::core::fmt::Debug>> {
Parse(<#inner_type as ::core::str::FromStr>::Err), // Parse(<Foo as ::core::str::FromStr>::Err),
} // }

impl ::core::fmt::Display for #parse_error_type_name {
impl #generics_with_fromstr_bound ::core::fmt::Display for #parse_error_type_name #generics_without_bounds {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self {
#parse_error_type_name::Parse(err) => write!(f, "Failed to parse {}: {}", #type_name_str, err),
#parse_error_type_name::Parse(err) => write!(f, "Failed to parse {}: {:?}", #type_name_str, err),
}
}
}
Expand All @@ -58,8 +69,12 @@ pub fn gen_def_parse_error(

cfg_if! {
if #[cfg(feature = "std")] {
let generics_with_fromstr_and_debug_bounds = add_bound_to_all_type_params(
&generics_with_fromstr_bound,
syn::parse_quote!(::core::fmt::Debug),
);
let impl_std_error = quote! {
impl ::std::error::Error for #parse_error_type_name {
impl #generics_with_fromstr_and_debug_bounds ::std::error::Error for #parse_error_type_name #generics_without_bounds {
fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
None
}
Expand Down
18 changes: 13 additions & 5 deletions nutype_macros/src/common/gen/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,25 +203,33 @@ pub fn gen_impl_trait_try_from(
/// Generate implementation of FromStr trait for non-string types (e.g. integers or floats).
pub fn gen_impl_trait_from_str(
type_name: &TypeName,
generics: &Generics,
inner_type: impl Into<InnerType>,
maybe_error_type_name: Option<&ErrorTypeName>,
) -> TokenStream {
let inner_type: InnerType = inner_type.into();
let parse_error_type_name = gen_parse_error_name(type_name);
let def_parse_error = gen_def_parse_error(
inner_type.clone(),
type_name,
generics,
inner_type.clone(),
maybe_error_type_name,
&parse_error_type_name,
);

let generics_without_bounds = strip_trait_bounds_on_generics(generics);
let generics_with_fromstr_bound = add_bound_to_all_type_params(
generics,
syn::parse_quote!(::core::str::FromStr<Err: ::core::fmt::Debug>),
);

if let Some(_error_type_name) = maybe_error_type_name {
// The case with validation
quote! {
#def_parse_error

impl ::core::str::FromStr for #type_name {
type Err = #parse_error_type_name;
impl #generics_with_fromstr_bound ::core::str::FromStr for #type_name #generics_without_bounds {
type Err = #parse_error_type_name #generics_without_bounds;

fn from_str(raw_string: &str) -> ::core::result::Result<Self, Self::Err> {
let raw_value: #inner_type = raw_string.parse().map_err(#parse_error_type_name::Parse)?;
Expand All @@ -234,8 +242,8 @@ pub fn gen_impl_trait_from_str(
quote! {
#def_parse_error

impl ::core::str::FromStr for #type_name {
type Err = #parse_error_type_name;
impl #generics_with_fromstr_bound ::core::str::FromStr for #type_name #generics_without_bounds {
type Err = #parse_error_type_name #generics_without_bounds;

fn from_str(raw_string: &str) -> ::core::result::Result<Self, Self::Err> {
let value: #inner_type = raw_string.parse().map_err(#parse_error_type_name::Parse)?;
Expand Down
2 changes: 1 addition & 1 deletion nutype_macros/src/float/gen/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ fn gen_implemented_traits<T: ToTokens>(
FloatIrregularTrait::AsRef => Ok(gen_impl_trait_as_ref(type_name, generics, inner_type)),
FloatIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, generics, inner_type)),
FloatIrregularTrait::FromStr => {
Ok(gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref()))
Ok(gen_impl_trait_from_str(type_name, generics, inner_type, maybe_error_type_name.as_ref()))
}
FloatIrregularTrait::From => Ok(gen_impl_trait_from(type_name, generics, inner_type)),
FloatIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, generics, inner_type)),
Expand Down
2 changes: 1 addition & 1 deletion nutype_macros/src/integer/gen/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ fn gen_implemented_traits<T: ToTokens>(
IntegerIrregularTrait::AsRef => Ok(gen_impl_trait_as_ref(type_name, generics, inner_type)),
IntegerIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, generics, inner_type)),
IntegerIrregularTrait::FromStr => {
Ok(gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref()))
Ok(gen_impl_trait_from_str(type_name, generics, inner_type, maybe_error_type_name.as_ref()))
}
IntegerIrregularTrait::From => Ok(gen_impl_trait_from(type_name, generics, inner_type)),
IntegerIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, generics, inner_type)),
Expand Down
1 change: 1 addition & 0 deletions test_suite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ arbitrary = "1.3.0"
arbtest = "0.2.0"
ron = "0.8.1"
rmp-serde = "1.1.2"
num = "0.4.3"

[features]
serde = ["nutype/serde", "dep:serde", "dep:serde_json"]
Expand Down
Loading
Loading