Files
wren/manual/howto/error-handling.html
T
retoor 5a4054ec66 feat: add example scripts for argparse, bytes, dataset, html, markdown, uuid, wdantic, and web chat modules
Add comprehensive demo scripts showcasing the usage of multiple Wren modules including argument parsing, byte manipulation, in-memory dataset operations, HTML encoding/slugification, markdown-to-HTML conversion, UUID generation/validation, schema validation with wdantic, and a full web chat application with REST endpoints.
2026-01-24 22:40:22 +00:00

479 lines
15 KiB
HTML

<!DOCTYPE html>
<!-- retoor <retoor@molodetz.nl> -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Error Handling - Wren-CLI Manual</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<button class="mobile-menu-toggle">Menu</button>
<div class="container">
<aside class="sidebar">
<div class="sidebar-header">
<h1><a href="../index.html">Wren-CLI</a></h1>
<div class="version">v0.4.0</div>
</div>
<nav class="sidebar-nav">
<div class="section">
<span class="section-title">Getting Started</span>
<ul>
<li><a href="../getting-started/index.html">Overview</a></li>
<li><a href="../getting-started/installation.html">Installation</a></li>
<li><a href="../getting-started/first-script.html">First Script</a></li>
<li><a href="../getting-started/repl.html">Using the REPL</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Language</span>
<ul>
<li><a href="../language/index.html">Syntax Overview</a></li>
<li><a href="../language/classes.html">Classes</a></li>
<li><a href="../language/methods.html">Methods</a></li>
<li><a href="../language/control-flow.html">Control Flow</a></li>
<li><a href="../language/fibers.html">Fibers</a></li>
<li><a href="../language/modules.html">Modules</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">API Reference</span>
<ul>
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/math.html">math</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Tutorials</span>
<ul>
<li><a href="../tutorials/index.html">Tutorial List</a></li>
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Template Rendering</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="index.html">How-To List</a></li>
<li><a href="http-requests.html">HTTP Requests</a></li>
<li><a href="json-parsing.html">JSON Parsing</a></li>
<li><a href="regex-patterns.html">Regex Patterns</a></li>
<li><a href="file-operations.html">File Operations</a></li>
<li><a href="async-operations.html">Async Operations</a></li>
<li><a href="error-handling.html" class="active">Error Handling</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
<nav class="breadcrumb">
<a href="../index.html">Home</a>
<span class="separator">/</span>
<a href="index.html">How-To Guides</a>
<span class="separator">/</span>
<span>Error Handling</span>
</nav>
<article>
<h1>Error Handling</h1>
<p>Wren uses fibers for error handling. The <code>fiber.try()</code> method catches errors without crashing your program.</p>
<h2>Basic Try/Catch Pattern</h2>
<pre><code>var fiber = Fiber.new {
Fiber.abort("Something went wrong!")
}
var result = fiber.try()
if (fiber.error) {
System.print("Error: %(fiber.error)")
} else {
System.print("Result: %(result)")
}</code></pre>
<h2>Throw an Error</h2>
<pre><code>Fiber.abort("This is an error message")</code></pre>
<h2>Catch Specific Error Types</h2>
<pre><code>var fiber = Fiber.new {
var x = null
return x.count // Error: null has no method 'count'
}
var result = fiber.try()
if (fiber.error) {
if (fiber.error.contains("null")) {
System.print("Null reference error")
} else {
System.print("Other error: %(fiber.error)")
}
}</code></pre>
<h2>Safe File Read</h2>
<pre><code>import "io" for File
var safeRead = Fn.new { |path|
var fiber = Fiber.new { File.read(path) }
var content = fiber.try()
if (fiber.error) {
return null
}
return content
}
var content = safeRead.call("config.txt")
if (content) {
System.print(content)
} else {
System.print("Could not read file")
}</code></pre>
<h2>Safe JSON Parse</h2>
<pre><code>import "json" for Json
var safeParse = Fn.new { |jsonStr|
var fiber = Fiber.new { Json.parse(jsonStr) }
var data = fiber.try()
if (fiber.error) {
System.print("Invalid JSON: %(fiber.error)")
return null
}
return data
}
var data = safeParse.call('{"valid": true}') // Works
var bad = safeParse.call('not json') // Returns null</code></pre>
<h2>Safe HTTP Request</h2>
<pre><code>import "http" for Http
var safeFetch = Fn.new { |url|
var fiber = Fiber.new { Http.get(url) }
var response = fiber.try()
if (fiber.error) {
return {"error": fiber.error, "ok": false}
}
if (response.statusCode >= 400) {
return {"error": "HTTP %(response.statusCode)", "ok": false}
}
return {"data": response.json, "ok": true}
}
var result = safeFetch.call("https://api.example.com/data")
if (result["ok"]) {
System.print(result["data"])
} else {
System.print("Error: %(result["error"])")
}</code></pre>
<h2>Input Validation</h2>
<pre><code>var validateEmail = Fn.new { |email|
if (email == null) {
Fiber.abort("Email is required")
}
if (!email.contains("@")) {
Fiber.abort("Invalid email format")
}
return true
}
var validate = Fn.new { |email|
var fiber = Fiber.new { validateEmail.call(email) }
fiber.try()
return fiber.error
}
System.print(validate.call(null)) // Email is required
System.print(validate.call("invalid")) // Invalid email format
System.print(validate.call("a@b.com")) // null (no error)</code></pre>
<h2>Result Type Pattern</h2>
<pre><code>class Result {
construct ok(value) {
_value = value
_error = null
}
construct error(message) {
_value = null
_error = message
}
isOk { _error == null }
isError { _error != null }
value { _value }
error { _error }
unwrap {
if (isError) Fiber.abort(_error)
return _value
}
unwrapOr(default) { isOk ? _value : default }
}
var divide = Fn.new { |a, b|
if (b == 0) {
return Result.error("Division by zero")
}
return Result.ok(a / b)
}
var result = divide.call(10, 2)
if (result.isOk) {
System.print("Result: %(result.value)")
}
var bad = divide.call(10, 0)
if (bad.isError) {
System.print("Error: %(bad.error)")
}
System.print(divide.call(10, 5).unwrapOr(0)) // 2
System.print(divide.call(10, 0).unwrapOr(0)) // 0</code></pre>
<h2>Assert Function</h2>
<pre><code>var assert = Fn.new { |condition, message|
if (!condition) {
Fiber.abort("Assertion failed: %(message)")
}
}
var processUser = Fn.new { |user|
assert.call(user != null, "User is required")
assert.call(user["name"] != null, "User name is required")
assert.call(user["age"] >= 0, "Age must be non-negative")
System.print("Processing %(user["name"])...")
}</code></pre>
<h2>Multiple Error Sources</h2>
<pre><code>import "http" for Http
import "json" for Json
var fetchAndParse = Fn.new { |url|
var httpFiber = Fiber.new { Http.get(url) }
var response = httpFiber.try()
if (httpFiber.error) {
return {"error": "Network error: %(httpFiber.error)"}
}
if (response.statusCode != 200) {
return {"error": "HTTP error: %(response.statusCode)"}
}
var jsonFiber = Fiber.new { Json.parse(response.body) }
var data = jsonFiber.try()
if (jsonFiber.error) {
return {"error": "Parse error: %(jsonFiber.error)"}
}
return {"data": data}
}
var result = fetchAndParse.call("https://api.example.com/data")
if (result.containsKey("error")) {
System.print(result["error"])
} else {
System.print(result["data"])
}</code></pre>
<h2>Error Logging</h2>
<pre><code>import "datetime" for DateTime
import "io" for File
class Logger {
construct new(logFile) {
_logFile = logFile
}
log(level, message) {
var timestamp = DateTime.now().toString
var entry = "[%(timestamp)] [%(level)] %(message)\n"
var existing = ""
var fiber = Fiber.new { File.read(_logFile) }
existing = fiber.try() || ""
File.write(_logFile, existing + entry)
if (level == "ERROR") {
System.print("ERROR: %(message)")
}
}
error(message) { log("ERROR", message) }
warn(message) { log("WARN", message) }
info(message) { log("INFO", message) }
}
var logger = Logger.new("app.log")
var safeDivide = Fn.new { |a, b|
if (b == 0) {
logger.error("Division by zero: %(a) / %(b)")
return null
}
return a / b
}
var result = safeDivide.call(10, 0)</code></pre>
<h2>Cleanup with Finally Pattern</h2>
<pre><code>import "sqlite" for Sqlite
var withDatabase = Fn.new { |dbPath, operation|
var db = Sqlite.open(dbPath)
var result = null
var error = null
var fiber = Fiber.new { operation.call(db) }
result = fiber.try()
error = fiber.error
db.close()
if (error) {
Fiber.abort(error)
}
return result
}
var users = withDatabase.call("app.db", Fn.new { |db|
return db.execute("SELECT * FROM users")
})
System.print(users)</code></pre>
<h2>Custom Error Class</h2>
<pre><code>class AppError {
construct new(code, message) {
_code = code
_message = message
}
code { _code }
message { _message }
toString { "[%(code)] %(message)" }
static notFound(resource) {
return AppError.new("NOT_FOUND", "%(resource) not found")
}
static validation(field, reason) {
return AppError.new("VALIDATION", "%(field): %(reason)")
}
static unauthorized() {
return AppError.new("UNAUTHORIZED", "Authentication required")
}
}
var findUser = Fn.new { |id|
if (id == null) {
Fiber.abort(AppError.validation("id", "is required").toString)
}
var user = null
if (user == null) {
Fiber.abort(AppError.notFound("User %(id)").toString)
}
return user
}
var fiber = Fiber.new { findUser.call(null) }
fiber.try()
System.print(fiber.error) // [VALIDATION] id: is required</code></pre>
<h2>Retry on Error</h2>
<pre><code>import "timer" for Timer
var retry = Fn.new { |operation, maxAttempts, delayMs|
var lastError = null
for (i in 1..maxAttempts) {
var fiber = Fiber.new { operation.call() }
var result = fiber.try()
if (!fiber.error) {
return result
}
lastError = fiber.error
System.print("Attempt %(i) failed: %(lastError)")
if (i < maxAttempts) {
Timer.sleep(delayMs)
}
}
Fiber.abort("All %(maxAttempts) attempts failed. Last error: %(lastError)")
}
var result = retry.call(Fn.new {
return "success"
}, 3, 1000)</code></pre>
<div class="admonition tip">
<div class="admonition-title">Best Practices</div>
<ul>
<li>Always wrap external calls (HTTP, file I/O) in fibers</li>
<li>Provide meaningful error messages</li>
<li>Log errors for debugging</li>
<li>Fail fast on programming errors, recover from user errors</li>
<li>Clean up resources (close files, connections) even on error</li>
</ul>
</div>
<div class="admonition note">
<div class="admonition-title">See Also</div>
<p>For more on fibers, see the <a href="../language/fibers.html">Fibers language guide</a>.</p>
</div>
</article>
<footer class="page-footer">
<a href="async-operations.html" class="prev">Async Operations</a>
<a href="../index.html" class="next">Home</a>
</footer>
</main>
</div>
<script src="../js/main.js"></script>
</body>
</html>