Skip to main content

xrpl_common_stdlib/host/
error_codes.rs

1use crate::host::Error::PointerOutOfBounds;
2use crate::host::trace::trace_num;
3use crate::host::{Error, Result, Result::Err, Result::Ok};
4
5/// The host function has an empty/stub implementation (not yet implemented by the host).
6pub const UNIMPLEMENTED: i32 = -1;
7/// The requested serialized field could not be found in the specified object.
8pub const FIELD_NOT_FOUND: i32 = -2;
9/// The provided buffer is too small to hold the requested data.
10pub const BUFFER_TOO_SMALL: i32 = -3;
11/// The API was asked to assume the object under analysis is an STArray but it was not.
12pub const NO_ARRAY: i32 = -4;
13/// The specified field is not a leaf field and cannot be accessed directly.
14pub const NOT_LEAF_FIELD: i32 = -5;
15/// The provided locator string is malformed or invalid.
16pub const LOCATOR_MALFORMED: i32 = -6;
17/// The specified slot number is outside the valid range.
18pub const SLOT_OUT_RANGE: i32 = -7;
19/// No free slots are available for allocation.
20pub const SLOTS_FULL: i32 = -8;
21/// The specified slot did not contain any slotted data (i.e., is empty).
22pub const EMPTY_SLOT: i32 = -9;
23/// The requested ledger object could not be found.
24pub const LEDGER_OBJ_NOT_FOUND: i32 = -10;
25/// The operation would exceed the allowed transfer limit.
26pub const OUT_OF_TRANSFER_LIMIT: i32 = -11;
27/// The data field is too large to be processed.
28pub const DATA_FIELD_TOO_LARGE: i32 = -12;
29/// A pointer or buffer length provided as a parameter described memory outside the allowed memory region.
30pub const POINTER_OUT_OF_BOUNDS: i32 = -13;
31/// No memory has been exported by the WebAssembly module.
32pub const NO_MEM_EXPORTED: i32 = -14;
33/// One or more of the parameters provided to the API are invalid.
34pub const INVALID_PARAMS: i32 = -15;
35/// The provided account identifier is invalid.
36pub const INVALID_ACCOUNT: i32 = -16;
37/// The specified field identifier is invalid or not recognized.
38pub const INVALID_FIELD: i32 = -17;
39/// The specified index is outside the valid bounds of the array or collection.
40pub const INDEX_OUT_OF_BOUNDS: i32 = -18;
41/// The input provided for floating-point parsing is malformed.
42pub const INVALID_FLOAT_INPUT: i32 = -19;
43/// An error occurred during floating-point computation.
44pub const INVALID_FLOAT_COMPUTATION: i32 = -20;
45
46// Library-owned error codes, reserved upward from `i32::MIN` so they can't collide with a host
47// code.
48
49/// A byte slice the host returned could not be decoded into the requested type.
50pub const INVALID_DECODING: i32 = i32::MIN;
51
52/// Stand-in for "the host returned some error", for tests that assert an error propagates rather
53/// than any particular code. The value is arbitrary; it only has to be a real host code the stdlib
54/// never constructs itself, so a passing test can't be explained by the code under test producing
55/// it independently. [`FIELD_NOT_FOUND`] cannot be used — the optional getters turn it into
56/// `Ok(None)`.
57#[cfg(any(test, feature = "test-host-bindings"))]
58pub const SOME_ERROR: i32 = NO_MEM_EXPORTED;
59
60/// Evaluates a result code and executes a closure on success (result_code > 0).
61///
62/// # Arguments
63///
64/// * `result_code` - An integer representing the operation result code
65/// * `on_success` - A closure that will be executed if result_code > 0
66///
67/// # Type Parameters
68///
69/// * `F` - The type of the closure
70/// * `T` - The return type of the closure
71///
72/// # Returns
73///
74/// Returns a `Result<T>` where:
75/// * `Ok(T)` - Contains the value returned by the closure if result_code > 0
76/// * `Ok(None)` - If result_code == 0 (no data/empty result)
77/// * `Err(Error)` - For negative result codes
78///
79/// # Note
80///
81/// This function treats 0 as a valid "no data" state and positive values as success.
82#[inline(always)]
83pub fn match_result_code<F, T>(result_code: i32, on_success: F) -> Result<T>
84where
85    F: FnOnce() -> T,
86{
87    match result_code {
88        code if code >= 0 => Ok(on_success()),
89        code => Err(Error::from_code(code)),
90    }
91}
92
93/// Evaluates a result code and executes a closure on success, handling optional return values.
94///
95/// This function is similar to `match_result_code` but is designed to work with closures
96/// that return `Option<T>` values, making it suitable for operations that may legitimately
97/// return no data even on success.
98///
99/// # Arguments
100///
101/// * `result_code` - An integer representing the operation result code
102/// * `on_success` - A closure that will be executed if result_code >= 0, returning `Option<T>`
103///
104/// # Type Parameters
105///
106/// * `F` - The type of the closure that returns `Option<T>`
107/// * `T` - The inner type of the optional value returned by the closure
108///
109/// # Returns
110///
111/// Returns a `Result<Option<T>>` where:
112/// * `Ok(Some(T))` - Contains the value returned by the closure if result_code >= 0 and closure returns Some
113/// * `Ok(None)` - If result_code >= 0 but the closure returns None
114/// * `Err(Error)` - For negative result codes
115///
116/// # Note
117///
118/// This function treats all non-negative result codes as success, allowing the closure
119/// to determine whether data is present through its Option return type.
120#[inline(always)]
121pub fn match_result_code_optional<F, T>(result_code: i32, on_success: F) -> Result<Option<T>>
122where
123    F: FnOnce() -> Option<T>,
124{
125    match result_code {
126        code if code >= 0 => Ok(on_success()),
127        code => Err(Error::from_code(code)),
128    }
129}
130
131/// Evaluates a result code against an expected number of bytes and executes a closure on exact match.
132///
133/// # Arguments
134///
135/// * `result_code` - An integer representing the operation result code
136/// * `expected_num_bytes` - The exact number of bytes expected to have been written
137/// * `on_success` - A closure that will be executed if the result code matches expected bytes
138///
139/// # Type Parameters
140///
141/// * `F` - The type of the closure
142/// * `T` - The return type of the closure
143///
144/// # Returns
145///
146/// Returns a `Result<T>` where:
147/// * `Ok(T)` - Contains the value returned by the closure if result_code matches expected_num_bytes
148/// * `Err(Error)` - For negative result codes
149///
150/// # Panics
151///
152/// Panics if `result_code` is non-negative but doesn't match `expected_num_bytes`. This
153/// signals an internal invariant violation (a host or stdlib bug) for which the caller has
154/// no recoverable course of action, rather than an input error.
155///
156/// # Note
157///
158/// This function requires an exact match between the result code and expected byte count,
159/// making it suitable for operations where the exact amount of data written is critical.
160#[inline]
161pub fn match_result_code_with_expected_bytes<F, T>(
162    result_code: i32,
163    expected_num_bytes: usize,
164    on_success: F,
165) -> Result<T>
166where
167    F: FnOnce() -> T,
168{
169    match result_code {
170        code if code as usize == expected_num_bytes => Ok(on_success()),
171        // Non-negative but wrong byte count: internal invariant violation (see `# Panics`).
172        code if code >= 0 => {
173            panic!(
174                "internal invariant violated: host wrote {code} bytes but {expected_num_bytes} were expected"
175            );
176        }
177        code => Err(Error::from_code(code)),
178    }
179}
180
181/// Evaluates a result code against expected bytes with optional field handling.
182///
183/// This function combines exact byte count validation with optional field semantics,
184/// making it suitable for operations that may encounter missing fields (which should
185/// return `None`) while still validating exact byte counts for present fields.
186///
187/// # Arguments
188///
189/// * `result_code` - An integer representing the operation result code (typically bytes written)
190/// * `expected_num_bytes` - The exact number of bytes expected for a successful operation
191/// * `on_success` - A closure that will be executed on exact byte match, returning `Option<T>`
192///
193/// # Type Parameters
194///
195/// * `F` - The type of the closure that returns `Option<T>`
196/// * `T` - The inner type of the optional value returned by the closure
197///
198/// # Returns
199///
200/// Returns a `Result<Option<T>>` where:
201/// * `Ok(Some(T))` - If result_code matches expected_num_bytes and closure returns Some
202/// * `Ok(None)` - If result_code matches expected_num_bytes and closure returns None, OR if result_code == FIELD_NOT_FOUND
203/// * `Err(PointerOutOfBounds)` - If result_code is non-negative but doesn't match expected bytes (with debug tracing)
204/// * `Err(Error)` - For other negative result codes (with debug tracing)
205///
206/// # Note
207///
208/// This function provides enhanced error handling with debug tracing for unexpected
209/// byte counts and error codes, making it easier to diagnose issues during development.
210/// The `FIELD_NOT_FOUND` error code is treated as a valid "no data" case.
211#[inline]
212pub fn match_result_code_with_expected_bytes_optional<F, T>(
213    result_code: i32,
214    expected_num_bytes: usize,
215    on_success: F,
216) -> Result<Option<T>>
217where
218    F: FnOnce() -> Option<T>,
219{
220    match result_code {
221        code if code as usize == expected_num_bytes => Ok(on_success()),
222        code if code == FIELD_NOT_FOUND => Ok(None),
223        // Handle all positive, unexpected values as an internal error.
224        code if code >= 0 => {
225            trace_num(
226                "Byte array was expected to have this many bytes: ",
227                expected_num_bytes as i64,
228            );
229            trace_num("Byte array had this many bytes: ", code as i64);
230            Err(PointerOutOfBounds)
231        }
232        // Handle all error values overtly.
233        code => {
234            trace_num("Encountered error_code:", code as i64);
235            Err(Error::from_code(code))
236        }
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    use crate::host::Error;
244    use crate::host::host_bindings_trait::MockHostBindings;
245    use crate::host::setup_mock;
246    use mockall::predicate::always;
247
248    #[test]
249    fn test_match_result_code_success_positive() {
250        let result = match_result_code(5, || "success");
251        assert!(result.is_ok());
252        assert_eq!(result.unwrap(), "success");
253    }
254
255    #[test]
256    fn test_match_result_code_success_zero() {
257        let result = match_result_code(0, || "zero_success");
258        assert!(result.is_ok());
259        assert_eq!(result.unwrap(), "zero_success");
260    }
261
262    #[test]
263    fn test_match_result_code_error_negative() {
264        let result = match_result_code(SOME_ERROR, || "should_not_execute");
265        assert_eq!(result.err().unwrap().code(), SOME_ERROR);
266    }
267
268    #[test]
269    fn test_match_result_code_error_field_not_found() {
270        let result = match_result_code(FIELD_NOT_FOUND, || "should_not_execute");
271        assert!(result.is_err());
272        assert_eq!(result.err().unwrap().code(), FIELD_NOT_FOUND);
273    }
274
275    #[test]
276    fn test_match_result_code_closure_not_called_on_error() {
277        let mut called = false;
278        let _result = match_result_code(BUFFER_TOO_SMALL, || {
279            called = true;
280            "should_not_execute"
281        });
282        assert!(!called);
283    }
284
285    #[test]
286    fn test_match_result_code_optional_success_some() {
287        let result = match_result_code_optional(10, || Some("data"));
288        assert!(result.is_ok());
289        assert_eq!(result.unwrap(), Some("data"));
290    }
291
292    #[test]
293    fn test_match_result_code_optional_success_none() {
294        let result = match_result_code_optional(0, || None::<&str>);
295        assert!(result.is_ok());
296        assert_eq!(result.unwrap(), None);
297    }
298
299    #[test]
300    fn test_match_result_code_optional_error() {
301        let result = match_result_code_optional(NO_ARRAY, || Some("should_not_execute"));
302        assert!(result.is_err());
303        assert_eq!(result.err().unwrap().code(), NO_ARRAY);
304    }
305
306    #[test]
307    fn test_match_result_code_with_expected_bytes_exact_match() {
308        let expected_bytes = 32;
309        let result = match_result_code_with_expected_bytes(32, expected_bytes, || "exact_match");
310        assert!(result.is_ok());
311        assert_eq!(result.unwrap(), "exact_match");
312    }
313
314    #[test]
315    #[should_panic]
316    fn test_match_result_code_with_expected_bytes_mismatch() {
317        let expected_bytes = 32;
318        // A non-negative code that doesn't match the expected byte count is an internal
319        // invariant violation and must panic.
320        let _ = match_result_code_with_expected_bytes(16, expected_bytes, || "should_not_execute");
321    }
322
323    #[test]
324    fn test_match_result_code_with_expected_bytes_negative_error() {
325        let expected_bytes = 32;
326        let result = match_result_code_with_expected_bytes(
327            INVALID_PARAMS,
328            expected_bytes,
329            || "should_not_execute",
330        );
331        assert!(result.is_err());
332        assert_eq!(result.err().unwrap().code(), INVALID_PARAMS);
333    }
334
335    #[test]
336    fn test_match_result_code_with_expected_bytes_zero_bytes() {
337        let expected_bytes = 0;
338        let result = match_result_code_with_expected_bytes(0, expected_bytes, || "zero_bytes");
339        assert!(result.is_ok());
340        assert_eq!(result.unwrap(), "zero_bytes");
341    }
342
343    #[test]
344    fn test_match_result_code_with_expected_bytes_optional_exact_match_some() {
345        let expected_bytes = 20;
346        let result =
347            match_result_code_with_expected_bytes_optional(20, expected_bytes, || Some("data"));
348        assert!(result.is_ok());
349        assert_eq!(result.unwrap(), Some("data"));
350    }
351
352    #[test]
353    fn test_match_result_code_with_expected_bytes_optional_exact_match_none() {
354        let expected_bytes = 20;
355        let result =
356            match_result_code_with_expected_bytes_optional(20, expected_bytes, || None::<&str>);
357        assert!(result.is_ok());
358        assert_eq!(result.unwrap(), None);
359    }
360
361    #[test]
362    fn test_match_result_code_with_expected_bytes_optional_field_not_found() {
363        let expected_bytes = 20;
364        let result =
365            match_result_code_with_expected_bytes_optional(FIELD_NOT_FOUND, expected_bytes, || {
366                Some("should_not_execute")
367            });
368        assert!(result.is_ok());
369        assert_eq!(result.unwrap(), None);
370    }
371
372    #[test]
373    fn test_match_result_code_with_expected_bytes_optional_byte_mismatch() {
374        let mut mock = MockHostBindings::new();
375
376        // Two traces on the error path, both carrying an Int64 payload.
377        mock.expect_trace()
378            .with(always(), always(), always(), always(), always())
379            .returning(|_, _, _, _, _| ())
380            .times(2);
381
382        let _guard = setup_mock(mock);
383
384        let expected_bytes = 20;
385        let result = match_result_code_with_expected_bytes_optional(15, expected_bytes, || {
386            Some("should_not_execute")
387        });
388        assert!(result.is_err());
389        assert_eq!(result.err().unwrap().code(), POINTER_OUT_OF_BOUNDS);
390    }
391
392    #[test]
393    fn test_match_result_code_with_expected_bytes_optional_other_error() {
394        let mut mock = MockHostBindings::new();
395
396        // One trace on the error path, carrying an Int64 payload.
397        mock.expect_trace()
398            .with(always(), always(), always(), always(), always())
399            .returning(|_, _, _, _, _| ());
400
401        let _guard = setup_mock(mock);
402
403        let expected_bytes = 20;
404        let result =
405            match_result_code_with_expected_bytes_optional(INVALID_ACCOUNT, expected_bytes, || {
406                Some("should_not_execute")
407            });
408        assert!(result.is_err());
409        assert_eq!(result.err().unwrap().code(), INVALID_ACCOUNT);
410    }
411
412    #[test]
413    fn test_match_result_code_with_expected_bytes_optional_zero_bytes() {
414        let expected_bytes = 0;
415        let result =
416            match_result_code_with_expected_bytes_optional(0, expected_bytes, || Some("zero_data"));
417        assert!(result.is_ok());
418        assert_eq!(result.unwrap(), Some("zero_data"));
419    }
420
421    #[test]
422    fn test_all_error_constants_are_negative() {
423        let error_codes = [
424            UNIMPLEMENTED,
425            FIELD_NOT_FOUND,
426            BUFFER_TOO_SMALL,
427            NO_ARRAY,
428            NOT_LEAF_FIELD,
429            LOCATOR_MALFORMED,
430            SLOT_OUT_RANGE,
431            SLOTS_FULL,
432            EMPTY_SLOT,
433            LEDGER_OBJ_NOT_FOUND,
434            OUT_OF_TRANSFER_LIMIT,
435            DATA_FIELD_TOO_LARGE,
436            POINTER_OUT_OF_BOUNDS,
437            NO_MEM_EXPORTED,
438            INVALID_PARAMS,
439            INVALID_ACCOUNT,
440            INVALID_FIELD,
441            INDEX_OUT_OF_BOUNDS,
442            INVALID_FLOAT_INPUT,
443            INVALID_FLOAT_COMPUTATION,
444            INVALID_DECODING,
445        ];
446
447        for &code in &error_codes {
448            assert!(code < 0, "Error code {} should be negative", code);
449        }
450    }
451
452    #[test]
453    fn test_error_constants_are_unique() {
454        let error_codes = [
455            UNIMPLEMENTED,
456            FIELD_NOT_FOUND,
457            BUFFER_TOO_SMALL,
458            NO_ARRAY,
459            NOT_LEAF_FIELD,
460            LOCATOR_MALFORMED,
461            SLOT_OUT_RANGE,
462            SLOTS_FULL,
463            EMPTY_SLOT,
464            LEDGER_OBJ_NOT_FOUND,
465            OUT_OF_TRANSFER_LIMIT,
466            DATA_FIELD_TOO_LARGE,
467            POINTER_OUT_OF_BOUNDS,
468            NO_MEM_EXPORTED,
469            INVALID_PARAMS,
470            INVALID_ACCOUNT,
471            INVALID_FIELD,
472            INDEX_OUT_OF_BOUNDS,
473            INVALID_FLOAT_INPUT,
474            INVALID_FLOAT_COMPUTATION,
475            INVALID_DECODING,
476        ];
477
478        // Check that all error codes are unique by comparing each pair
479        for (i, &code1) in error_codes.iter().enumerate() {
480            for (j, &code2) in error_codes.iter().enumerate() {
481                if i != j {
482                    assert_ne!(
483                        code1, code2,
484                        "Error codes at indices {} and {} are not unique: {} == {}",
485                        i, j, code1, code2
486                    );
487                }
488            }
489        }
490    }
491
492    #[test]
493    fn test_error_from_code_roundtrip() {
494        let test_codes = [
495            UNIMPLEMENTED,
496            FIELD_NOT_FOUND,
497            BUFFER_TOO_SMALL,
498            NO_ARRAY,
499            NOT_LEAF_FIELD,
500            LOCATOR_MALFORMED,
501            SLOT_OUT_RANGE,
502            SLOTS_FULL,
503            EMPTY_SLOT,
504            LEDGER_OBJ_NOT_FOUND,
505            OUT_OF_TRANSFER_LIMIT,
506            DATA_FIELD_TOO_LARGE,
507            POINTER_OUT_OF_BOUNDS,
508            NO_MEM_EXPORTED,
509            INVALID_PARAMS,
510            INVALID_ACCOUNT,
511            INVALID_FIELD,
512            INDEX_OUT_OF_BOUNDS,
513            INVALID_FLOAT_INPUT,
514            INVALID_FLOAT_COMPUTATION,
515            INVALID_DECODING,
516        ];
517
518        for &code in &test_codes {
519            let error = Error::from_code(code);
520            assert_eq!(
521                error.code(),
522                code,
523                "Error code roundtrip failed for code {}",
524                code
525            );
526        }
527    }
528
529    #[test]
530    fn test_closure_execution_count() {
531        let mut execution_count = 0;
532        let closure = || {
533            execution_count += 1;
534            "executed"
535        };
536
537        // Test that closure is executed exactly once on success
538        let _result = match_result_code(1, closure);
539        assert_eq!(execution_count, 1);
540
541        // Reset counter and test that closure is not executed on error
542        execution_count = 0;
543        let closure = || {
544            execution_count += 1;
545            "should_not_execute"
546        };
547        let _result = match_result_code(SOME_ERROR, closure);
548        assert_eq!(execution_count, 0);
549    }
550
551    #[test]
552    fn test_large_positive_result_codes() {
553        // Test with large positive numbers that might be typical byte counts
554        let large_positive = 1024;
555        let result = match_result_code(large_positive, || "large_success");
556        assert!(result.is_ok());
557        assert_eq!(result.unwrap(), "large_success");
558
559        // Test with expected bytes matching
560        let result = match_result_code_with_expected_bytes(
561            large_positive,
562            large_positive as usize,
563            || "exact_large",
564        );
565        assert!(result.is_ok());
566        assert_eq!(result.unwrap(), "exact_large");
567    }
568
569    #[test]
570    fn test_edge_case_usize_conversion() {
571        // Test edge case where result_code as usize might have conversion issues
572        let result_code = 255i32;
573        let expected_bytes = 255usize;
574        let result =
575            match_result_code_with_expected_bytes(result_code, expected_bytes, || "converted");
576        assert!(result.is_ok());
577        assert_eq!(result.unwrap(), "converted");
578    }
579}