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}