Skip to main content

ghidra_ipc/
error_map.rs

1//! Total map from the Java worker's string error codes to the Rust `ErrorCode` set (spec ยง4.1).
2//! Every known worker string maps to a variant; any UNKNOWN string falls back to `WorkerUnavailable`
3//! with the raw code preserved in `details.worker_code` so nothing is silently lost.
4
5use crate::error::{ErrorCode, ErrorEnvelope};
6use serde_json::json;
7
8/// Map a `(worker_code, message)` pair from an `RpcResponse::Failure` error object into a structured
9/// `ErrorEnvelope`. `worker_code` is the string in the worker error's `code` field; `message` its
10/// `message` field. Unknown codes are preserved (never dropped).
11pub fn map_worker_error(worker_code: &str, message: &str) -> ErrorEnvelope {
12    let code = match worker_code {
13        "PROGRAM_NOT_OPEN" => ErrorCode::ProgramNotOpen,
14        "PROGRAM_NOT_FOUND" => ErrorCode::ProgramNotFound,
15        "NOT_A_FUNCTION" => ErrorCode::NotAFunction,
16        "AMBIGUOUS_SYMBOL" => ErrorCode::AmbiguousSymbol,
17        "INVALID_PARAMS" => ErrorCode::InvalidParams,
18        "DECOMPILE_TIMEOUT" => ErrorCode::DecompileTimeout,
19        "DECOMPILATION_FAILED" => ErrorCode::DecompilationFailed,
20        "FRAME_TOO_LARGE" => ErrorCode::FrameTooLarge,
21        "PARSE_ERROR" => ErrorCode::ParseError,
22        "WORKER_UNAVAILABLE" => ErrorCode::WorkerUnavailable,
23        "SYMBOL_NOT_FOUND" => ErrorCode::SymbolNotFound,
24        "VARIABLE_NOT_FOUND" => ErrorCode::VariableNotFound,
25        "DATATYPE_NOT_FOUND" => ErrorCode::DatatypeNotFound,
26        "AMBIGUOUS_DATATYPE" => ErrorCode::AmbiguousDatatype,
27        "ADDRESS_NOT_FOUND" => ErrorCode::AddressNotFound,
28        "RENAME_CONFLICT" => ErrorCode::RenameConflict,
29        "STATE_CONFLICT" => ErrorCode::StateConflict,
30        "TRANSACTION_FAILED" => ErrorCode::TransactionFailed,
31        unknown => {
32            return ErrorEnvelope::new(ErrorCode::WorkerUnavailable, message.to_string())
33                .with_details(json!({ "worker_code": unknown }));
34        }
35    };
36    ErrorEnvelope::new(code, message.to_string())
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    // Every string the M0 worker can emit (GhidraMcpWorker.java: INVALID_PARAMS, PROGRAM_NOT_FOUND,
44    // PROGRAM_NOT_OPEN, NOT_A_FUNCTION, DECOMPILE_TIMEOUT, WORKER_UNAVAILABLE, FRAME_TOO_LARGE,
45    // PARSE_ERROR), the M1a additions (AMBIGUOUS_SYMBOL, DECOMPILATION_FAILED), the M1b read/nav
46    // additions (SYMBOL_NOT_FOUND, DATATYPE_NOT_FOUND, AMBIGUOUS_DATATYPE, ADDRESS_NOT_FOUND), and the
47    // M2a write-foundations addition (RENAME_CONFLICT). COMPLETE list.
48    #[test]
49    fn every_known_worker_code_maps_to_its_variant() {
50        let cases = [
51            ("PROGRAM_NOT_OPEN", ErrorCode::ProgramNotOpen),
52            ("PROGRAM_NOT_FOUND", ErrorCode::ProgramNotFound),
53            ("NOT_A_FUNCTION", ErrorCode::NotAFunction),
54            ("AMBIGUOUS_SYMBOL", ErrorCode::AmbiguousSymbol),
55            ("INVALID_PARAMS", ErrorCode::InvalidParams),
56            ("DECOMPILE_TIMEOUT", ErrorCode::DecompileTimeout),
57            ("DECOMPILATION_FAILED", ErrorCode::DecompilationFailed),
58            ("FRAME_TOO_LARGE", ErrorCode::FrameTooLarge),
59            ("PARSE_ERROR", ErrorCode::ParseError),
60            ("WORKER_UNAVAILABLE", ErrorCode::WorkerUnavailable),
61            ("SYMBOL_NOT_FOUND", ErrorCode::SymbolNotFound),
62            ("VARIABLE_NOT_FOUND", ErrorCode::VariableNotFound),
63            ("DATATYPE_NOT_FOUND", ErrorCode::DatatypeNotFound),
64            ("AMBIGUOUS_DATATYPE", ErrorCode::AmbiguousDatatype),
65            ("ADDRESS_NOT_FOUND", ErrorCode::AddressNotFound),
66            ("RENAME_CONFLICT", ErrorCode::RenameConflict),
67        ];
68        for (s, want) in cases {
69            assert_eq!(map_worker_error(s, "m").error.code, want, "code {s}");
70        }
71    }
72
73    #[test]
74    fn unknown_code_falls_back_and_preserves_raw_string() {
75        let env = map_worker_error("SOME_FUTURE_CODE", "boom");
76        assert_eq!(env.error.code, ErrorCode::WorkerUnavailable);
77        assert_eq!(env.error.message, "boom");
78        assert_eq!(
79            env.error.details.unwrap()["worker_code"],
80            "SOME_FUTURE_CODE"
81        );
82    }
83
84    #[test]
85    fn state_conflict_and_transaction_failed_map_to_typed_codes() {
86        use crate::error::ErrorCode;
87        assert_eq!(
88            super::map_worker_error("STATE_CONFLICT", "drift")
89                .error
90                .code,
91            ErrorCode::StateConflict
92        );
93        assert_eq!(
94            super::map_worker_error("TRANSACTION_FAILED", "rolled back")
95                .error
96                .code,
97            ErrorCode::TransactionFailed
98        );
99    }
100}