209 lines
5.4 KiB
Plaintext
209 lines
5.4 KiB
Plaintext
#!/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)"
|
|
}
|
|
}
|