Skip to main content

cljrs_runtime/env/
error.rs

1//! Evaluation-time error types.
2
3use cljrs_value::Value;
4use cljrs_value::ValueError;
5
6#[derive(Debug, thiserror::Error)]
7pub enum EvalError {
8    #[error("runtime error: {0}")]
9    Runtime(String),
10
11    /// The cooperative execution-credit budget was exhausted.
12    #[error("gas exhausted")]
13    GasExhausted,
14
15    /// An operation attempted to use a capability unavailable to an isolated
16    /// transaction function.
17    #[error("effect forbidden in transaction function: {0}")]
18    ForbiddenEffect(String),
19
20    #[error("unbound symbol: {0}")]
21    UnboundSymbol(String),
22
23    #[error("arity error calling {name}: expected {expected}, got {got}")]
24    Arity {
25        name: String,
26        expected: String,
27        got: usize,
28    },
29
30    #[error("not callable: {0}")]
31    NotCallable(String),
32
33    /// A value thrown via `throw` or `ex-info`.
34    #[error("{0}")]
35    Thrown(Value),
36
37    #[error("read error: {0}")]
38    Read(#[from] cljrs_types::error::CljxError),
39
40    /// Internal signal for `recur` — caught by the loop/fn trampoline.
41    /// Never propagated to user code.
42    #[doc(hidden)]
43    #[error("internal: recur outside loop or fn")]
44    Recur(Vec<Value>),
45
46    #[error(
47        "commit {commit:?} failed signature verification — \
48         refusing to execute versioned symbol (enable GPG/SSH trust or disable \
49         :verify-commit-signatures): {reason}"
50    )]
51    CommitSignatureVerificationFailed { commit: String, reason: String },
52}
53
54impl EvalError {
55    /// Convert this error into a Clojure error *value* (`Value::Error`).
56    ///
57    /// A `Thrown` value is returned unchanged (preserving its `ex-data` /
58    /// `ex-cause`); any other error is wrapped in a fresh `ExceptionInfo` with
59    /// the error's display string as the message. Used where an error must be
60    /// stored as a value and later re-thrown — e.g. a failed `Future`'s state.
61    pub fn to_error_value(self) -> Value {
62        match self {
63            EvalError::Thrown(v) => v,
64            other => {
65                let msg = other.to_string();
66                Value::Error(cljrs_gc::GcPtr::new(cljrs_value::ExceptionInfo::new(
67                    cljrs_value::ValueError::Other(msg.clone()),
68                    msg,
69                    None,
70                    None,
71                )))
72            }
73        }
74    }
75}
76
77/// Surface a builtin's `ValueError` to the evaluator as a *catchable* condition.
78///
79/// Internal runtime errors (`IndexOutOfBounds`, `WrongType`, `ArityError`, …)
80/// are normalized into an `EvalError::Thrown(Value::Error(..))` carrying the
81/// original `ValueError` variant and its plain display message — no
82/// `runtime error:` prefix — so that `(catch :default e ..)` /
83/// `(catch Throwable e ..)` bind a value on which `ex-message` / `ex-data`
84/// behave the same as for a user `throw` / `ex-info`. A `ValueError::Thrown`
85/// (a builtin re-throwing a Clojure value) is surfaced as that exact value.
86pub fn value_error_to_eval_error(err: ValueError) -> EvalError {
87    match err {
88        ValueError::Thrown(v) => EvalError::Thrown(v),
89        ValueError::GasExhausted => EvalError::GasExhausted,
90        other => {
91            let msg = other.to_string();
92            EvalError::Thrown(Value::Error(cljrs_gc::GcPtr::new(
93                cljrs_value::ExceptionInfo::new(other, msg, None, None),
94            )))
95        }
96    }
97}
98
99pub type EvalResult<T = Value> = Result<T, EvalError>;