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
221    #[test]
222    fn test_match_result_code_success_positive() {
223        let result = match_result_code(5, || "success");
224        assert!(result.is_ok());
225        assert_eq!(result.unwrap(), "success");
226    }
227
228    #[test]
229    fn test_match_result_code_success_zero() {
230        let result = match_result_code(0, || "zero_success");
231        assert!(result.is_ok());
232        assert_eq!(result.unwrap(), "zero_success");
233    }
234
235    #[test]
236    fn test_match_result_code_error_negative() {
237        let result = match_result_code(INTERNAL_ERROR, || "should_not_execute");
238        assert!(result.is_err());
239        assert_eq!(result.err().unwrap().code(), INTERNAL_ERROR);
240    }
241
242    #[test]
243    fn test_match_result_code_error_field_not_found() {
244        let result = match_result_code(FIELD_NOT_FOUND, || "should_not_execute");
245        assert!(result.is_err());
246        assert_eq!(result.err().unwrap().code(), FIELD_NOT_FOUND);
247    }
248
249    #[test]
250    fn test_match_result_code_closure_not_called_on_error() {
251        let mut called = false;
252        let _result = match_result_code(BUFFER_TOO_SMALL, || {
253            called = true;
254            "should_not_execute"
255        });
256        assert!(!called);
257    }
258
259    #[test]
260    fn test_match_result_code_optional_success_some() {
261        let result = match_result_code_optional(10, || Some("data"));
262        assert!(result.is_ok());
263        assert_eq!(result.unwrap(), Some("data"));
264    }
265
266    #[test]
267    fn test_match_result_code_optional_success_none() {
268        let result = match_result_code_optional(0, || None::<&str>);
269        assert!(result.is_ok());
270        assert_eq!(result.unwrap(), None);
271    }
272
273    #[test]
274    fn test_match_result_code_optional_error() {
275        let result = match_result_code_optional(NO_ARRAY, || Some("should_not_execute"));
276        assert!(result.is_err());
277        assert_eq!(result.err().unwrap().code(), NO_ARRAY);
278    }
279
280    #[test]
281    fn test_match_result_code_with_expected_bytes_exact_match() {
282        let expected_bytes = 32;
283        let result = match_result_code_with_expected_bytes(32, expected_bytes, || "exact_match");
284        assert!(result.is_ok());
285        assert_eq!(result.unwrap(), "exact_match");
286    }
287
288    #[test]
289    fn test_match_result_code_with_expected_bytes_mismatch() {
290        let expected_bytes = 32;
291        let result =
292            match_result_code_with_expected_bytes(16, expected_bytes, || "should_not_execute");
293        assert!(result.is_err());
294        assert_eq!(result.err().unwrap().code(), INTERNAL_ERROR);
295    }
296
297    #[test]
298    fn test_match_result_code_with_expected_bytes_negative_error() {
299        let expected_bytes = 32;
300        let result = match_result_code_with_expected_bytes(
301            INVALID_PARAMS,
302            expected_bytes,
303            || "should_not_execute",
304        );
305        assert!(result.is_err());
306        assert_eq!(result.err().unwrap().code(), INVALID_PARAMS);
307    }
308
309    #[test]
310    fn test_match_result_code_with_expected_bytes_zero_bytes() {
311        let expected_bytes = 0;
312        let result = match_result_code_with_expected_bytes(0, expected_bytes, || "zero_bytes");
313        assert!(result.is_ok());
314        assert_eq!(result.unwrap(), "zero_bytes");
315    }
316
317    #[test]
318    fn test_match_result_code_with_expected_bytes_optional_exact_match_some() {
319        let expected_bytes = 20;
320        let result =
321            match_result_code_with_expected_bytes_optional(20, expected_bytes, || Some("data"));
322        assert!(result.is_ok());
323        assert_eq!(result.unwrap(), Some("data"));
324    }
325
326    #[test]
327    fn test_match_result_code_with_expected_bytes_optional_exact_match_none() {
328        let expected_bytes = 20;
329        let result =
330            match_result_code_with_expected_bytes_optional(20, expected_bytes, || None::<&str>);
331        assert!(result.is_ok());
332        assert_eq!(result.unwrap(), None);
333    }
334
335    #[test]
336    fn test_match_result_code_with_expected_bytes_optional_field_not_found() {
337        let expected_bytes = 20;
338        let result =
339            match_result_code_with_expected_bytes_optional(FIELD_NOT_FOUND, expected_bytes, || {
340                Some("should_not_execute")
341            });
342        assert!(result.is_ok());
343        assert_eq!(result.unwrap(), None);
344    }
345
346    #[test]
347    fn test_match_result_code_with_expected_bytes_optional_byte_mismatch() {
348        let expected_bytes = 20;
349        let result = match_result_code_with_expected_bytes_optional(15, expected_bytes, || {
350            Some("should_not_execute")
351        });
352        assert!(result.is_err());
353        assert_eq!(result.err().unwrap().code(), POINTER_OUT_OF_BOUNDS);
354    }
355
356    #[test]
357    fn test_match_result_code_with_expected_bytes_optional_other_error() {
358        let expected_bytes = 20;
359        let result =
360            match_result_code_with_expected_bytes_optional(INVALID_ACCOUNT, expected_bytes, || {
361                Some("should_not_execute")
362            });
363        assert!(result.is_err());
364        assert_eq!(result.err().unwrap().code(), INVALID_ACCOUNT);
365    }
366
367    #[test]
368    fn test_match_result_code_with_expected_bytes_optional_zero_bytes() {
369        let expected_bytes = 0;
370        let result =
371            match_result_code_with_expected_bytes_optional(0, expected_bytes, || Some("zero_data"));
372        assert!(result.is_ok());
373        assert_eq!(result.unwrap(), Some("zero_data"));
374    }
375
376    #[test]
377    fn test_all_error_constants_are_negative() {
378        let error_codes = [
379            INTERNAL_ERROR,
380            FIELD_NOT_FOUND,
381            BUFFER_TOO_SMALL,
382            NO_ARRAY,
383            NOT_LEAF_FIELD,
384            LOCATOR_MALFORMED,
385            SLOT_OUT_RANGE,
386            SLOTS_FULL,
387            EMPTY_SLOT,
388            LEDGER_OBJ_NOT_FOUND,
389            INVALID_DECODING,
390            DATA_FIELD_TOO_LARGE,
391            POINTER_OUT_OF_BOUNDS,
392            NO_MEM_EXPORTED,
393            INVALID_PARAMS,
394            INVALID_ACCOUNT,
395            INVALID_FIELD,
396            INDEX_OUT_OF_BOUNDS,
397            INVALID_FLOAT_INPUT,
398            INVALID_FLOAT_COMPUTATION,
399        ];
400
401        for &code in &error_codes {
402            assert!(code < 0, "Error code {} should be negative", code);
403        }
404    }
405
406    #[test]
407    fn test_error_constants_are_unique() {
408        let error_codes = [
409            INTERNAL_ERROR,
410            FIELD_NOT_FOUND,
411            BUFFER_TOO_SMALL,
412            NO_ARRAY,
413            NOT_LEAF_FIELD,
414            LOCATOR_MALFORMED,
415            SLOT_OUT_RANGE,
416            SLOTS_FULL,
417            EMPTY_SLOT,
418            LEDGER_OBJ_NOT_FOUND,
419            INVALID_DECODING,
420            DATA_FIELD_TOO_LARGE,
421            POINTER_OUT_OF_BOUNDS,
422            NO_MEM_EXPORTED,
423            INVALID_PARAMS,
424            INVALID_ACCOUNT,
425            INVALID_FIELD,
426            INDEX_OUT_OF_BOUNDS,
427            INVALID_FLOAT_INPUT,
428            INVALID_FLOAT_COMPUTATION,
429        ];
430
431        // Check that all error codes are unique by comparing each pair
432        for (i, &code1) in error_codes.iter().enumerate() {
433            for (j, &code2) in error_codes.iter().enumerate() {
434                if i != j {
435                    assert_ne!(
436                        code1, code2,
437                        "Error codes at indices {} and {} are not unique: {} == {}",
438                        i, j, code1, code2
439                    );
440                }
441            }
442        }
443    }
444
445    #[test]
446    fn test_error_from_code_roundtrip() {
447        let test_codes = [
448            INTERNAL_ERROR,
449            FIELD_NOT_FOUND,
450            BUFFER_TOO_SMALL,
451            NO_ARRAY,
452            NOT_LEAF_FIELD,
453            LOCATOR_MALFORMED,
454            SLOT_OUT_RANGE,
455            SLOTS_FULL,
456            EMPTY_SLOT,
457            LEDGER_OBJ_NOT_FOUND,
458            INVALID_DECODING,
459            DATA_FIELD_TOO_LARGE,
460            POINTER_OUT_OF_BOUNDS,
461            NO_MEM_EXPORTED,
462            INVALID_PARAMS,
463            INVALID_ACCOUNT,
464            INVALID_FIELD,
465            INDEX_OUT_OF_BOUNDS,
466            INVALID_FLOAT_INPUT,
467            INVALID_FLOAT_COMPUTATION,
468        ];
469
470        for &code in &test_codes {
471            let error = Error::from_code(code);
472            assert_eq!(
473                error.code(),
474                code,
475                "Error code roundtrip failed for code {}",
476                code
477            );
478        }
479    }
480
481    #[test]
482    fn test_closure_execution_count() {
483        let mut execution_count = 0;
484        let closure = || {
485            execution_count += 1;
486            "executed"
487        };
488
489        // Test that closure is executed exactly once on success
490        let _result = match_result_code(1, closure);
491        assert_eq!(execution_count, 1);
492
493        // Reset counter and test that closure is not executed on error
494        execution_count = 0;
495        let closure = || {
496            execution_count += 1;
497            "should_not_execute"
498        };
499        let _result = match_result_code(INTERNAL_ERROR, closure);
500        assert_eq!(execution_count, 0);
501    }
502
503    #[test]
504    fn test_large_positive_result_codes() {
505        // Test with large positive numbers that might be typical byte counts
506        let large_positive = 1024;
507        let result = match_result_code(large_positive, || "large_success");
508        assert!(result.is_ok());
509        assert_eq!(result.unwrap(), "large_success");
510
511        // Test with expected bytes matching
512        let result = match_result_code_with_expected_bytes(
513            large_positive,
514            large_positive as usize,
515            || "exact_large",
516        );
517        assert!(result.is_ok());
518        assert_eq!(result.unwrap(), "exact_large");
519    }
520
521    #[test]
522    fn test_edge_case_usize_conversion() {
523        // Test edge case where result_code as usize might have conversion issues
524        let result_code = 255i32;
525        let expected_bytes = 255usize;
526        let result =
527            match_result_code_with_expected_bytes(result_code, expected_bytes, || "converted");
528        assert!(result.is_ok());
529        assert_eq!(result.unwrap(), "converted");
530    }
531}