error handling
change kppsopqqvwkmuzxsqryslzovrruzrrpo
commit a32a12c00ad415cfa85a31551f93498ad851d1d4
author Alpha Chen <alpha@kejadlen.dev>
date
parent yssntpwp
diff --git a/Cargo.lock b/Cargo.lock
index f322cf7..501366d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,21 @@
 # It is not intended for manual editing.
 version = 4
 
+[[package]]
+name = "addr2line"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
 [[package]]
 name = "anstream"
 version = "0.6.21"
@@ -52,18 +67,27 @@ dependencies = [
  "windows-sys 0.60.2",
 ]
 
-[[package]]
-name = "anyhow"
-version = "1.0.100"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
-
 [[package]]
 name = "autocfg"
 version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
 
+[[package]]
+name = "backtrace"
+version = "0.3.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-link",
+]
+
 [[package]]
 name = "bitflags"
 version = "2.10.0"
@@ -80,6 +104,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
 [[package]]
 name = "cc"
 version = "1.2.42"
@@ -136,6 +166,33 @@ version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
 
+[[package]]
+name = "color-eyre"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d"
+dependencies = [
+ "backtrace",
+ "color-spantrace",
+ "eyre",
+ "indenter",
+ "once_cell",
+ "owo-colors",
+ "tracing-error",
+]
+
+[[package]]
+name = "color-spantrace"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427"
+dependencies = [
+ "once_cell",
+ "owo-colors",
+ "tracing-core",
+ "tracing-error",
+]
+
 [[package]]
 name = "colorchoice"
 version = "1.0.4"
@@ -164,6 +221,68 @@ dependencies = [
  "windows-sys 0.61.2",
 ]
 
+[[package]]
+name = "eyre"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
+dependencies = [
+ "indenter",
+ "once_cell",
+]
+
+[[package]]
+name = "facet"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f2eff8d6840932e1fbd7c4fc80a0521ab303323ea31ec341ea185384c3b1383"
+dependencies = [
+ "facet-core",
+ "facet-macros",
+ "static_assertions",
+]
+
+[[package]]
+name = "facet-core"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c80a6e22351f448828529adf1be52f3037c99ac831bcf3fb9a0df2d5c273df8a"
+dependencies = [
+ "bitflags",
+ "impls",
+]
+
+[[package]]
+name = "facet-macros"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a8c1da45a93b10c5829601e19846c7f13d8cde6e0b65740fee5ea51f96d7d7e"
+dependencies = [
+ "facet-core",
+ "facet-macros-emit",
+]
+
+[[package]]
+name = "facet-macros-emit"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "561cd90d2074ce22cb0ea0a4c4395779efd03f9b4d1a2712369b0a63ddd5cfbf"
+dependencies = [
+ "facet-macros-parse",
+ "quote",
+]
+
+[[package]]
+name = "facet-macros-parse"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b88ddf668029af6fec4d3e3baaedc82f63667416a0e4b2487226212f70b53af9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unsynn",
+]
+
 [[package]]
 name = "find-msvc-tools"
 version = "0.1.4"
@@ -174,20 +293,48 @@ checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
 name = "frork"
 version = "0.1.0"
 dependencies = [
- "anyhow",
  "clap",
+ "color-eyre",
+ "facet",
  "mlua",
  "thiserror",
  "tracing",
  "tracing-subscriber",
 ]
 
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "gimli"
+version = "0.32.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
+
 [[package]]
 name = "heck"
 version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
+[[package]]
+name = "impls"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a46645bbd70538861a90d0f26c31537cdf1e44aae99a794fb75a664b70951bc"
+
+[[package]]
+name = "indenter"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
+
 [[package]]
 name = "is_terminal_polyfill"
 version = "1.70.2"
@@ -252,6 +399,15 @@ version = "2.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
 
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+]
+
 [[package]]
 name = "mlua"
 version = "0.11.4"
@@ -280,6 +436,12 @@ dependencies = [
  "pkg-config",
 ]
 
+[[package]]
+name = "mutants"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126"
+
 [[package]]
 name = "nu-ansi-term"
 version = "0.50.3"
@@ -298,6 +460,15 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "object"
+version = "0.37.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.21.3"
@@ -310,6 +481,12 @@ version = "1.70.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
 
+[[package]]
+name = "owo-colors"
+version = "4.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
+
 [[package]]
 name = "parking_lot"
 version = "0.12.5"
@@ -372,6 +549,12 @@ dependencies = [
  "bitflags",
 ]
 
+[[package]]
+name = "rustc-demangle"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
+
 [[package]]
 name = "rustc-hash"
 version = "2.1.1"
@@ -432,6 +615,12 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "shadow_counted"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65da48d447333cebe1aadbdd3662f3ba56e76e67f53bc46f3dd5f67c74629d6b"
+
 [[package]]
 name = "sharded-slab"
 version = "0.1.7"
@@ -453,6 +642,12 @@ version = "1.15.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
 
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
 [[package]]
 name = "strsim"
 version = "0.11.1"
@@ -472,18 +667,18 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.69"
+version = "2.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.69"
+version = "2.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -531,6 +726,16 @@ dependencies = [
  "valuable",
 ]
 
+[[package]]
+name = "tracing-error"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db"
+dependencies = [
+ "tracing",
+ "tracing-subscriber",
+]
+
 [[package]]
 name = "tracing-log"
 version = "0.2.0"
@@ -562,6 +767,18 @@ version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
 
+[[package]]
+name = "unsynn"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7940603a9e25cf11211cc43b81f4fcad2b8ab4df291ca855f32c40e1ac22d5bc"
+dependencies = [
+ "fxhash",
+ "mutants",
+ "proc-macro2",
+ "shadow_counted",
+]
+
 [[package]]
 name = "utf8parse"
 version = "0.2.2"
diff --git a/Cargo.toml b/Cargo.toml
index 43381c4..43c7ced 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,9 +4,10 @@ version = "0.1.0"
 edition = "2024"
 
 [dependencies]
-anyhow = "1.0"
-clap = { version = "4.5", features = ["derive"] }
-mlua = { version = "0.11", features = ["lua54", "vendored"] }
-thiserror = "1.0"
-tracing = "0.1"
-tracing-subscriber = "0.3"
+color-eyre = "*"
+clap = { version = "*", features = ["derive"] }
+facet = "*"
+mlua = { version = "*", features = ["lua54", "vendored"] }
+thiserror = "*"
+tracing = "*"
+tracing-subscriber = "*"
diff --git a/src/main.rs b/src/main.rs
index 3235e75..b3ea4cd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
-use anyhow::{Result, anyhow, bail};
 use clap::{Parser, Subcommand};
+use color_eyre::{Result, eyre};
+use eyre::WrapErr;
 use mlua::prelude::*;
 use std::cell::RefCell;
 use std::collections::HashMap;
@@ -23,14 +24,26 @@ enum Commands {
     Check { script: String },
 }
 
-#[derive(Error, Debug)]
+#[derive(Error, Debug, Clone)]
 pub enum FrorkError {
     #[error("No operation specified")]
     NoOperation,
     #[error("Unknown operation: {operation}")]
     UnknownOperation { operation: String },
-    #[error("Symlink requires exactly 2 arguments: target and source")]
-    InvalidSymlinkArgs,
+    #[error("Unknown assertion type: {assertion_type}")]
+    UnknownAssertionType { assertion_type: String },
+    #[error("Invalid arguments: {0}")]
+    InvalidArguments(String),
+    #[error("Lua error: {0}")]
+    Lua(String),
+    #[error("Assertion error: {0}")]
+    Assertion(String),
+}
+
+impl From<LuaError> for FrorkError {
+    fn from(err: LuaError) -> Self {
+        FrorkError::Lua(err.to_string())
+    }
 }
 
 fn expand_tilde(path: &str) -> String {
@@ -52,7 +65,7 @@ enum Status {
 }
 
 trait AssertionType {
-    fn check(&self) -> Status;
+    fn status(&self) -> Result<Status>;
     fn display(&self) -> String;
 }
 
@@ -77,15 +90,13 @@ impl Registry {
             .insert(name.to_string(), Box::new(factory));
     }
 
-    fn create(
-        &self,
-        assertion_type: &str,
-        args: LuaMultiValue,
-    ) -> Result<Option<Box<dyn AssertionType>>> {
-        match self.assertion_types.get(assertion_type) {
-            Some(factory) => factory(args).map(Some),
-            None => Ok(None),
-        }
+    fn create(&self, assertion_type: &str, args: LuaMultiValue) -> Result<Box<dyn AssertionType>> {
+        let factory = self.assertion_types.get(assertion_type).ok_or_else(|| {
+            FrorkError::UnknownAssertionType {
+                assertion_type: assertion_type.to_string(),
+            }
+        })?;
+        factory(args)
     }
 }
 
@@ -96,49 +107,57 @@ struct Symlink {
 
 impl Symlink {
     fn new(args: LuaMultiValue) -> Result<Self> {
-        let args_vec: Vec<LuaValue> = args.into_iter().collect();
+        let args_vec: Vec<LuaValue> = args.into_vec();
 
         if args_vec.len() != 2 {
-            bail!(FrorkError::InvalidSymlinkArgs);
+            return Err(FrorkError::InvalidArguments(format!(
+                "Symlink requires exactly 2 arguments, got {}",
+                args_vec.len()
+            ))
+            .into());
         }
 
-        let target = args_vec[0]
-            .to_string()
-            .map_err(|_| anyhow!("Target must be a string"))?;
-        let source = args_vec[1]
-            .to_string()
-            .map_err(|_| anyhow!("Source must be a string"))?;
-
-        Ok(Self {
-            target: expand_tilde(&target),
-            source: expand_tilde(&source),
-        })
+        let strings: Vec<String> = args_vec
+            .into_iter()
+            .map(|val| {
+                val.to_string().map(|s| expand_tilde(&s)).map_err(|_| {
+                    FrorkError::InvalidArguments("Arguments must be strings".to_string())
+                })
+            })
+            .collect::<Result<_, _>>()?;
+
+        let [target, source]: [String; 2] = strings.try_into().unwrap();
+
+        Ok(Self { target, source })
     }
 }
 
 impl AssertionType for Symlink {
-    fn check(&self) -> Status {
-        if Path::new(&self.target).exists() {
-            // Check if it's a symlink pointing to the correct source
-            if let Ok(link_target) = fs::read_link(&self.target) {
-                if link_target == Path::new(&self.source) {
-                    Status::Ok
-                } else {
-                    unimplemented!(
-                        "symlink {} {} points to wrong target",
-                        self.target,
-                        self.source
-                    );
-                }
+    fn status(&self) -> Result<Status> {
+        if !Path::new(&self.target).exists() {
+            return Ok(Status::Missing);
+        }
+
+        if let Ok(link_target) = fs::read_link(&self.target) {
+            if link_target == Path::new(&self.source) {
+                Ok(Status::Ok)
             } else {
                 unimplemented!(
-                    "symlink {} {} target exists but is not a symlink",
-                    self.target,
-                    self.source
+                    "{}",
+                    FrorkError::Assertion(format!(
+                        "symlink {} {} points to wrong target",
+                        self.target, self.source
+                    ))
                 );
             }
         } else {
-            Status::Missing
+            unimplemented!(
+                "{}",
+                FrorkError::Assertion(format!(
+                    "symlink {} {} target exists but is not a symlink",
+                    self.target, self.source
+                ))
+            );
         }
     }
 
@@ -164,15 +183,17 @@ impl FennelAssertion {
 }
 
 impl AssertionType for FennelAssertion {
-    fn check(&self) -> Status {
-        // Call the Lua function with args and convert result
-        match self.status_fn.call::<String>(self.args.clone()) {
-            Ok(result) => match result.as_str() {
-                "ok" => Status::Ok,
-                "missing" => Status::Missing,
-                _ => Status::Ok, // Default fallback
-            },
-            Err(_) => Status::Ok, // Default fallback on error
+    fn status(&self) -> Result<Status> {
+        let result = self
+            .status_fn
+            .call::<String>(self.args.clone())
+            .map_err(FrorkError::from)?;
+        match result.as_str() {
+            "ok" => Ok(Status::Ok),
+            "missing" => Ok(Status::Missing),
+            _ => {
+                Err(FrorkError::Assertion(format!("Invalid status returned: '{}'", result)).into())
+            }
         }
     }
 
@@ -188,51 +209,52 @@ impl AssertionType for FennelAssertion {
 }
 
 struct Frork {
-    registry: Rc<RefCell<Registry>>,
+    // RefCell needed for interior mutability - register() method needs to add
+    // new assertion types at runtime when called from Lua/Fennel code
+    registry: RefCell<Registry>,
 }
 
 impl Frork {
     fn new() -> Self {
         let mut registry = Registry::new();
-        registry.register("symlink", |args| Ok(Box::new(Symlink::new(args)?)));
+        registry.register("symlink", |args| {
+            Symlink::new(args).map(|s| Box::new(s) as Box<dyn AssertionType>)
+        });
 
         Self {
-            registry: Rc::new(RefCell::new(registry)),
+            registry: RefCell::new(registry),
         }
     }
 
     fn ok(&self, args: LuaMultiValue) -> Result<()> {
         if args.is_empty() {
-            bail!(FrorkError::NoOperation);
+            return Err(FrorkError::NoOperation.into());
         }
 
         let mut args_iter = args.into_iter();
-        let operation = args_iter
+        let assertion_type = args_iter
             .next()
             .and_then(|v| v.to_string().ok())
-            .ok_or_else(|| anyhow!("First argument must be operation name"))?;
+            .ok_or_else(|| {
+                FrorkError::InvalidArguments("First argument must be assertion type".to_string())
+            })?;
 
         let assertion_args: LuaMultiValue = args_iter.collect();
 
-        match self.registry.borrow().create(&operation, assertion_args)? {
-            Some(assertion) => {
-                let status = assertion.check();
-                match status {
-                    Status::Ok => println!("ok: {}", assertion.display()),
-                    Status::Missing => println!("missing: {}", assertion.display()),
-                }
-                Ok(())
-            }
-            None => bail!(FrorkError::UnknownOperation {
-                operation: operation.to_string()
-            }),
+        let assertion = self
+            .registry
+            .borrow()
+            .create(&assertion_type, assertion_args)?;
+        let status = assertion.status()?;
+        match status {
+            Status::Ok => println!("ok: {}", assertion.display()),
+            Status::Missing => println!("missing: {}", assertion.display()),
         }
+        Ok(())
     }
 
-    fn register(&self, name: &str, table: LuaTable) -> Result<()> {
-        let status_fn: LuaFunction = table
-            .get("status")
-            .map_err(|e| anyhow!("Failed to get status function: {}", e))?;
+    fn register(&self, name: &str, table: LuaTable) -> Result<(), FrorkError> {
+        let status_fn: LuaFunction = table.get("status")?;
 
         let name_clone = name.to_string();
         self.registry.borrow_mut().register(name, move |args| {
@@ -270,42 +292,35 @@ impl Frork {
     }
 }
 
-fn run(script_path: &str) -> Result<()> {
+fn run(script_path: &str) -> Result<(), FrorkError> {
     let lua = Lua::new();
 
     let fennel_code = include_str!("../fennel-1.6.0.lua");
-    let fennel_module = lua
-        .load(fennel_code)
-        .eval::<LuaValue>()
-        .map_err(|e| anyhow!("Failed to load Fennel: {}", e))?;
-    lua.register_module("fennel", fennel_module)
-        .map_err(|e| anyhow!("Failed to register Fennel module: {}", e))?;
+    let fennel_module = lua.load(fennel_code).eval::<LuaValue>()?;
+    lua.register_module("fennel", fennel_module)?;
 
     let frork = Frork::new();
-    let frork_module = frork
-        .bind(&lua)
-        .map_err(|e| anyhow!("Failed to create Frork module: {}", e))?;
-    lua.register_module("frork", frork_module)
-        .map_err(|e| anyhow!("Failed to register Frork module: {}", e))?;
+    let frork_module = frork.bind(&lua)?;
+    lua.register_module("frork", frork_module)?;
 
     lua.load(format!(
         r#"require("fennel").install().dofile("{}")"#,
         script_path
     ))
-    .exec()
-    .map_err(|e| anyhow!("Failed to execute script '{}': {}", script_path, e))?;
+    .exec()?;
 
     Ok(())
 }
 
-fn main() -> Result<()> {
+fn main() -> color_eyre::Result<()> {
+    color_eyre::install()?;
     tracing_subscriber::fmt::init();
 
     let cli = Cli::parse();
 
     match &cli.command {
         Commands::Check { script } => {
-            run(script)?;
+            run(script).wrap_err("Failed to run script")?;
         }
     }