Not sure I’m completely answering the question here, but lately I’ve been using a pretty light weight pipeable pattern using polyvariants to accumulate errors. A superficial example:
type parsed = {content: string}
let parse = async s => {
switch s {
| "hello" => Ok({content: "hello"})
| _ => Error(#InvalidInput)
}
}
let transform = async v => {
switch v {
| {content: "hello" as s} => Ok(s)
| _ => Error(#InvalidTransformTarget)
}
}
let report = async v => {
switch v {
| "hello" =>
Console.log("Yup!")
Ok("hello")
| _ => Error(#CannotReport)
}
}
let consume = v => {
switch v {
| "hello" => Ok(#Consumed)
| "skip" => Ok(#Skipped)
| _ => Error(#CouldNotConsume)
}
}
let okThenSync = async (p, fn): result<'v, [> ]> => {
switch await p {
| exception e => Error(#UnknownError(e))
| Ok(v) => fn(v)
| Error(e) => Error(e)
}
}
let okThen = async (p, fn): result<'v, [> ]> => {
switch await p {
| exception e => Error(#UnknownError(e))
| Ok(v) => await fn(v)
| Error(e) => Error(e)
}
}
let run = async () => {
let res =
parse("test")
->okThen(transform)
->okThen(report)
->okThenSync(consume)
switch await res {
| Ok(#Consumed) => Console.log("Consumed!")
| Ok(#Skipped) => Console.log("Skipped this one")
| Error(#InvalidInput) => Console.log("Invalid input")
| Error(#InvalidTransformTarget) => Console.log("Invalid transform target")
| Error(#CannotReport) => Console.log("Cannot report!")
| Error(#CouldNotConsume) => Console.log("Could not consume content")
| Error(#UnknownError(e)) => Console.error2("Exception!", e)
}
}
It’s small and malleable enough that you can just turn it into whatever you need, add helpers as you go, etc.
EDIT: Added general error handling as well.