Prae v0.8 release - validating your types was never that easy!

Hello everyone! My crate prae just got a huge update! It is now much more flexible and lightweight.

For those who don't know (most of you), prae is a crate that aims to provide a better way to define types that require validation, like this:

use prae::Wrapper;

prae::define! {
    #[derive(Debug)]
    pub Username: String;
    adjust |un| *username = un.trim().to_owned();
    ensure |un| !un.is_empty();
}

let un = Username::new(" my username ").unwrap();
assert_eq!(un.get(), "my username");

let err = Username::new("  ").unwrap_err();
assert_eq!(err.original, "value is invalid");
assert_eq!(err.value, "");

The most important changes:

  • The crate provides two declarative macros instead of one procedural. This means bye-bye to syn and quote and hello to faster compilation times. The syntax of macros is also a bit nicer.
  • The API was greatly simplified:
    • Previously it provided two important types: trait Bound and struct Bounded. When user invoked macro, it generated a unit struct, implemented Bound for it and created a type alias to the Bounded struct (basically generated two types: the one you would use and a helper one).
    • Now it provides one important type: trait Wrapper. When user invokes macro, it generates a struct wrapper (like in Newtype pattern) and implements the Wrapper trait for it (actually, it also implements AsRef, Borrow, From and TryFrom). Since generated type is local (there's no type alias), you can basically do with it anything you can do with a tuple struct, for example use attribute macros and add custom impl blocks.

Since the generated type is so customisable, you now can specify so-called "plugins" during creation of the type. These plugins are just macros that receive the name of the type and do anything they want with it. For example, prae provides a impl_serde plugin under the serde feature. This plugin implements serde::Serialize and serde::Deserialize for your type, with a note that the implementation of Deserialize will fail if you'll try to deserialize an invalid value.

Here is an example for how to create a type that can be used as async-graphql::Scalar using plugins:

prae::define! {
    pub Username: String;
    plugins: [
        prae::impl_serde,
        async_graphql::scalar,
    ]
}

The crate no longer implements every possible trait for the generated type (for example, previously if you created a wrapper around a type that implemented Clone, your wrapper would also implement Clone). Instead, it encourages you to implement only the things you need (using attribute macros, plugins or manual impls).

Please, give that library a try! I would love to hear feedback.

2 Likes