1use crate::host::Error::PointerOutOfBounds;
2use crate::host::trace::trace_num;
3use crate::host::{Error, Result, Result::Err, Result::Ok};
4
5pub const INTERNAL_ERROR: i32 = -1;
7pub const FIELD_NOT_FOUND: i32 = -2;
9pub const BUFFER_TOO_SMALL: i32 = -3;
11pub const NO_ARRAY: i32 = -4;
13pub const NOT_LEAF_FIELD: i32 = -5;
15pub const LOCATOR_MALFORMED: i32 = -6;
17pub const SLOT_OUT_RANGE: i32 = -7;
19pub const SLOTS_FULL: i32 = -8;
21pub const EMPTY_SLOT: i32 = -9;
23pub const LEDGER_OBJ_NOT_FOUND: i32 = -10;
25pub const INVALID_DECODING: i32 = -11;
27pub const DATA_FIELD_TOO_LARGE: i32 = -12;
29pub const POINTER_OUT_OF_BOUNDS: i32 = -13;
31pub const NO_MEM_EXPORTED: i32 = -14;
33pub const INVALID_PARAMS: i32 = -15;
35pub const INVALID_ACCOUNT: i32 = -16;
37pub const INVALID_FIELD: i32 = -17;
39pub const INDEX_OUT_OF_BOUNDS: i32 = -18;
41pub const INVALID_FLOAT_INPUT: i32 = -19;
43pub const INVALID_FLOAT_COMPUTATION: i32 = -20;
45
46#[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#[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#[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 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#[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 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 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 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 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 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 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 let _result = match_result_code(1, closure);
523 assert_eq!(execution_count, 1);
524
525 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 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 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 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}