iota_sdk_graphql_client/
error.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::num::{ParseIntError, TryFromIntError};
6
7use cynic::GraphQlError;
8use iota_types::{AddressParseError, DigestParseError, TypeParseError};
9
10type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
11
12pub type Result<T, E = Error> = std::result::Result<T, E>;
13
14/// General error type for the client. It is used to wrap all the possible
15/// errors that can occur.
16#[derive(Debug)]
17pub struct Error {
18    inner: Box<InnerError>,
19}
20
21/// Error type for the client. It is split into multiple fields to allow for
22/// more granular error handling. The `source` field is used to store the
23/// original error.
24#[derive(Debug)]
25struct InnerError {
26    /// Error kind.
27    kind: Kind,
28    /// Errors returned by the GraphQL server.
29    query_errors: Option<Vec<GraphQlError>>,
30    /// The original error.
31    source: Option<BoxError>,
32}
33
34#[derive(Debug, Copy, Clone)]
35#[non_exhaustive]
36pub enum Kind {
37    Deserialization,
38    Parse,
39    Query,
40    Missing,
41    Other,
42}
43
44impl std::error::Error for Error {
45    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
46        self.inner.source.as_deref().map(|e| e as _)
47    }
48}
49
50impl Error {
51    /// Original GraphQL query errors.
52    pub fn graphql_errors(&self) -> Option<&[GraphQlError]> {
53        self.inner.query_errors.as_deref()
54    }
55
56    /// Convert the given error into a generic error.
57    pub fn from_error<E: Into<BoxError>>(kind: Kind, error: E) -> Self {
58        Self {
59            inner: Box::new(InnerError {
60                kind,
61                source: Some(error.into()),
62                query_errors: None,
63            }),
64        }
65    }
66
67    /// Convert the given message into a generic error.
68    pub fn from_message(kind: Kind, message: String) -> Self {
69        Self {
70            inner: Box::new(InnerError {
71                kind,
72                source: Some(message.into()),
73                query_errors: None,
74            }),
75        }
76    }
77
78    /// Special constructor for queries that expect to return data but it's
79    /// none.
80    pub fn empty_response_error() -> Self {
81        Self {
82            inner: Box::new(InnerError {
83                kind: Kind::Query,
84                source: Some("Expected a non-empty response data from query".into()),
85                query_errors: None,
86            }),
87        }
88    }
89
90    /// Create a Query kind of error with the original graphql errors.
91    pub fn graphql_error(errors: Vec<GraphQlError>) -> Self {
92        Self {
93            inner: Box::new(InnerError {
94                kind: Kind::Query,
95                source: None,
96                query_errors: Some(errors),
97            }),
98        }
99    }
100}
101
102impl std::fmt::Display for Kind {
103    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
104        match self {
105            Kind::Deserialization => write!(f, "Deserialization error:"),
106            Kind::Parse => write!(f, "Parse error:"),
107            Kind::Query => write!(f, "Query error:"),
108            Kind::Missing => write!(f, "Missing:"),
109            Kind::Other => write!(f, "Error:"),
110        }
111    }
112}
113
114impl std::fmt::Display for Error {
115    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
116        write!(f, "{}", self.inner.kind)?;
117
118        if let Some(source) = &self.inner.source {
119            write!(f, " {source}")?;
120        }
121
122        if let Some(errors) = &self.inner.query_errors {
123            write!(
124                f,
125                " [{}]",
126                errors
127                    .iter()
128                    .map(|e| e.to_string())
129                    .collect::<Vec<_>>()
130                    .join(", ")
131            )?;
132        }
133        Ok(())
134    }
135}
136
137impl From<bcs::Error> for Error {
138    fn from(error: bcs::Error) -> Self {
139        Self::from_error(Kind::Deserialization, error)
140    }
141}
142
143impl From<reqwest::Error> for Error {
144    fn from(error: reqwest::Error) -> Self {
145        Self::from_error(Kind::Other, error)
146    }
147}
148
149impl From<url::ParseError> for Error {
150    fn from(error: url::ParseError) -> Self {
151        Self::from_error(Kind::Parse, error)
152    }
153}
154
155impl From<ParseIntError> for Error {
156    fn from(error: ParseIntError) -> Self {
157        Self::from_error(Kind::Parse, error)
158    }
159}
160
161impl From<AddressParseError> for Error {
162    fn from(error: AddressParseError) -> Self {
163        Self::from_error(Kind::Parse, error)
164    }
165}
166
167impl From<base64ct::Error> for Error {
168    fn from(error: base64ct::Error) -> Self {
169        Self::from_error(Kind::Parse, error)
170    }
171}
172
173impl From<chrono::ParseError> for Error {
174    fn from(error: chrono::ParseError) -> Self {
175        Self::from_error(Kind::Parse, error)
176    }
177}
178
179impl From<DigestParseError> for Error {
180    fn from(error: DigestParseError) -> Self {
181        Self::from_error(Kind::Parse, error)
182    }
183}
184
185impl From<TryFromIntError> for Error {
186    fn from(error: TryFromIntError) -> Self {
187        Self::from_error(Kind::Parse, error)
188    }
189}
190
191impl From<TypeParseError> for Error {
192    fn from(error: TypeParseError) -> Self {
193        Self::from_error(Kind::Parse, error)
194    }
195}