Skip to content

Instantly share code, notes, and snippets.

@mrmurphy
Last active August 29, 2019 16:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrmurphy/76862048b17572753b80c5ebed62704b to your computer and use it in GitHub Desktop.
Save mrmurphy/76862048b17572753b80c5ebed62704b to your computer and use it in GitHub Desktop.
Using ADTs to form invariants around application logic.
// These types enhance safety around binary data that we want to synthesize onto
// a record, but store in S3 instead of in Postgres. If the encoder for the
// wrapping type requires that a field has a type of `S3Data.Outgoing.t`, then
// the type system will ensure that the data has been loaded up from S3 before
// it gets sent back to the user. In the same way, if the wrapping type uses
// `Incoming.t` at the API layer, and just `t` at the database layer, then the
// type system ensures that some action has been taken to save the binary to S3
// before writing to the database.
module Saved = {
type t =
| Saved
| NotUsed;
let encodeForDb = t => {
switch (t) {
| NotUsed => Obj.magic(Js.undefined)
| _ => Obj.magic(BufferBuddy.fromString("Y"))
};
};
let decodeFromDb = json => {
switch (Js.Json.classify(json)) {
| Js.Json.JSONObject(_) => Saved
| _ => NotUsed
};
};
};
module Incoming = {
type t =
| Unsaved(Node.Buffer.t)
| NotUsed;
let decodeFromApi = json => {
open Json.Decode;
let asB64String = optional(string, json);
switch (asB64String) {
| Some(str) => Unsaved(BufferBuddy.fromStringWithEncoding(str, `base64))
| None => NotUsed
};
};
};
module Loaded = {
type t =
| Loaded(Node.Buffer.t)
| NotUsed;
let getWithDefault = (t, a) =>
switch (t) {
| Loaded(b) => b
| NotUsed => a
};
let encodeForApi = t => {
switch (t) {
| Loaded(buf) =>
buf->BufferBuddy.toStringWithEncoding("base64")->Json.Encode.string
| NotUsed => Js.Json.null
};
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment