1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Copyright 2019-2024 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT
//! We have three goals for our error shims:
//! - preserve upstream error _numbers_.
//! - preserve upstream error messages.
//! - allow _matching_ on specific errors.
//!
//! There are a couple of things that make this difficult:
//! - `fvm_shared*::error::ErrorNumber` is `#[non_exhaustive]`
//!
//! We have designed with the following assumptions about the `fvm*` crates:
//! - new error variants are append-only
//! - error messages are consistent between crates

use self::ErrorNumber as NShim;
use self::SyscallError as EShim;
use fvm2::kernel::SyscallError as E2;
use fvm3::kernel::SyscallError as E3;
use fvm4::kernel::SyscallError as E4;
use fvm_shared2::error::ErrorNumber as N2;
use fvm_shared3::error::ErrorNumber as N3;
use fvm_shared4::error::ErrorNumber as N4;
use num_traits::FromPrimitive;
use std::fmt;
use std::fmt::Debug;

macro_rules! error_number {
    ($($variant:ident),* $(,)?) => {
        #[derive(Debug, Clone)]
        pub enum ErrorNumber {
            $($variant,)*
            /// This is to catch `#[non_exhaustive]` upstream errors, and MUST NOT for be constructed
            Unknown(u32),
        }

        impl From<N4> for ErrorNumber {
            fn from(error: N4) -> Self {
                match error {
                    $(N4::$variant => Self::$variant,)*
                    _ => Self::Unknown(error as u32),
                }
            }
        }

        impl fmt::Display for ErrorNumber {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                match self {
                    $(Self::$variant => std::fmt::Display::fmt(&N4::$variant, f),)*
                    Self::Unknown(u) => std::fmt::Debug::fmt(&u, f),
                }
            }
        }
    }
}

error_number! {
    IllegalArgument,
    IllegalOperation,
    LimitExceeded,
    AssertionFailed,
    InsufficientFunds,
    NotFound,
    InvalidHandle,
    IllegalCid,
    IllegalCodec,
    Serialization,
    Forbidden,
    BufferTooSmall,
    ReadOnly,
}

impl From<N2> for ErrorNumber {
    fn from(value: N2) -> Self {
        let opt: Option<N4> = FromPrimitive::from_u32(value as u32);
        match opt {
            Some(err) => err.into(),
            None => Self::Unknown(value as u32),
        }
    }
}

impl From<N3> for ErrorNumber {
    fn from(value: N3) -> Self {
        let opt: Option<N4> = FromPrimitive::from_u32(value as u32);
        match opt {
            Some(err) => err.into(),
            None => Self::Unknown(value as u32),
        }
    }
}

#[derive(Debug, Clone, thiserror::Error)]
#[error("syscall error: {message} (exit_code={number})")]
pub struct SyscallError {
    pub message: String,
    pub number: NShim,
}

impl From<E2> for EShim {
    fn from(value: E2) -> Self {
        let E2(message, number) = value;
        Self {
            message,
            number: number.into(),
        }
    }
}

impl From<E3> for EShim {
    fn from(value: E3) -> Self {
        let E3(message, number) = value;
        Self {
            message,
            number: number.into(),
        }
    }
}

impl From<E4> for EShim {
    fn from(value: E4) -> Self {
        let E4(message, number) = value;
        Self {
            message,
            number: number.into(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_n2_error_fmt() {
        let shim: NShim = N2::IllegalArgument.into();

        assert_eq!(format!("{}", shim), "illegal argument");
    }

    #[test]
    fn test_n3_error_fmt() {
        let shim: NShim = N3::ReadOnly.into();

        assert_eq!(format!("{}", shim), "execution context is read-only");
    }

    #[test]
    fn test_unknown_error_fmt() {
        let shim: NShim = ErrorNumber::Unknown(23);

        assert_eq!(format!("{}", shim), "23");
    }
}