A ReasonML library for opaque data types with validation.
At runtime, they are just strings. But since each of them is a distinct module, they form distinct types; so you can never e.g. mix up an Email with a Name. In addition, you can set up validation rules and guarantee (short of escape hatches like Obj.magic
) that all strings in the type will have been validated.
Below we create three string-like modules, UserName
, Email
, and MessageText
. Each has its own validation logic (or lack thereof).
open Opaque.String;
module UserName: StringType = Make(
Validation.Compose(
Validation.MinLength({let n = 10;}),
Validation.MaxLength({let n = 80;}),
), ()
);
module Email = Opaque.String.Make(Opaque.RegexValidation({
let regex = [%re {|/magic email regex/|}];
}, ()));
// This has no validation (although in practice a max length might be good)
module MessageText = Opaque.String.Make(String.NoValidation);
We can't create a username that's less than 5 characters:
// raises `TooShort("bad", 10)`
let badUsername = "bad"->UserName.fromString;
Or an email that doesn't match our regex:
// raises `RegexMatchError("i am not an email", <some regex>)`
let badEmail = "i am not an email"->Email.fromString;
This guarantee means that you have total confidence that you won't be handling invalid data, and pushes error boundaries as early as possible.
The fromString
function will raise an exn
exceptions to on failure. Different validators raise different exceptions, so you can switch on them with try
or | exception _
to do error handling.
Alternatively you can use resultFromString
, which avoids potential error cascades. It's up to you.
// Ok("probablyok"), which is typed as result(UserName.t, exn)
let goodUsername = "probablyok"->UserName.resultFromString;
// Error(TooShort("nope", 10))
let badUsername = "nope"->UserName.resultFromString;
The module also generates fromJson
and toJson
functions, allowing easy conversion to/from JSON for the custom types.
There's also a MakeStringSet
functor which creates a Set
module for managing sets of custom types with Belt.String
. There might be more on this later.
yarn re:build
# Alternatively, in watch mode
yarn re:watch
yarn test
# Alternatively, in watch mode
yarn test:watch