Skip to main content

stoffel/
error.rs

1//! SDK error types and recovery metadata.
2//!
3//! Errors keep lower-crate failures typed where possible while exposing stable
4//! categories for application recovery logic, logging, and status APIs.
5
6use std::fmt;
7use std::str::FromStr;
8
9use serde::{Deserialize, Serialize};
10
11pub use stoffel_mpc_coordinator_shared::CoordinatorError;
12pub use stoffelnet::network_utils::{ConsensusError, NetworkError};
13
14/// SDK result type.
15pub type Result<T> = std::result::Result<T, Error>;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "snake_case")]
19pub enum ErrorCategory {
20    Compilation,
21    Configuration,
22    Network,
23    Consensus,
24    Coordinator,
25    Preprocessing,
26    Computation,
27    Runtime,
28    Input,
29    Unsupported,
30    Io,
31    Bytecode,
32    ConfigParse,
33    ConfigSerialize,
34}
35
36impl ErrorCategory {
37    pub fn as_str(self) -> &'static str {
38        match self {
39            ErrorCategory::Compilation => "compilation",
40            ErrorCategory::Configuration => "configuration",
41            ErrorCategory::Network => "network",
42            ErrorCategory::Consensus => "consensus",
43            ErrorCategory::Coordinator => "coordinator",
44            ErrorCategory::Preprocessing => "preprocessing",
45            ErrorCategory::Computation => "computation",
46            ErrorCategory::Runtime => "runtime",
47            ErrorCategory::Input => "input",
48            ErrorCategory::Unsupported => "unsupported",
49            ErrorCategory::Io => "io",
50            ErrorCategory::Bytecode => "bytecode",
51            ErrorCategory::ConfigParse => "config_parse",
52            ErrorCategory::ConfigSerialize => "config_serialize",
53        }
54    }
55
56    pub fn is_recoverable(self) -> bool {
57        matches!(
58            self,
59            ErrorCategory::Network
60                | ErrorCategory::Consensus
61                | ErrorCategory::Preprocessing
62                | ErrorCategory::Io
63        )
64    }
65
66    pub fn recovery_hint(self) -> Option<&'static str> {
67        match self {
68            ErrorCategory::Network => {
69                Some("check connectivity and retry with the same program and inputs")
70            }
71            ErrorCategory::Consensus => Some(
72                "verify all parties see the same node/client set and consider a longer timeout",
73            ),
74            ErrorCategory::Preprocessing => {
75                Some("ensure each server has enough triples and random shares before retrying")
76            }
77            ErrorCategory::Io => Some("check the file path or filesystem permissions and retry"),
78            ErrorCategory::Configuration => {
79                Some("fix the invalid SDK configuration before retrying")
80            }
81            ErrorCategory::Input => {
82                Some("fix the provided function or client inputs before retrying")
83            }
84            ErrorCategory::Unsupported => {
85                Some("use a supported SDK execution mode or wire the required lower-level backend")
86            }
87            _ => None,
88        }
89    }
90}
91
92impl fmt::Display for ErrorCategory {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.write_str(self.as_str())
95    }
96}
97
98impl FromStr for ErrorCategory {
99    type Err = Error;
100
101    fn from_str(value: &str) -> Result<Self> {
102        match value.trim() {
103            "compilation" => Ok(ErrorCategory::Compilation),
104            "configuration" => Ok(ErrorCategory::Configuration),
105            "network" => Ok(ErrorCategory::Network),
106            "consensus" => Ok(ErrorCategory::Consensus),
107            "coordinator" => Ok(ErrorCategory::Coordinator),
108            "preprocessing" => Ok(ErrorCategory::Preprocessing),
109            "computation" => Ok(ErrorCategory::Computation),
110            "runtime" => Ok(ErrorCategory::Runtime),
111            "input" => Ok(ErrorCategory::Input),
112            "unsupported" => Ok(ErrorCategory::Unsupported),
113            "io" => Ok(ErrorCategory::Io),
114            "bytecode" => Ok(ErrorCategory::Bytecode),
115            "config_parse" => Ok(ErrorCategory::ConfigParse),
116            "config_serialize" => Ok(ErrorCategory::ConfigSerialize),
117            category => Err(Error::Configuration(format!(
118                "unsupported error category '{category}'"
119            ))),
120        }
121    }
122}
123
124#[derive(Debug, thiserror::Error)]
125pub enum Error {
126    #[error("Compilation failed: {0}")]
127    Compilation(String),
128    #[error("Invalid configuration: {0}")]
129    Configuration(String),
130    #[error("Network error: {0}")]
131    Network(#[from] NetworkError),
132    #[error("Network connection error: {0}")]
133    NetworkConnection(String),
134    #[error("Consensus failed: {0}")]
135    Consensus(#[from] ConsensusError),
136    #[error("Coordinator error: {0}")]
137    Coordinator(#[from] CoordinatorError),
138    #[error("Preprocessing failed: {0}")]
139    Preprocessing(String),
140    #[error("Computation failed: {0}")]
141    Computation(String),
142    #[error("Function not found: {0}")]
143    FunctionNotFound(String),
144    #[error("Invalid input: {0}")]
145    InvalidInput(String),
146    #[error("Unsupported SDK operation: {0}")]
147    Unsupported(String),
148    #[error("IO error: {0}")]
149    Io(#[from] std::io::Error),
150    #[error("Bytecode error: {0}")]
151    Bytecode(String),
152    #[error("Config parse error: {0}")]
153    ConfigParse(#[from] toml::de::Error),
154    #[error("Config serialize error: {0}")]
155    ConfigSerialize(#[from] toml::ser::Error),
156}
157
158impl Error {
159    pub fn category(&self) -> ErrorCategory {
160        match self {
161            Error::Compilation(_) => ErrorCategory::Compilation,
162            Error::Configuration(_) => ErrorCategory::Configuration,
163            Error::Network(_) => ErrorCategory::Network,
164            Error::NetworkConnection(_) => ErrorCategory::Network,
165            Error::Consensus(_) => ErrorCategory::Consensus,
166            Error::Coordinator(_) => ErrorCategory::Coordinator,
167            Error::Preprocessing(_) => ErrorCategory::Preprocessing,
168            Error::Computation(_) => ErrorCategory::Computation,
169            Error::FunctionNotFound(_) => ErrorCategory::Runtime,
170            Error::InvalidInput(_) => ErrorCategory::Input,
171            Error::Unsupported(_) => ErrorCategory::Unsupported,
172            Error::Io(_) => ErrorCategory::Io,
173            Error::Bytecode(_) => ErrorCategory::Bytecode,
174            Error::ConfigParse(_) => ErrorCategory::ConfigParse,
175            Error::ConfigSerialize(_) => ErrorCategory::ConfigSerialize,
176        }
177    }
178
179    pub fn is_recoverable(&self) -> bool {
180        self.category().is_recoverable()
181    }
182
183    pub fn recovery_hint(&self) -> Option<&'static str> {
184        self.category().recovery_hint()
185    }
186}
187
188pub(crate) fn format_compiler_errors<E: fmt::Display>(errors: &[E]) -> String {
189    errors
190        .iter()
191        .map(ToString::to_string)
192        .collect::<Vec<_>>()
193        .join("\n")
194}