gstd/common/
errors.rs

1// This file is part of Gear.
2
3// Copyright (C) 2021-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Type definitions and helpers for error handling.
20//!
21//! Enumerates possible errors in programs `Error`.
22//! Errors related to conversion, decoding, message status code, other internal
23//! errors.
24
25pub use gcore::errors::{Error as CoreError, *};
26pub use scale_info::scale::Error as CodecError;
27
28use crate::ActorId;
29use alloc::vec::Vec;
30use core::{fmt, str};
31use gprimitives::utils::ByteSliceFormatter;
32use parity_scale_codec::Decode;
33
34/// `Result` type with a predefined error type ([`Error`]).
35pub type Result<T, E = Error> = core::result::Result<T, E>;
36
37/// Common error type returned by API functions from other modules.
38#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
39pub enum Error {
40    /* Protocol under-hood errors */
41    /// Error type from `gcore`.
42    ///
43    /// NOTE: this error could only be returned from syscalls.
44    #[error(transparent)]
45    Core(#[from] CoreError),
46
47    /* API lib under-hood errors */
48    /// Conversion error.
49    ///
50    /// NOTE: this error returns from incorrect bytes conversion.
51    #[error("Conversion error: {0}")]
52    Convert(#[from] ConversionError),
53
54    /// `scale-codec` decoding error.
55    ///
56    /// NOTE: this error returns from APIs that return specific `Decode` types.
57    #[error("Scale codec decoding error: {0}")]
58    Decode(CodecError),
59
60    /// Gstd API usage error.
61    ///
62    /// Note: this error returns from `gstd` APIs in case of invalid arguments.
63    #[error("Gstd API error: {0}")]
64    Gstd(#[from] UsageError),
65
66    /* Business logic errors */
67    /// Received error reply while awaited response from another actor.
68    ///
69    /// NOTE: this error could only be returned from async messaging.
70    // TODO: consider to load payload lazily (#4595)
71    #[error("Received error reply '{0}' due to {1}")]
72    ErrorReply(ErrorReplyPayload, ErrorReplyReason),
73
74    /// Received reply that couldn't be identified as successful or not
75    /// due to unsupported reply code.
76    ///
77    /// NOTE: this error could only be returned from async messaging.
78    #[error("Received unsupported reply '{hex}'", hex = ByteSliceFormatter::Dynamic(.0))]
79    UnsupportedReply(Vec<u8>),
80
81    /// Timeout reached while expecting for reply.
82    ///
83    /// NOTE: this error could only be returned from async messaging.
84    #[error("Timeout has occurred: expected at {0}, now {1}")]
85    Timeout(u32, u32),
86}
87
88impl Error {
89    /// Check whether an error is [`Error::Timeout`].
90    pub fn timed_out(&self) -> bool {
91        matches!(self, Error::Timeout(..))
92    }
93
94    /// Check whether an error is [`SimpleExecutionError::UserspacePanic`] from
95    /// error reply and return its decoded message's payload of
96    /// a custom type.
97    pub fn error_reply_panic<T: Decode>(&self) -> Option<Result<T, Self>> {
98        self.error_reply_panic_bytes()
99            .map(|mut bytes| T::decode(&mut bytes).map_err(Error::Decode))
100    }
101
102    /// Check whether an error is [`SimpleExecutionError::UserspacePanic`] from
103    /// error reply and return its payload as bytes.
104    pub fn error_reply_panic_bytes(&self) -> Option<&[u8]> {
105        if let Self::ErrorReply(
106            payload,
107            ErrorReplyReason::Execution(SimpleExecutionError::UserspacePanic),
108        ) = self
109        {
110            Some(&payload.0)
111        } else {
112            None
113        }
114    }
115
116    /// Check whether an error is [`SimpleUnavailableActorError::ProgramExited`]
117    /// from error reply and return inheritor of exited program.
118    pub fn error_reply_exit_inheritor(&self) -> Option<ActorId> {
119        if let Self::ErrorReply(
120            payload,
121            ErrorReplyReason::UnavailableActor(SimpleUnavailableActorError::ProgramExited),
122        ) = self
123        {
124            let id = ActorId::try_from(payload.0.as_slice())
125                .unwrap_or_else(|e| unreachable!("protocol always returns valid `ActorId`: {e}"));
126            Some(id)
127        } else {
128            None
129        }
130    }
131}
132
133/// New-type representing error reply payload. Expected to be utf-8 string.
134#[derive(Clone, Eq, PartialEq)]
135pub struct ErrorReplyPayload(pub Vec<u8>);
136
137impl ErrorReplyPayload {
138    /// Returns byte slice.
139    pub fn as_bytes(&self) -> &[u8] {
140        self.0.as_slice()
141    }
142
143    /// Represents self as utf-8 str, if possible.
144    pub fn try_as_str(&self) -> Option<&str> {
145        str::from_utf8(&self.0).ok()
146    }
147
148    /// Returns inner byte vector.
149    pub fn into_inner(self) -> Vec<u8> {
150        self.0
151    }
152}
153
154impl From<Vec<u8>> for ErrorReplyPayload {
155    fn from(value: Vec<u8>) -> Self {
156        Self(value)
157    }
158}
159
160impl fmt::Debug for ErrorReplyPayload {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        self.try_as_str()
163            .map(|v| write!(f, "{v}"))
164            .unwrap_or_else(|| write!(f, "{}", ByteSliceFormatter::Dynamic(&self.0)))
165    }
166}
167
168impl fmt::Display for ErrorReplyPayload {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        fmt::Debug::fmt(self, f)
171    }
172}
173
174/// Error type returned by gstd API while using invalid arguments.
175#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
176pub enum UsageError {
177    /// This error occurs when providing zero duration to waiting functions
178    /// (e.g. see `exactly` and `up_to` functions in
179    /// [`CodecMessageFuture`](crate::msg::CodecMessageFuture)).
180    #[error("Wait duration can not be zero")]
181    EmptyWaitDuration,
182    /// This error occurs when providing zero gas amount to system gas reserving
183    /// function (see
184    /// [`Config::set_system_reserve`](crate::Config::set_system_reserve)).
185    #[error("System reservation amount can not be zero in config")]
186    ZeroSystemReservationAmount,
187    /// This error occurs when providing zero duration to mutex lock function
188    #[error("Mutex lock duration can not be zero")]
189    ZeroMxLockDuration,
190    /// This error occurs when handle_reply is called without (or with zero)
191    /// reply deposit
192    /// (see [`MessageFuture::handle_reply`](crate::msg::MessageFuture::handle_reply)).
193    #[error("Reply deposit can not be zero when setting reply hook")]
194    ZeroReplyDeposit,
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    use crate::{format, vec};
201
202    #[test]
203    fn error_unsupported_reply_display() {
204        let payload = Error::UnsupportedReply(vec![1, 2, 3]);
205        assert_eq!(
206            format!("{payload}"),
207            "Received unsupported reply '0x010203'"
208        );
209    }
210}