diff --git a/crates/iota-sdk-graphql-client/Cargo.toml b/crates/iota-sdk-graphql-client/Cargo.toml index b79b2520d..ff058b223 100644 --- a/crates/iota-sdk-graphql-client/Cargo.toml +++ b/crates/iota-sdk-graphql-client/Cargo.toml @@ -14,12 +14,12 @@ bcs.workspace = true chrono = "0.4.26" cynic.workspace = true derive_more = { workspace = true, features = ["from"] } -eyre.workspace = true futures.workspace = true reqwest = { workspace = true, default-features = false, features = ["rustls-tls", "json"] } serde.workspace = true serde_json.workspace = true strum = { workspace = true, features = ["derive"] } +thiserror.workspace = true tokio = { workspace = true, features = ["time"] } tracing = "0.1.37" url = "2.5.3" @@ -30,6 +30,7 @@ iota-types = { workspace = true, features = ["serde", "hash"] } getrandom = { version = "0.2", features = ["js"] } [dev-dependencies] +eyre.workspace = true rand.workspace = true iota-types = { workspace = true, features = ["serde", "rand", "hash"] } diff --git a/crates/iota-sdk-graphql-client/src/faucet.rs b/crates/iota-sdk-graphql-client/src/faucet.rs index dc7f6c54c..bc475510e 100644 --- a/crates/iota-sdk-graphql-client/src/faucet.rs +++ b/crates/iota-sdk-graphql-client/src/faucet.rs @@ -4,7 +4,6 @@ use std::time::Duration; -use eyre::{bail, eyre}; use iota_types::{Address, Digest, ObjectId}; use reqwest::{StatusCode, Url}; use serde::{Deserialize, Serialize}; @@ -18,6 +17,26 @@ pub const FAUCET_LOCAL_HOST: &str = "http://localhost:9123"; const FAUCET_REQUEST_TIMEOUT: Duration = Duration::from_secs(120); const FAUCET_POLL_INTERVAL: Duration = Duration::from_secs(2); +#[derive(thiserror::Error, Debug)] +pub enum FaucetError { + #[error("Cannot fetch request status due to a bad gateway.")] + BadGateway, + #[error("Faucet request was unsuccessful: {0}")] + Request(String), + #[error("Reqwest error: {0}")] + Reqwest(#[from] reqwest::Error), + #[error("Faucet request was unsuccessful: {0}")] + StatusCode(StatusCode), + #[error("Faucet request timed out")] + TimedOut, + #[error( + "Faucet service received too many requests from this IP address. Please try again after 60 minutes." + )] + TooManyRequests, + #[error("Faucet service is currently overloaded or unavailable. Please try again later.")] + Unavailable, +} + pub struct FaucetClient { faucet_url: Url, inner: reqwest::Client, @@ -95,13 +114,13 @@ impl FaucetClient { /// Request gas from the faucet. Note that this will return the UUID of the /// request and not wait until the token is received. Use /// `request_and_wait` to wait for the token. - pub async fn request(&self, address: Address) -> eyre::Result> { + pub async fn request(&self, address: Address) -> Result, FaucetError> { self.request_impl(address).await } /// Internal implementation of a faucet request. It returns the task Uuid as /// a String. - async fn request_impl(&self, address: Address) -> eyre::Result> { + async fn request_impl(&self, address: Address) -> Result, FaucetError> { let address = address.to_string(); let json_body = json![{ "FixedAmountRequest": { @@ -126,7 +145,7 @@ impl FaucetClient { if let Some(err) = faucet_resp.error { error!("Faucet request was unsuccessful: {err}"); - bail!("Faucet request was unsuccessful: {err}") + Err(FaucetError::Request(err)) } else { info!("Request successful: {:?}", faucet_resp.task); Ok(faucet_resp.task) @@ -134,19 +153,15 @@ impl FaucetClient { } StatusCode::TOO_MANY_REQUESTS => { error!("Faucet service received too many requests from this IP address."); - bail!( - "Faucet service received too many requests from this IP address. Please try again after 60 minutes." - ); + Err(FaucetError::TooManyRequests) } StatusCode::SERVICE_UNAVAILABLE => { error!("Faucet service is currently overloaded or unavailable."); - bail!( - "Faucet service is currently overloaded or unavailable. Please try again later." - ); + Err(FaucetError::Unavailable) } status_code => { error!("Faucet request was unsuccessful: {status_code}"); - bail!("Faucet request was unsuccessful: {status_code}"); + Err(FaucetError::StatusCode(status_code)) } } } @@ -158,21 +173,25 @@ impl FaucetClient { /// /// Note that the faucet is heavily rate-limited, so calling repeatedly the /// faucet would likely result in a 429 code or 502 code. - pub async fn request_and_wait(&self, address: Address) -> eyre::Result> { + pub async fn request_and_wait( + &self, + address: Address, + ) -> Result, FaucetError> { let request_id = self.request(address).await?; + if let Some(request_id) = request_id { - let poll_response = tokio::time::timeout(FAUCET_REQUEST_TIMEOUT, async { + let status_response = tokio::time::timeout(FAUCET_REQUEST_TIMEOUT, async { let mut interval = tokio::time::interval(FAUCET_POLL_INTERVAL); loop { interval.tick().await; info!("Polling faucet request status: {request_id}"); - let req = self.request_status(request_id.clone()).await; + let status_response = self.request_status(request_id.clone()).await?; - if let Ok(Some(poll_response)) = req { - match poll_response.status { + if let Some(status_response) = status_response { + match status_response.status { BatchSendStatusType::Succeeded => { info!("Faucet request {request_id} succeeded"); - break Ok(poll_response); + break Ok::<_, FaucetError>(status_response); } BatchSendStatusType::Discarded => { break Ok(BatchSendStatus { @@ -184,12 +203,6 @@ impl FaucetClient { continue; } } - } else if let Some(err) = req.err() { - error!("Faucet request {request_id} failed. Error: {:?}", err); - break Err(eyre!( - "Faucet request {request_id} failed. Error: {:?}", - err - )); } } }) @@ -199,9 +212,9 @@ impl FaucetClient { "Faucet request {request_id} timed out. Timeout set to {} seconds", FAUCET_REQUEST_TIMEOUT.as_secs() ); - eyre!("Faucet request timed out") + FaucetError::TimedOut })??; - Ok(poll_response.transferred_gas_objects) + Ok(status_response.transferred_gas_objects) } else { Ok(None) } @@ -210,22 +223,19 @@ impl FaucetClient { /// Check the faucet request status. /// /// Possible statuses are defined in: [`BatchSendStatusType`] - pub async fn request_status(&self, id: String) -> eyre::Result> { + pub async fn request_status(&self, id: String) -> Result, FaucetError> { let status_url = format!("{}v1/status/{}", self.faucet_url, id); info!("Checking status of faucet request: {status_url}"); let response = self.inner.get(&status_url).send().await?; + if response.status() == StatusCode::TOO_MANY_REQUESTS { - bail!("Cannot fetch request status due to too many requests from this IP address."); + return Err(FaucetError::TooManyRequests); } else if response.status() == StatusCode::BAD_GATEWAY { - bail!("Cannot fetch request status due to a bad gateway.") + return Err(FaucetError::BadGateway); } - let json = response - .json::() - .await - .map_err(|e| { - error!("Failed to parse faucet response: {:?}", e); - eyre!("Failed to parse faucet response: {:?}", e) - })?; + + let json = response.json::().await?; + Ok(json.status) } } diff --git a/crates/iota-sdk-types/Cargo.toml b/crates/iota-sdk-types/Cargo.toml index d54dba6c8..b7a07ab8a 100644 --- a/crates/iota-sdk-types/Cargo.toml +++ b/crates/iota-sdk-types/Cargo.toml @@ -42,7 +42,6 @@ proptest = ["dep:proptest", "dep:test-strategy", "serde"] base64ct = { workspace = true, features = ["alloc"] } bnum.workspace = true bs58 = "0.5.1" -eyre.workspace = true hex.workspace = true paste.workspace = true roaring.workspace = true @@ -58,7 +57,7 @@ serde_json = { workspace = true, optional = true } serde_repr = { version = "0.1", optional = true } serde_with = { version = "3.9", default-features = false, features = ["alloc"], optional = true } -# JsonSchema definitions for types, useful for generating an OpenAPI Specificaiton. +# JsonSchema definitions for types, useful for generating an OpenAPI Specification. schemars = { version = "0.8.21", optional = true } # RNG support @@ -73,6 +72,7 @@ test-strategy = { workspace = true, optional = true } [dev-dependencies] bcs.workspace = true +eyre.workspace = true jsonschema = { version = "0.20", default-features = false } num-bigint = "0.4.6" paste.workspace = true diff --git a/crates/iota-sdk-types/src/crypto/intent.rs b/crates/iota-sdk-types/src/crypto/intent.rs index 70380c3af..345c19e06 100644 --- a/crates/iota-sdk-types/src/crypto/intent.rs +++ b/crates/iota-sdk-types/src/crypto/intent.rs @@ -5,11 +5,16 @@ #[cfg(feature = "serde")] use std::str::FromStr; -#[cfg(feature = "serde")] -use eyre::eyre; - pub const INTENT_PREFIX_LENGTH: usize = 3; +pub enum IntentError { + Bytes, + Intent, + Scope, + Version, + AppId, +} + /// A Signing Intent /// /// An intent is a compact struct that serves as the domain separator for a @@ -95,9 +100,9 @@ impl Intent { } #[cfg(feature = "serde")] - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() != INTENT_PREFIX_LENGTH { - return Err(eyre!("Invalid Intent")); + return Err(IntentError::Bytes); } Ok(Self { scope: bytes[0].try_into()?, @@ -109,11 +114,11 @@ impl Intent { #[cfg(feature = "serde")] impl FromStr for Intent { - type Err = eyre::Report; + type Err = IntentError; fn from_str(s: &str) -> Result { let bytes: Vec = - hex::decode(s.strip_prefix("0x").unwrap_or(s)).map_err(|_| eyre!("Invalid Intent"))?; + hex::decode(s.strip_prefix("0x").unwrap_or(s)).map_err(|_| IntentError::Intent)?; Self::from_bytes(bytes.as_slice()) } } @@ -170,10 +175,10 @@ impl IntentScope { #[cfg(feature = "serde")] impl TryFrom for IntentScope { - type Error = eyre::Report; + type Error = IntentError; fn try_from(value: u8) -> Result { - bcs::from_bytes(&[value]).map_err(|_| eyre!("Invalid IntentScope")) + bcs::from_bytes(&[value]).map_err(|_| IntentError::Scope) } } @@ -207,10 +212,10 @@ impl IntentVersion { #[cfg(feature = "serde")] impl TryFrom for IntentVersion { - type Error = eyre::Report; + type Error = IntentError; fn try_from(value: u8) -> Result { - bcs::from_bytes(&[value]).map_err(|_| eyre!("Invalid IntentVersion")) + bcs::from_bytes(&[value]).map_err(|_| IntentError::Version) } } @@ -247,10 +252,10 @@ impl IntentAppId { #[cfg(feature = "serde")] impl TryFrom for IntentAppId { - type Error = eyre::Report; + type Error = IntentError; fn try_from(value: u8) -> Result { - bcs::from_bytes(&[value]).map_err(|_| eyre!("Invalid IntentAppId")) + bcs::from_bytes(&[value]).map_err(|_| IntentError::AppId) } } diff --git a/crates/iota-sdk-types/src/iota_names/config.rs b/crates/iota-sdk-types/src/iota_names/config.rs index eb596105a..59c516bca 100644 --- a/crates/iota-sdk-types/src/iota_names/config.rs +++ b/crates/iota-sdk-types/src/iota_names/config.rs @@ -3,7 +3,7 @@ use std::str::FromStr; -use crate::{Address, ObjectId}; +use crate::{ObjectId, address::Address, iota_names::error::IotaNamesError}; #[derive(Clone, Debug, Eq, PartialEq)] #[cfg_attr( @@ -48,7 +48,7 @@ impl IotaNamesConfig { } } - pub fn from_env() -> eyre::Result { + pub fn from_env() -> Result { Ok(Self::new( std::env::var("IOTA_NAMES_PACKAGE_ADDRESS")?.parse()?, std::env::var("IOTA_NAMES_OBJECT_ID")?.parse()?, diff --git a/crates/iota-sdk-types/src/iota_names/error.rs b/crates/iota-sdk-types/src/iota_names/error.rs index 1a1771aa3..d069fd652 100644 --- a/crates/iota-sdk-types/src/iota_names/error.rs +++ b/crates/iota-sdk-types/src/iota_names/error.rs @@ -1,10 +1,10 @@ // Copyright (c) 2025 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::ObjectId; +use crate::{ObjectId, address::AddressParseError}; #[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +// #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum IotaNamesError { #[error("Name length {0} exceeds maximum length {1}")] NameLengthExceeded(usize, usize), @@ -26,4 +26,8 @@ pub enum IotaNamesError { MalformedObject(ObjectId), #[error("Invalid TLN {0}")] InvalidTln(String), + #[error("Missing environment variable {0}")] + MissingEnvVar(#[from] std::env::VarError), + #[error("Address parser error {0}")] + AddressParser(#[from] AddressParseError), } diff --git a/crates/iota-sdk-types/src/object.rs b/crates/iota-sdk-types/src/object.rs index e17627d6e..fe5c51714 100644 --- a/crates/iota-sdk-types/src/object.rs +++ b/crates/iota-sdk-types/src/object.rs @@ -449,12 +449,11 @@ impl Object { } #[cfg(feature = "serde")] - pub fn to_rust(&self) -> eyre::Result { - use eyre::OptionExt; - - Ok(bcs::from_bytes::( - &self.as_struct_opt().ok_or_eyre("not a struct")?.contents, - )?) + pub fn to_rust( + &self, + ) -> Result> { + let contents = &self.as_struct_opt().ok_or("not a struct")?.contents; + Ok(bcs::from_bytes::(contents)?) } } diff --git a/crates/iota-sdk-types/src/transaction/fixtures/update-transaction-fixtures/Cargo.toml b/crates/iota-sdk-types/src/transaction/fixtures/update-transaction-fixtures/Cargo.toml index caed22cb7..4e161b826 100644 --- a/crates/iota-sdk-types/src/transaction/fixtures/update-transaction-fixtures/Cargo.toml +++ b/crates/iota-sdk-types/src/transaction/fixtures/update-transaction-fixtures/Cargo.toml @@ -7,7 +7,6 @@ edition = "2024" [dependencies] bcs.workspace = true -eyre.workspace = true fastcrypto = { git = "https://github.com/MystenLabs/fastcrypto", rev = "5f2c63266a065996d53f98156f0412782b468597" } futures.workspace = true tokio.workspace = true @@ -15,3 +14,6 @@ tokio.workspace = true iota-json-rpc-types = { path = "../../../../../../../iota/crates/iota-json-rpc-types" } iota-types = { path = "../../../../../../../iota/crates/iota-types" } test-cluster = { path = "../../../../../../../iota/crates/test-cluster" } + +[dev-dependencies] +eyre.workspace = true