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 intentionally scaffolded contracts from runtime failures.
17#[derive(Debug, thiserror::Error)]
18pub enum HostlibError {
19 /// The method exists in the registration table but has no implementation
20 /// yet. This is the canonical scaffold-stage error: it tells callers
21 /// "the contract is stable, but this module has not been implemented."
22 #[error(
23 "hostlib: {builtin} is not implemented yet (scaffolded contract without an implementation)"
24 )]
25 Unimplemented {
26 /// Fully-qualified builtin name, e.g. `"hostlib_ast_parse_file"`.
27 builtin: &'static str,
28 },
29
30 /// A required parameter was missing from the call payload.
31 #[error("hostlib: {builtin}: missing required parameter '{param}'")]
32 MissingParameter {
33 /// Fully-qualified builtin name.
34 builtin: &'static str,
35 /// Name of the missing parameter.
36 param: &'static str,
37 },
38
39 /// A parameter was present but had the wrong shape (wrong type, malformed).
40 #[error("hostlib: {builtin}: invalid parameter '{param}': {message}")]
41 InvalidParameter {
42 /// Fully-qualified builtin name.
43 builtin: &'static str,
44 /// Name of the invalid parameter.
45 param: &'static str,
46 /// Human-readable description of the violation.
47 message: String,
48 },
49
50 /// Catch-all wrapper for I/O, parsing, or other backend failures.
51 #[error("hostlib: {builtin}: {message}")]
52 Backend {
53 /// Fully-qualified builtin name.
54 builtin: &'static str,
55 /// Human-readable failure description.
56 message: String,
57 },
58}
59
60impl HostlibError {
61 /// The fully-qualified builtin name this error came from. Useful for
62 /// embedder logging and for the routing tests in `tests/`.
63 pub fn builtin(&self) -> &'static str {
64 match self {
65 HostlibError::Unimplemented { builtin }
66 | HostlibError::MissingParameter { builtin, .. }
67 | HostlibError::InvalidParameter { builtin, .. }
68 | HostlibError::Backend { builtin, .. } => builtin,
69 }
70 }
71}
72
73impl From<HostlibError> for VmError {
74 fn from(err: HostlibError) -> VmError {
75 // Surface as a `Thrown` dict so Harn `try`/`catch` can pattern-match
76 // on `kind`, `builtin`, and `message`. This matches how the existing
77 // `host_call` error path shapes its exceptions.
78 let kind = match err {
79 HostlibError::Unimplemented { .. } => "unimplemented",
80 HostlibError::MissingParameter { .. } => "missing_parameter",
81 HostlibError::InvalidParameter { .. } => "invalid_parameter",
82 HostlibError::Backend { .. } => "backend_error",
83 };
84 let builtin = err.builtin();
85 let message = err.to_string();
86
87 let mut dict: BTreeMap<String, VmValue> = BTreeMap::new();
88 dict.insert("kind".to_string(), VmValue::String(Rc::from(kind)));
89 dict.insert("builtin".to_string(), VmValue::String(Rc::from(builtin)));
90 dict.insert("message".to_string(), VmValue::String(Rc::from(message)));
91 VmError::Thrown(VmValue::Dict(Rc::new(dict)))
92 }
93}