Skip to content

Commit 62c8549

Browse files
feat(Mountain): Add structured error utilities for RPC responses
- Introduces `error_utils` module in Mountain backend handlers for standardized error formatting - Implements `rpc_error_string` and `rpc_param_error_string` to create JSON error responses compatible with VS Code extension expectations - Adds `map_common_error_to_rpc_string` to convert internal `CommonError` variants to user-facing error codes/messages - Enforces consistent error reporting across Track dispatcher and Vine IPC layer - Improves extension compatibility by mapping filesystem errors (ENOENT, EACCES) and configuration errors to known codes - Centralizes error logging at transformation boundaries to aid debugging while maintaining clean client-facing messages - Supports error handling requirements for Cocoon extension host shim communication via structured JSON protocols
1 parent a3762f7 commit 62c8549

2 files changed

Lines changed: 140 additions & 0 deletions

File tree

Source/Library.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ pub mod handlers {
4646
pub mod workspace;
4747

4848
pub mod workspace_fs_api;
49+
50+
pub mod error_utils;
4951
}
5052

5153
pub mod Entry;

Source/handlers/error_utils.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// ---------------------------------------------------------------------------------------------
2+
// Mountain Error Utilities (handlers/error_utils.rs)
3+
// --------------------------------------------------------------------------------------------
4+
// Provides shared utility functions for creating and formatting structured JSON
5+
// error strings, typically used for RPC responses or Tauri command error
6+
// results. Also includes helpers for mapping `CommonError` to these structured
7+
// strings, ensuring consistent error reporting across the application.
8+
//
9+
// Responsibilities:
10+
// - Defining a standard way to create JSON error strings (`rpc_error_string`).
11+
// - Providing a specialized helper for parameter validation errors
12+
// (`rpc_param_error_string`).
13+
// - Mapping internal `CommonError` types to user-facing JSON error strings
14+
// (`map_common_error_to_rpc_string`).
15+
// - Centralizing error logging for these common error paths.
16+
//
17+
// Key Interactions:
18+
// - Used by various handler modules (`handlers/*`) and RPC method
19+
// implementations (`rpc.rs`) to generate error responses for Track or Vine.
20+
// - Consumes `CommonError` from the effect system or other operations.
21+
// --------------------------------------------------------------------------------------------
22+
23+
use Land_Common::errors::CommonError;
24+
// For logging errors when they are created/mapped
25+
use log::error;
26+
// Value might not be needed if only creating strings
27+
use serde_json::{Value, json};
28+
29+
/// Creates a structured JSON error string.
30+
///
31+
/// # Arguments
32+
/// * `message` - The primary error message.
33+
/// * `code` - An optional error code string (e.g., "ENOENT", "EBADARG").
34+
/// Defaults to "EUNKNOWN_RPC_ERROR".
35+
///
36+
/// # Returns
37+
/// A JSON string representing the error.
38+
pub fn rpc_error_string(message:String, code:Option<&str>) -> String {
39+
// Keep: Log the error being created, can be helpful for seeing what errors are
40+
// being generated Potentially only log if it's a severe code, or make logging
41+
// conditional. For now, let's assume this utility is called when an error is
42+
// definitive.
43+
if code.unwrap_or("").starts_with('E') && code.unwrap_or("") != "SUCCESS" {
44+
// Avoid logging success "errors"
45+
error!("[RPC Error Created] Code: {:?}, Message: {}", code.unwrap_or("N/A"), message);
46+
}
47+
48+
json!({ "message": message, "code": code.unwrap_or("EUNKNOWN_RPC_ERROR") }).to_string()
49+
}
50+
51+
/// Creates a structured JSON error string specifically for parameter validation
52+
/// errors. Logs the error as well. The error code is fixed to "EBADARG".
53+
///
54+
/// # Arguments
55+
/// * `method_name` - The name of the method or command where the error
56+
/// occurred.
57+
/// * `param_name` - The name of the problematic parameter.
58+
/// * `expected_type` - A description of the expected type or format.
59+
/// * `idx` - Optional index of the parameter if it's in an array.
60+
///
61+
/// # Returns
62+
/// A JSON string representing the parameter error.
63+
pub fn rpc_param_error_string(method_name:&str, param_name:&str, expected_type:&str, idx:Option<usize>) -> String {
64+
let base_msg = format!(
65+
"Missing or invalid '{}' parameter (expected {}) for method/command '{}'",
66+
param_name, expected_type, method_name
67+
);
68+
69+
let full_msg = if let Some(i) = idx {
70+
format!("{} at arg index {}.", base_msg, i)
71+
} else {
72+
base_msg
73+
};
74+
75+
// Note: `rpc_error_string` will also log this.
76+
rpc_error_string(full_msg, Some("EBADARG"))
77+
}
78+
79+
/// Maps a `CommonError` from the effect system or other operations
80+
/// to a structured JSON error string suitable for RPC/Tauri responses.
81+
/// Logs the original error along with the context of the operation.
82+
///
83+
/// # Arguments
84+
/// * `e` - The `CommonError` instance.
85+
/// * `operation_context` - A string describing the operation during which the
86+
/// error occurred (for logging).
87+
///
88+
/// # Returns
89+
/// A JSON string representing the mapped error.
90+
pub fn map_common_error_to_rpc_string(e:CommonError, operation_context:&str) -> String {
91+
// The `rpc_error_string` will log the mapped error. Logging the original
92+
// `CommonError` here provides context before it's transformed.
93+
error!(
94+
"[CommonError Mapping] Operation '{}' resulted in CommonError: {:?}",
95+
operation_context, e
96+
);
97+
98+
let (message, code_str) = match e {
99+
CommonError::FsNotFound(p) => (format!("Resource not found: {}", p.display()), "ENOENT"),
100+
CommonError::FsPermissionDenied(p, m) => (format!("Permission denied for '{}': {}", p.display(), m), "EACCES"),
101+
CommonError::FsFileExists(p) => (format!("Resource already exists: {}", p.display()), "EEXIST"),
102+
CommonError::FsNotADirectory(p) => (format!("Path is not a directory: {}", p.display()), "ENOTDIR"),
103+
CommonError::FsIsADirectory(p) => (format!("Path is a directory: {}", p.display()), "EISDIR"),
104+
CommonError::FsNotEmpty(p) => (format!("Directory not empty: {}", p.display()), "ENOTEMPTY"),
105+
CommonError::FsRead(p, m) => (format!("Read error for '{}': {}", p.display(), m), "EIO_READ"),
106+
CommonError::FsWrite(p, m) => (format!("Write error for '{}': {}", p.display(), m), "EIO_WRITE"),
107+
CommonError::FsStat(p, m) => (format!("Stat error for '{}': {}", p.display(), m), "EIO_STAT"),
108+
CommonError::FsReadDir(p, m) => (format!("ReadDir error for '{}': {}", p.display(), m), "EIO_READDIR"),
109+
CommonError::FsMkdir(p, m) => (format!("Mkdir error for '{}': {}", p.display(), m), "EIO_MKDIR"),
110+
CommonError::FsDelete(p, m) => (format!("Delete error for '{}': {}", p.display(), m), "EIO_DELETE"),
111+
CommonError::FsRename(p, m) => (format!("Rename error for '{}': {}", p.display(), m), "EIO_RENAME"),
112+
CommonError::FsCopy(p, m) => (format!("Copy error for '{}': {}", p.display(), m), "EIO_COPY"),
113+
CommonError::ConfigUpdate(op, m) => {
114+
(format!("Configuration update error for '{}': {}", op, m), "ECONFIGUPDATE")
115+
},
116+
// Message from ConfigLoad is often sufficient
117+
CommonError::ConfigLoad(m) => (m, "ECONFIGLOAD"),
118+
CommonError::InvalidArg(arg_name, m) => (format!("Invalid argument '{}': {}", arg_name, m), "EBADARG"),
119+
CommonError::NotImplemented(feat) => (format!("Feature not implemented: {}", feat), "ENOSYS"),
120+
CommonError::StateLock(m) => (format!("Internal state access error: {}", m), "ESTATELOCK"),
121+
CommonError::IpcError(m) => (format!("Inter-process communication error: {}", m), "EIPC"),
122+
CommonError::CommandExecution(cmd, msg) => (format!("Command '{}' execution failed: {}", cmd, msg), "ECMDEXEC"),
123+
CommonError::CommandRegistration(cmd, msg) => {
124+
(format!("Command '{}' registration failed: {}", cmd, msg), "ECMDREG")
125+
},
126+
CommonError::CommandList(msg) => (format!("Failed to list commands: {}", msg), "ECMDLIST"),
127+
CommonError::SecretsAccess(key, msg) => (format!("Secret access for key '{}' failed: {}", key, msg), "ESECRET"),
128+
CommonError::OutputChannel(name, msg) => {
129+
(format!("Output channel '{}' operation failed: {}", name, msg), "EOUTPUT")
130+
},
131+
CommonError::Diagnostics(msg) => (format!("Diagnostics operation failed: {}", msg), "EDIAG"),
132+
CommonError::UiInteraction(msg) => (format!("UI interaction failed: {}", msg), "EUI"),
133+
// More specific general internal error
134+
CommonError::Unknown(m) => (m, "EUNKNOWN_INTERNAL_ERROR"),
135+
};
136+
137+
rpc_error_string(message, Some(code_str))
138+
}

0 commit comments

Comments
 (0)