xrpl_wasm_stdlib/host/
error_codes.rs

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