https://github.com/adnelson/re-opaque
Easy and useful library for creating opaque types
https://github.com/adnelson/re-opaque
Last synced: 3 months ago
JSON representation
Easy and useful library for creating opaque types
- Host: GitHub
- URL: https://github.com/adnelson/re-opaque
- Owner: adnelson
- Created: 2020-07-18T05:39:17.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2023-01-06T11:47:32.000Z (over 3 years ago)
- Last Synced: 2025-02-26T10:47:59.039Z (over 1 year ago)
- Language: Reason
- Size: 831 KB
- Stars: 11
- Watchers: 1
- Forks: 0
- Open Issues: 11
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# re-opaque
A ReasonML library for opaque data types with validation.
## Opaque strings
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.
### Example
Below we create three string-like modules, `UserName`, `Email`, and `MessageText`. Each has its own validation logic (or lack thereof).
```reason
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:
```reason
// raises `TooShort("bad", 10)`
let badUsername = "bad"->UserName.fromString;
```
Or an email that doesn't match our regex:
```reason
// raises `RegexMatchError("i am not an email", )`
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.
### Error handling
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.
```reason
// Ok("probablyok"), which is typed as result(UserName.t, exn)
let goodUsername = "probablyok"->UserName.resultFromString;
// Error(TooShort("nope", 10))
let badUsername = "nope"->UserName.resultFromString;
```
### Extras
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.
## Build
```
yarn re:build
# Alternatively, in watch mode
yarn re:watch
```
## Test
```
yarn test
# Alternatively, in watch mode
yarn test:watch
```