Skip to content

Instantly share code, notes, and snippets.

@metatablecat
Created July 22, 2024 19:47
Show Gist options
  • Save metatablecat/55e3041826c9f837c9f39f6b6f14009a to your computer and use it in GitHub Desktop.
Save metatablecat/55e3041826c9f837c9f39f6b6f14009a to your computer and use it in GitHub Desktop.
Extract from Catlibs, result class that emulates Rust result objects, might be missing some meta behaviour, but should serve its purpose for Luau.
export type Ok<T> = {
IsOk: true,
Ok: T,
}
export type Err<T> = {
IsOk: false,
Err: T
}
export type OkErr<O, E> = Ok<O>|Err<E>
export type Result<O, E> = {
Ok: (Result<O, E>, ok: O) -> Ok<O>,
Err: (Result<O, E>, err: E) -> Err<E>,
FromLuaError: (Result<O, E>, msg: string) -> Err<E>,
FromPcall: <A...>(
Result<O, E>,
f: (A...) -> O,
A...
) -> OkErr<O, E>
}
type matches<E> = {
default: E?,
[string]: E
}
local function findFirstMatch<E>(msg: string, matches: matches<E>): E?
-- finds the first match given a msg and table of matches
-- matches = {[Pattern] = return}
-- return the match's value
-- iterate over matches
for pattern, value in matches do
if string.match(msg, pattern) then
return value
end
end
return matches.default
end
local function result(isok, d): OkErr<any, any>
return {
IsOk = isok,
Ok = if isok then d else nil,
Err = if not isok then d else nil
}
end
--[[
`Result` is an error handling factory based on `Result` objects from Rust.
It can be used to match Lua error strings to Enum values (see Code Example)
### Code Example
```lua
local CatError = Result {
["A cat was not found"] = "CAT_NOT_FOUND"
["This cat has an invalid name"] = "CAT_INVALID_NAME",
default = "CAT_UNKNOWN_ERR"
}
local function Cat(name)
if type(name) ~= "string" then
error("This cat has an invalid name")
end
return assert(Cats[name], "A cat was not found")
end
local catResult = Result:FromPcall(Cat, 0)
if catResult.IsOk then
...
else
if catResult.Err = "CAT_NOT_FOUND" then
elseif catResult.Err = "CAT_INVALID_NAME" then
end
end
```
]]
local function Result<O, E>(matches: matches<E>): Result<O, E>
local gen = {}
gen._errmatch = matches
--[[
Emits an `Ok` value on the Result
]]
function gen:Ok(ok: O): Ok<O>
local r = result(true, ok)
table.freeze(r)
return r
end
--[[
Emits an `Err` (Error) value on the Result
]]
function gen:Err(err: E): Err<E>
local r = result(false, err)
table.freeze(r)
return r
end
--[[
Generates an `Err` off a provided error mapping in the constructor
]]
function gen:FromLuaError(msg: string): Err<E>
local matchedMsg = findFirstMatch(msg, self._errmatch)
return self:Err(matchedMsg)
end
--[[
Executes the passed function in a pcall then generates a `Result` based on
the state of the pcall.
]]
function gen:FromPcall<A...>(f: (A...) -> O, ...:A...): OkErr<O, E>
local s, e = pcall(f, ...)
if not s then
return self:FromLuaError(e)
else
return self:Ok(e)
end
end
return gen
end
return Result
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment