use serde::Deserialize;
use std::collections::HashMap;
use std::fmt;
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
#[error("Request error: {0}")]
Request(#[from] reqwest::Error),
#[error("Invalid URL: {0}")]
InvalidUrl(#[from] url::ParseError),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Deserialization error: {0}")]
Deserialization(String),
#[error("Missing expected data: {0}")]
MissingData(&'static str),
}
impl From<base64ct::Error> for Error {
fn from(err: base64ct::Error) -> Self {
Self::Deserialization(format!("base64 decode: {err}"))
}
}
impl From<bcs::Error> for Error {
fn from(err: bcs::Error) -> Self {
Self::Deserialization(format!("bcs decode: {err}"))
}
}
impl From<sui_sdk_types::TypeParseError> for Error {
fn from(err: sui_sdk_types::TypeParseError) -> Self {
Self::Deserialization(format!("type parse: {err}"))
}
}
impl From<std::num::ParseIntError> for Error {
fn from(err: std::num::ParseIntError) -> Self {
Self::Deserialization(format!("integer parse: {err}"))
}
}
impl From<sui_sdk_types::DigestParseError> for Error {
fn from(err: sui_sdk_types::DigestParseError) -> Self {
Self::Deserialization(format!("digest parse: {err}"))
}
}
#[derive(Debug, Deserialize)]
pub struct GraphQLError {
message: String,
locations: Option<Vec<Location>>,
path: Option<Vec<PathFragment>>,
extensions: Option<HashMap<String, serde_json::Value>>,
}
impl fmt::Display for GraphQLError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl GraphQLError {
pub fn message(&self) -> &str {
&self.message
}
pub fn locations(&self) -> Option<&[Location]> {
self.locations.as_deref()
}
pub fn path(&self) -> Option<&[PathFragment]> {
self.path.as_deref()
}
pub fn extensions(&self) -> Option<&HashMap<String, serde_json::Value>> {
self.extensions.as_ref()
}
pub fn code(&self) -> Option<&str> {
self.extensions.as_ref()?.get("code")?.as_str()
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum PathFragment {
Key(String),
Index(i32),
}
#[derive(Debug, Deserialize)]
pub struct Location {
pub line: i32,
pub column: i32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = Error::Request(
reqwest::Client::new()
.get("not a valid url")
.build()
.unwrap_err(),
);
assert!(err.to_string().contains("Request error"));
}
#[test]
fn test_graphql_error_display() {
let err: GraphQLError =
serde_json::from_value(serde_json::json!({"message": "Field not found"})).unwrap();
assert_eq!(err.to_string(), "Field not found");
}
#[test]
fn test_graphql_error_code() {
let err: GraphQLError = serde_json::from_value(serde_json::json!({
"message": "Unknown field \"foo\" on type \"Query\".",
"extensions": {"code": "GRAPHQL_VALIDATION_FAILED"}
}))
.unwrap();
assert_eq!(err.code(), Some("GRAPHQL_VALIDATION_FAILED"));
}
#[test]
fn test_graphql_error_no_code() {
let err: GraphQLError =
serde_json::from_value(serde_json::json!({"message": "Error"})).unwrap();
assert_eq!(err.code(), None);
}
#[test]
fn test_path_fragment_deserialization() {
let json = r#"["object", 0, "field"]"#;
let path: Vec<PathFragment> = serde_json::from_str(json).unwrap();
assert!(matches!(&path[0], PathFragment::Key(k) if k == "object"));
assert!(matches!(&path[1], PathFragment::Index(0)));
assert!(matches!(&path[2], PathFragment::Key(k) if k == "field"));
}
}