Skip to main content

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