209 lines
5.4 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env nu
# Result Type Pattern - Hybrid error handling without try-catch
# Combines preconditions (fail-fast), Result pattern, and functional composition
# Version: 1.0
#
# Usage:
# use lib_provisioning/result.nu *
#
# def my-operation []: record {
# if (precondition-fails) { return (err "message") }
# ok {result: "value"}
# }
# Construct success result with value
# Type: any -> {ok: any, err: null}
export def ok [value: any] {
{ok: $value, err: null}
}
# Construct error result with message
# Type: string -> {ok: null, err: string}
export def err [message: string] {
{ok: null, err: $message}
}
# Check if result is successful
# Type: record -> bool
export def is-ok [result: record] {
$result.err == null
}
# Check if result is error
# Type: record -> bool
export def is-err [result: record] {
$result.err != null
}
# Monadic bind: chain operations on Results
# Type: record, closure -> record
# Stops propagation on error
export def and-then [result: record, fn: closure] {
if (is-ok $result) {
do $fn $result.ok
} else {
$result # Propagate error
}
}
# Map over Result value without stopping on error
# Type: record, closure -> record
export def map [result: record, fn: closure] {
if (is-ok $result) {
ok (do $fn $result.ok)
} else {
$result
}
}
# Map over Result error
# Type: record, closure -> record
export def map-err [result: record, fn: closure] {
if (is-err $result) {
err (do $fn $result.err)
} else {
$result
}
}
# Unwrap Result or return default
# Type: record, any -> any
export def unwrap-or [result: record, default: any] {
if (is-ok $result) {
$result.ok
} else {
$default
}
}
# Unwrap Result or throw error
# Type: record -> any (throws if error)
export def unwrap! [result: record] {
if (is-ok $result) {
$result.ok
} else {
error make {msg: $result.err}
}
}
# Combine two Results (stops on first error)
# Type: record, record -> record
export def combine [result1: record, result2: record] {
if (is-err $result1) {
return $result1
}
if (is-err $result2) {
return $result2
}
ok {first: $result1.ok, second: $result2.ok}
}
# Combine list of Results (stops on first error)
# Type: list -> record
export def combine-all [results: list] {
let mut accumulated = (ok [])
for result in $results {
if (is-err $accumulated) {
break
}
$accumulated = (and-then $accumulated {|acc|
if (is-ok $result) {
ok ($acc | append $result.ok)
} else {
err $result.err
}
})
}
$accumulated
}
# Try operation with automatic error wrapping
# Type: closure -> record
# Catches Nushell errors and wraps them (no try-catch)
export def try-wrap [fn: closure] {
let result = (do { do $fn } | complete)
if $result.exit_code == 0 {
ok ($result.stdout)
} else {
err $result.stderr
}
}
# Match on Result (like Rust's match)
# Type: record, closure, closure -> any
export def match-result [result: record, on-ok: closure, on-err: closure] {
if (is-ok $result) {
do $on-ok $result.ok
} else {
do $on-err $result.err
}
}
# Execute bash command and wrap result
# Type: string -> record
# Returns: {ok: output, err: null} on success; {ok: null, err: message} on error (no try-catch)
export def bash-wrap [cmd: string] {
let result = (do { bash -c $cmd } | complete)
if $result.exit_code == 0 {
ok ($result.stdout | str trim)
} else {
err $"Command failed: ($result.stderr)"
}
}
# Execute bash command, check exit code
# Type: string -> record
# Returns: {ok: {exit_code: int, stdout: string}, err: null} or {ok: null, err: message} (no try-catch)
export def bash-check [cmd: string] {
let result = (do { bash -c $cmd | complete } | complete)
if $result.exit_code == 0 {
let bash_result = ($result.stdout)
if ($bash_result.exit_code == 0) {
ok $bash_result
} else {
err ($bash_result.stderr)
}
} else {
err $"Command failed: ($result.stderr)"
}
}
# Try bash command with fallback value
# Type: string, any -> any
# Returns value on success, fallback on error (no try-catch)
export def bash-or [cmd: string, fallback: any] {
let result = (do { bash -c $cmd } | complete)
if $result.exit_code == 0 {
($result.stdout | str trim)
} else {
$fallback
}
}
# Read JSON file safely
# Type: string -> record
# Returns: {ok: parsed_json, err: null} or {ok: null, err: message} (no try-catch)
export def json-read [file_path: string] {
let read_result = (do { open $file_path | from json } | complete)
if $read_result.exit_code == 0 {
ok ($read_result.stdout)
} else {
err $"Failed to read JSON from ($file_path): ($read_result.stderr)"
}
}
# Write JSON to file safely
# Type: string, any -> record
# Returns: {ok: true, err: null} or {ok: false, err: message} (no try-catch)
export def json-write [file_path: string, data: any] {
let json_str = ($data | to json)
let write_result = (do { bash -c $"cat > ($file_path) << 'EOF'\n($json_str)\nEOF" } | complete)
if $write_result.exit_code == 0 {
ok true
} else {
err $"Failed to write JSON to ($file_path): ($write_result.stderr)"
}
}