Skip to main content

harn_hostlib/
error.rs

1//! Error type for hostlib host calls.
2//!
3//! Builtins translate this into VM-level errors via [`Into<harn_vm::VmError>`]
4//! so that Harn scripts see structured exceptions rather than panics.
5
6use std::collections::BTreeMap;
7use std::rc::Rc;
8
9use harn_vm::{VmError, VmValue};
10
11/// All errors a hostlib builtin can surface.
12///
13/// Variants intentionally describe the *kind* of failure rather than the
14/// specific module — every module routes its missing-implementation errors
15/// through [`HostlibError::Unimplemented`] so embedders and tests can
16/// distinguish "this is a stub waiting for B2/B3/etc." from real failures
17/// once implementations land.
18#[derive(Debug, thiserror::Error)]
19pub enum HostlibError {
20    /// The method exists in the registration table but has no implementation
21    /// yet. This is the canonical scaffold-stage error: it tells callers
22    /// "the contract is stable, the body is coming in a follow-up issue."
23    #[error(
24        "hostlib: {builtin} is not implemented yet (scaffold; tracked by issue #563 follow-ups)"
25    )]
26    Unimplemented {
27        /// Fully-qualified builtin name, e.g. `"hostlib_ast_parse_file"`.
28        builtin: &'static str,
29    },
30
31    /// A required parameter was missing from the call payload.
32    #[error("hostlib: {builtin}: missing required parameter '{param}'")]
33    MissingParameter {
34        /// Fully-qualified builtin name.
35        builtin: &'static str,
36        /// Name of the missing parameter.
37        param: &'static str,
38    },
39
40    /// A parameter was present but had the wrong shape (wrong type, malformed).
41    #[error("hostlib: {builtin}: invalid parameter '{param}': {message}")]
42    InvalidParameter {
43        /// Fully-qualified builtin name.
44        builtin: &'static str,
45        /// Name of the invalid parameter.
46        param: &'static str,
47        /// Human-readable description of the violation.
48        message: String,
49    },
50
51    /// Catch-all wrapper for I/O, parsing, or other backend failures.
52    /// Implementations land in follow-up issues; today nothing surfaces
53    /// this variant.
54    #[error("hostlib: {builtin}: {message}")]
55    Backend {
56        /// Fully-qualified builtin name.
57        builtin: &'static str,
58        /// Human-readable failure description.
59        message: String,
60    },
61}
62
63impl HostlibError {
64    /// The fully-qualified builtin name this error came from. Useful for
65    /// embedder logging and for the routing tests in `tests/`.
66    pub fn builtin(&self) -> &'static str {
67        match self {
68            HostlibError::Unimplemented { builtin }
69            | HostlibError::MissingParameter { builtin, .. }
70            | HostlibError::InvalidParameter { builtin, .. }
71            | HostlibError::Backend { builtin, .. } => builtin,
72        }
73    }
74}
75
76impl From<HostlibError> for VmError {
77    fn from(err: HostlibError) -> VmError {
78        // Surface as a `Thrown` dict so Harn `try`/`catch` can pattern-match
79        // on `kind`, `builtin`, and `message`. This matches how the existing
80        // `host_call` error path shapes its exceptions.
81        let kind = match err {
82            HostlibError::Unimplemented { .. } => "unimplemented",
83            HostlibError::MissingParameter { .. } => "missing_parameter",
84            HostlibError::InvalidParameter { .. } => "invalid_parameter",
85            HostlibError::Backend { .. } => "backend_error",
86        };
87        let builtin = err.builtin();
88        let message = err.to_string();
89
90        let mut dict: BTreeMap<String, VmValue> = BTreeMap::new();
91        dict.insert("kind".to_string(), VmValue::String(Rc::from(kind)));
92        dict.insert("builtin".to_string(), VmValue::String(Rc::from(builtin)));
93        dict.insert("message".to_string(), VmValue::String(Rc::from(message)));
94        VmError::Thrown(VmValue::Dict(Rc::new(dict)))
95    }
96}