diff --git a/crates/sui-graphql-client/Cargo.toml b/crates/sui-graphql-client/Cargo.toml index 0f48749e3..1104c5254 100644 --- a/crates/sui-graphql-client/Cargo.toml +++ b/crates/sui-graphql-client/Cargo.toml @@ -12,7 +12,7 @@ description = "Sui GraphQL RPC Client for the Sui Blockchain" anyhow = "1.0.71" async-stream = "0.3.3" async-trait = "0.1.61" -base64ct = { version = "1.6.0", features = ["alloc"] } +base64ct = { version = "1.6.0", features = ["alloc", "std"] } bcs = "0.1.4" chrono = "0.4.26" cynic = "3.7.3" @@ -23,6 +23,7 @@ serde_json = {version = "1.0.95"} sui-types = { package = "sui-sdk-types", path = "../sui-sdk-types", features = ["serde"] } tracing = "0.1.37" tokio = "1.36.0" +url = "2.5.3" [dev-dependencies] sui-types = { package = "sui-sdk-types", path = "../sui-sdk-types", features = ["serde", "rand", "hash"] } diff --git a/crates/sui-graphql-client/src/error.rs b/crates/sui-graphql-client/src/error.rs new file mode 100644 index 000000000..c79873b2a --- /dev/null +++ b/crates/sui-graphql-client/src/error.rs @@ -0,0 +1,179 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::num::ParseIntError; +use std::num::TryFromIntError; + +use cynic::GraphQlError; + +use sui_types::types::AddressParseError; +use sui_types::types::DigestParseError; +use sui_types::types::TypeParseError; + +type BoxError = Box; + +pub type Result = std::result::Result; + +/// General error type for the client. It is used to wrap all the possible errors that can occur. +#[derive(Debug)] +pub struct Error { + inner: Box, +} + +/// Error type for the client. It is split into multiple fields to allow for more granular error +/// handling. The `source` field is used to store the original error. +#[derive(Debug)] +struct InnerError { + /// Error kind. + kind: Kind, + /// Errors returned by the GraphQL server. + query_errors: Option>, + /// The original error. + source: Option, +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum Kind { + Deserialization, + Parse, + Query, + Other, +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.inner.source.as_deref().map(|e| e as _) + } +} + +impl Error { + // Public accessors + + /// Returns the kind of error. + pub fn kind(&self) -> &Kind { + &self.inner.kind + } + + /// Original GraphQL query errors. + pub fn graphql_errors(&self) -> Option<&[GraphQlError]> { + self.inner.query_errors.as_deref() + } + + // Private constructors + + /// Convert the given error into a generic error. + pub(crate) fn from_error>(kind: Kind, error: E) -> Self { + Self { + inner: Box::new(InnerError { + kind, + source: Some(error.into()), + query_errors: None, + }), + } + } + + /// Special constructor for queries that expect to return data but it's none. + pub(crate) fn empty_response_error() -> Self { + Self { + inner: Box::new(InnerError { + kind: Kind::Query, + source: Some("Expected a non-empty response data from query".into()), + query_errors: None, + }), + } + } + + /// Create a Query kind of error with the original graphql errors. + pub(crate) fn graphql_error(errors: Vec) -> Self { + Self { + inner: Box::new(InnerError { + kind: Kind::Query, + source: None, + query_errors: Some(errors), + }), + } + } +} + +impl std::fmt::Display for Kind { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Kind::Deserialization => write!(f, "Deserialization error:"), + Kind::Parse => write!(f, "Parse error:"), + Kind::Query => write!(f, "Query error:"), + Kind::Other => write!(f, "Error:"), + } + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.inner.kind)?; + + if let Some(source) = &self.inner.source { + writeln!(f, " {}", source)?; + } + Ok(()) + } +} + +impl From for Error { + fn from(error: bcs::Error) -> Self { + Self::from_error(Kind::Deserialization, error) + } +} + +impl From for Error { + fn from(error: reqwest::Error) -> Self { + Self::from_error(Kind::Other, error) + } +} + +impl From for Error { + fn from(error: url::ParseError) -> Self { + Self::from_error(Kind::Parse, error) + } +} + +impl From for Error { + fn from(error: ParseIntError) -> Self { + Self::from_error(Kind::Parse, error) + } +} + +impl From for Error { + fn from(error: AddressParseError) -> Self { + Self::from_error(Kind::Parse, error) + } +} + +impl From for Error { + fn from(error: base64ct::Error) -> Self { + Self::from_error(Kind::Parse, error) + } +} + +impl From for Error { + fn from(error: chrono::ParseError) -> Self { + Self::from_error(Kind::Parse, error) + } +} + +impl From for Error { + fn from(error: DigestParseError) -> Self { + Self::from_error(Kind::Parse, error) + } +} + +impl From for Error { + fn from(error: TryFromIntError) -> Self { + Self::from_error(Kind::Parse, error) + } +} + +impl From for Error { + fn from(error: TypeParseError) -> Self { + Self::from_error(Kind::Parse, error) + } +} diff --git a/crates/sui-graphql-client/src/lib.rs b/crates/sui-graphql-client/src/lib.rs index bc2dc64ed..a3196af6d 100644 --- a/crates/sui-graphql-client/src/lib.rs +++ b/crates/sui-graphql-client/src/lib.rs @@ -3,10 +3,12 @@ #![doc = include_str!("../README.md")] +pub mod error; pub mod faucet; pub mod query_types; pub mod streams; +use error::Error; use query_types::ActiveValidatorsArgs; use query_types::ActiveValidatorsQuery; use query_types::BalanceArgs; @@ -93,10 +95,6 @@ use sui_types::types::TransactionKind; use sui_types::types::TypeTag; use sui_types::types::UserSignature; -use anyhow::anyhow; -use anyhow::ensure; -use anyhow::Error; -use anyhow::Result; use base64ct::Encoding; use cynic::serde; use cynic::GraphQlResponse; @@ -109,10 +107,11 @@ use serde::de::DeserializeOwned; use serde::Serialize; use std::str::FromStr; +use crate::error::Kind; +use crate::error::Result; use crate::query_types::CheckpointTotalTxQuery; const DEFAULT_ITEMS_PER_PAGE: i32 = 10; - const MAINNET_HOST: &str = "https://sui-mainnet.mystenlabs.com/graphql"; const TESTNET_HOST: &str = "https://sui-testnet.mystenlabs.com/graphql"; const DEVNET_HOST: &str = "https://sui-devnet.mystenlabs.com/graphql"; @@ -235,10 +234,7 @@ impl From for NameValue { impl DynamicFieldOutput { /// Deserialize the name of the dynamic field into the specified type. - pub fn deserialize_name( - &self, - expected_type: &TypeTag, - ) -> Result { + pub fn deserialize_name(&self, expected_type: &TypeTag) -> Result { assert_eq!( expected_type, &self.name.type_, "Expected type {}, but got {}", @@ -246,14 +242,11 @@ impl DynamicFieldOutput { ); let bcs = &self.name.bcs; - bcs::from_bytes::(bcs).map_err(|_| anyhow!("Cannot decode BCS bytes")) + bcs::from_bytes::(bcs).map_err(Into::into) } /// Deserialize the value of the dynamic field into the specified type. - pub fn deserialize_value( - &self, - expected_type: &TypeTag, - ) -> Result { + pub fn deserialize_value(&self, expected_type: &TypeTag) -> Result { let typetag = self.value.as_ref().map(|(typename, _)| typename); assert_eq!( Some(&expected_type), @@ -264,9 +257,9 @@ impl DynamicFieldOutput { ); if let Some((_, bcs)) = &self.value { - bcs::from_bytes::(bcs).map_err(|_| anyhow!("Cannot decode BCS bytes")) + bcs::from_bytes::(bcs).map_err(Into::into) } else { - Err(anyhow!("No value found")) + Err(Error::from_error(Kind::Deserialization, "Value is missing")) } } } @@ -288,8 +281,8 @@ impl Client { // =========================================================================== /// Create a new GraphQL client with the provided server address. - pub fn new(server: &str) -> Result { - let rpc = reqwest::Url::parse(server).map_err(|_| anyhow!("Invalid URL: {}", server))?; + pub fn new(server: &str) -> Result { + let rpc = reqwest::Url::parse(server)?; let client = Client { rpc, @@ -322,7 +315,7 @@ impl Client { /// Set the server address for the GraphQL GraphQL client. It should be a valid URL with a host and /// optionally a port number. - pub fn set_rpc_server(&mut self, server: &str) -> Result<(), Error> { + pub fn set_rpc_server(&mut self, server: &str) -> Result<()> { let rpc = reqwest::Url::parse(server)?; self.rpc = rpc; Ok(()) @@ -379,18 +372,18 @@ impl Client { // =========================================================================== /// Get the chain identifier. - pub async fn chain_id(&self) -> Result { + pub async fn chain_id(&self) -> Result { let operation = ChainIdentifierQuery::build(()); let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } response .data .map(|e| e.chain_identifier) - .ok_or_else(|| Error::msg("No data in response")) + .ok_or_else(Error::empty_response_error) } /// Get the reference gas price for the provided epoch or the last known one if no epoch is @@ -398,26 +391,24 @@ impl Client { /// /// This will return `Ok(None)` if the epoch requested is not available in the GraphQL service /// (e.g., due to pruning). - pub async fn reference_gas_price(&self, epoch: Option) -> Result, Error> { + pub async fn reference_gas_price(&self, epoch: Option) -> Result> { let operation = EpochSummaryQuery::build(EpochSummaryArgs { id: epoch }); let response = self.run_query(&operation).await?; - if let Some(data) = response.data { - data.epoch - .and_then(|e| e.reference_gas_price.map(|x| x.try_into())) - .transpose() - } else if let Some(errors) = response.errors { - Err(Error::msg(format!("{:?}", errors))) - } else { - Err(Error::msg("No data in response")) + if let Some(errors) = response.errors { + return Err(Error::graphql_error(errors)); } + + response + .data + .and_then(|e| e.epoch) + .and_then(|e| e.reference_gas_price) + .map(|x| x.try_into()) + .transpose() } /// Get the protocol configuration. - pub async fn protocol_config( - &self, - version: Option, - ) -> Result, Error> { + pub async fn protocol_config(&self, version: Option) -> Result> { let operation = ProtocolConfigQuery::build(ProtocolVersionArgs { id: version }); let response = self.run_query(&operation).await?; Ok(response.data.map(|p| p.protocol_config)) @@ -425,7 +416,7 @@ impl Client { /// Get the GraphQL service configuration, including complexity limits, read and mutation limits, /// supported versions, and others. - pub async fn service_config(&self) -> Result<&ServiceConfig, Error> { + pub async fn service_config(&self) -> Result<&ServiceConfig> { // If the value is already initialized, return it if let Some(service_config) = self.service_config.get() { return Ok(service_config); @@ -436,10 +427,14 @@ impl Client { let operation = ServiceConfigQuery::build(()); let response = self.run_query(&operation).await?; + if let Some(errors) = response.errors { + return Err(Error::graphql_error(errors)); + } + response .data .map(|s| s.service_config) - .ok_or_else(|| Error::msg("No data in response"))? + .ok_or_else(Error::empty_response_error)? }; let service_config = self.service_config.get_or_init(move || service_config); @@ -453,7 +448,7 @@ impl Client { &self, epoch: Option, pagination_filter: PaginationFilter, - ) -> Result, Error> { + ) -> Result> { let (after, before, first, last) = self.pagination_filter(pagination_filter).await; let operation = ActiveValidatorsQuery::build(ActiveValidatorsArgs { @@ -466,7 +461,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } if let Some(validators) = response @@ -488,27 +483,21 @@ impl Client { /// The total number of transaction blocks in the network by the end of the provided /// checkpoint digest. - pub async fn total_transaction_blocks_by_digest( - &self, - digest: Digest, - ) -> Result, Error> { + pub async fn total_transaction_blocks_by_digest(&self, digest: Digest) -> Result> { self.internal_total_transaction_blocks(Some(digest.to_string()), None) .await } /// The total number of transaction blocks in the network by the end of the provided checkpoint /// sequence number. - pub async fn total_transaction_blocks_by_seq_num( - &self, - seq_num: u64, - ) -> Result, Error> { + pub async fn total_transaction_blocks_by_seq_num(&self, seq_num: u64) -> Result> { self.internal_total_transaction_blocks(None, Some(seq_num)) .await } /// The total number of transaction blocks in the network by the end of the last known /// checkpoint. - pub async fn total_transaction_blocks(&self) -> Result, Error> { + pub async fn total_transaction_blocks(&self) -> Result> { self.internal_total_transaction_blocks(None, None).await } @@ -518,11 +507,13 @@ impl Client { &self, digest: Option, seq_num: Option, - ) -> Result, Error> { - ensure!( - !(digest.is_some() && seq_num.is_some()), - "Cannot provide both digest and seq_num." - ); + ) -> Result> { + if digest.is_some() && seq_num.is_some() { + return Err(Error::from_error( + Kind::Other, + "Conflicting arguments: either digest or seq_num can be provided, but not both.", + )); + } let operation = CheckpointTotalTxQuery::build(CheckpointArgs { id: CheckpointId { @@ -533,7 +524,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } Ok(response @@ -548,11 +539,7 @@ impl Client { /// Get the balance of all the coins owned by address for the provided coin type. /// Coin type will default to `0x2::coin::Coin<0x2::sui::SUI>` if not provided. - pub async fn balance( - &self, - address: Address, - coin_type: Option<&str>, - ) -> Result, Error> { + pub async fn balance(&self, address: Address, coin_type: Option<&str>) -> Result> { let operation = BalanceQuery::build(BalanceArgs { address, coin_type: coin_type.map(|x| x.to_string()), @@ -560,17 +547,16 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } let total_balance = response .data .map(|b| b.owner.and_then(|o| o.balance.map(|b| b.total_balance))) - .ok_or_else(|| Error::msg("No data in response"))? + .ok_or_else(Error::empty_response_error)? .flatten() .map(|x| x.0.parse::()) - .transpose() - .map_err(|e| Error::msg(format!("Cannot parse balance into u128: {e}")))?; + .transpose()?; Ok(total_balance) } @@ -587,7 +573,7 @@ impl Client { owner: Address, coin_type: Option<&str>, pagination_filter: PaginationFilter, - ) -> Result, Error> { + ) -> Result> { let response = self .objects( Some(ObjectFilter { @@ -620,7 +606,7 @@ impl Client { address: Address, coin_type: Option<&'static str>, streaming_direction: Direction, - ) -> impl Stream> { + ) -> impl Stream> { stream_paginated_query( move |filter| self.coins(address, coin_type, filter), streaming_direction, @@ -628,19 +614,19 @@ impl Client { } /// Get the coin metadata for the coin type. - pub async fn coin_metadata(&self, coin_type: &str) -> Result, Error> { + pub async fn coin_metadata(&self, coin_type: &str) -> Result> { let operation = CoinMetadataQuery::build(CoinMetadataArgs { coin_type }); let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } Ok(response.data.and_then(|x| x.coin_metadata)) } /// Get total supply for the coin type. - pub async fn total_supply(&self, coin_type: &str) -> Result, Error> { + pub async fn total_supply(&self, coin_type: &str) -> Result> { let coin_metadata = self.coin_metadata(coin_type).await?; coin_metadata @@ -659,11 +645,13 @@ impl Client { &self, digest: Option, seq_num: Option, - ) -> Result, Error> { - ensure!( - !(digest.is_some() && seq_num.is_some()), - "Either digest or seq_num must be provided" - ); + ) -> Result> { + if digest.is_some() && seq_num.is_some() { + return Err(Error::from_error( + Kind::Other, + "either digest or seq_num must be provided", + )); + } let operation = CheckpointQuery::build(CheckpointArgs { id: CheckpointId { @@ -674,20 +662,20 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } response .data .map(|c| c.checkpoint.map(|c| c.try_into()).transpose()) - .ok_or_else(|| Error::msg("No data in response"))? + .ok_or(Error::empty_response_error())? } /// Get a page of [`CheckpointSummary`] for the provided parameters. pub async fn checkpoints<'a>( &self, pagination_filter: PaginationFilter, - ) -> Result, Error> { + ) -> Result> { let (after, before, first, last) = self.pagination_filter(pagination_filter).await; let operation = CheckpointsQuery::build(CheckpointsArgs { @@ -699,7 +687,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } if let Some(checkpoints) = response.data { @@ -722,14 +710,14 @@ impl Client { pub async fn checkpoints_stream( &self, streaming_direction: Direction, - ) -> impl Stream> + '_ { + ) -> impl Stream> + '_ { stream_paginated_query(move |filter| self.checkpoints(filter), streaming_direction) } /// Return the sequence number of the latest checkpoint that has been executed. pub async fn latest_checkpoint_sequence_number( &self, - ) -> Result, Error> { + ) -> Result> { Ok(self .checkpoint(None, None) .await? @@ -765,7 +753,7 @@ impl Client { address: Address, type_: TypeTag, name: impl Into, - ) -> Result, Error> { + ) -> Result> { let bcs = name.into().0; let operation = DynamicFieldQuery::build(DynamicFieldArgs { address, @@ -778,7 +766,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } let result = response @@ -786,8 +774,7 @@ impl Client { .and_then(|d| d.owner) .and_then(|o| o.dynamic_field) .map(|df| df.try_into()) - .transpose() - .map_err(|e| Error::msg(format!("{:?}", e)))?; + .transpose()?; Ok(result) } @@ -805,7 +792,7 @@ impl Client { address: Address, type_: TypeTag, name: impl Into, - ) -> Result, Error> { + ) -> Result> { let bcs = name.into().0; let operation = DynamicObjectFieldQuery::build(DynamicFieldArgs { address, @@ -818,7 +805,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } let result: Option = response @@ -826,8 +813,7 @@ impl Client { .and_then(|d| d.object) .and_then(|o| o.dynamic_object_field) .map(|df| df.try_into()) - .transpose() - .map_err(|e| Error::msg(format!("{:?}", e)))?; + .transpose()?; Ok(result) } @@ -839,7 +825,7 @@ impl Client { &self, address: Address, pagination_filter: PaginationFilter, - ) -> Result, Error> { + ) -> Result> { let (after, before, first, last) = self.pagination_filter(pagination_filter).await; let operation = DynamicFieldsOwnerQuery::build(DynamicFieldConnectionArgs { address, @@ -851,7 +837,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } let Some(DynamicFieldsOwnerQuery { owner: Some(dfs) }) = response.data else { @@ -864,8 +850,7 @@ impl Client { .nodes .into_iter() .map(TryInto::try_into) - .collect::, Error>>() - .map_err(|e| Error::msg(format!("{:?}", e)))?, + .collect::>>()?, )) } @@ -875,7 +860,7 @@ impl Client { &self, address: Address, streaming_direction: Direction, - ) -> impl Stream> + '_ { + ) -> impl Stream> + '_ { stream_paginated_query( move |filter| self.dynamic_fields(address, filter), streaming_direction, @@ -888,11 +873,11 @@ impl Client { /// Return the number of checkpoints in this epoch. This will return `Ok(None)` if the epoch /// requested is not available in the GraphQL service (e.g., due to pruning). - pub async fn epoch_total_checkpoints(&self, epoch: Option) -> Result, Error> { + pub async fn epoch_total_checkpoints(&self, epoch: Option) -> Result> { let response = self.epoch_summary(epoch).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } Ok(response @@ -903,14 +888,11 @@ impl Client { /// Return the number of transaction blocks in this epoch. This will return `Ok(None)` if the /// epoch requested is not available in the GraphQL service (e.g., due to pruning). - pub async fn epoch_total_transaction_blocks( - &self, - epoch: Option, - ) -> Result, Error> { + pub async fn epoch_total_transaction_blocks(&self, epoch: Option) -> Result> { let response = self.epoch_summary(epoch).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } Ok(response @@ -924,7 +906,7 @@ impl Client { async fn epoch_summary( &self, epoch: Option, - ) -> Result, Error> { + ) -> Result> { let operation = EpochSummaryQuery::build(EpochSummaryArgs { id: epoch }); self.run_query(&operation).await } @@ -938,7 +920,7 @@ impl Client { &self, filter: Option, pagination_filter: PaginationFilter, - ) -> Result, Error> { + ) -> Result> { let (after, before, first, last) = self.pagination_filter(pagination_filter).await; let operation = EventsQuery::build(EventsQueryArgs { @@ -952,7 +934,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } if let Some(events) = response.data { @@ -963,12 +945,10 @@ impl Client { .into_iter() .map(|e| e.bcs.0) .map(|b| base64ct::Base64::decode_vec(&b)) - .collect::, base64ct::Error>>() - .map_err(|e| Error::msg(format!("Cannot decode Base64 event bcs bytes: {e}")))? + .collect::, base64ct::Error>>()? .iter() .map(|b| bcs::from_bytes::(b)) - .collect::, bcs::Error>>() - .map_err(|e| Error::msg(format!("Cannot decode bcs bytes into Event: {e}")))?; + .collect::, bcs::Error>>()?; Ok(Page::new(page_info, nodes)) } else { @@ -981,7 +961,7 @@ impl Client { &self, filter: Option, streaming_direction: Direction, - ) -> impl Stream> + '_ { + ) -> impl Stream> + '_ { stream_paginated_query( move |pag_filter| self.events(filter.clone(), pag_filter), streaming_direction, @@ -996,17 +976,13 @@ impl Client { /// /// If the object does not exist (e.g., due to pruning), this will return `Ok(None)`. /// Similarly, if this is not an object but an address, it will return `Ok(None)`. - pub async fn object( - &self, - address: Address, - version: Option, - ) -> Result, Error> { + pub async fn object(&self, address: Address, version: Option) -> Result> { let operation = ObjectQuery::build(ObjectQueryArgs { address, version }); let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } if let Some(object) = response.data { @@ -1014,12 +990,11 @@ impl Client { let bcs = obj .and_then(|o| o.bcs) .map(|bcs| base64ct::Base64::decode_vec(bcs.0.as_str())) - .transpose() - .map_err(|e| Error::msg(format!("Cannot decode Base64 object bcs bytes: {e}",)))?; + .transpose()?; + let object = bcs .map(|b| bcs::from_bytes::(&b)) - .transpose() - .map_err(|e| Error::msg(format!("Cannot decode bcs bytes into Object: {e}",)))?; + .transpose()?; Ok(object) } else { @@ -1048,7 +1023,7 @@ impl Client { &self, filter: Option>, pagination_filter: PaginationFilter, - ) -> Result, Error> { + ) -> Result> { let (after, before, first, last) = self.pagination_filter(pagination_filter).await; let operation = ObjectsQuery::build(ObjectsQueryArgs { after: after.as_deref(), @@ -1060,7 +1035,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } if let Some(objects) = response.data { @@ -1074,13 +1049,11 @@ impl Client { b64.as_ref() .map(|b| base64ct::Base64::decode_vec(b.0.as_str())) }) - .collect::, base64ct::Error>>() - .map_err(|e| Error::msg(format!("Cannot decode Base64 object bcs bytes: {e}")))?; + .collect::, base64ct::Error>>()?; let objects = bcs .iter() .map(|b| bcs::from_bytes::(b)) - .collect::, bcs::Error>>() - .map_err(|e| Error::msg(format!("Cannot decode bcs bytes into Object: {e}")))?; + .collect::, bcs::Error>>()?; Ok(Page::new(page_info, objects)) } else { @@ -1093,7 +1066,7 @@ impl Client { &'a self, filter: Option>, streaming_direction: Direction, - ) -> impl Stream> + 'a { + ) -> impl Stream> + 'a { stream_paginated_query( move |pag_filter| self.objects(filter.clone(), pag_filter), streaming_direction, @@ -1101,7 +1074,7 @@ impl Client { } /// Return the object's bcs content [`Vec`] based on the provided [`Address`]. - pub async fn object_bcs(&self, object_id: Address) -> Result>, Error> { + pub async fn object_bcs(&self, object_id: Address) -> Result>> { let operation = ObjectQuery::build(ObjectQueryArgs { address: object_id, version: None, @@ -1110,15 +1083,14 @@ impl Client { let response = self.run_query(&operation).await.unwrap(); if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } if let Some(object) = response.data.map(|d| d.object) { - object + Ok(object .and_then(|o| o.bcs) .map(|bcs| base64ct::Base64::decode_vec(bcs.0.as_str())) - .transpose() - .map_err(|e| Error::msg(format!("Cannot decode Base64 object bcs bytes: {e}"))) + .transpose()?) } else { Ok(None) } @@ -1132,13 +1104,13 @@ impl Client { &self, address: Address, version: Option, - ) -> Result, Error> { + ) -> Result> { let operation = ObjectQuery::build(ObjectQueryArgs { address, version }); let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } if let Some(object) = response.data { @@ -1159,23 +1131,22 @@ impl Client { &self, address: Address, version: Option, - ) -> Result>, Error> { + ) -> Result>> { let operation = ObjectQuery::build(ObjectQueryArgs { address, version }); let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } if let Some(object) = response.data { - object + Ok(object .object .and_then(|o| o.as_move_object) .and_then(|o| o.contents) .map(|bcs| base64ct::Base64::decode_vec(bcs.bcs.0.as_str())) - .transpose() - .map_err(|e| Error::msg(format!("Cannot decode Base64 object bcs bytes: {e}"))) + .transpose()?) } else { Ok(None) } @@ -1198,25 +1169,23 @@ impl Client { &self, address: Address, version: Option, - ) -> Result, Error> { + ) -> Result> { let operation = PackageQuery::build(PackageArgs { address, version }); let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } - response + Ok(response .data .and_then(|x| x.package) .and_then(|x| x.package_bcs) .map(|bcs| base64ct::Base64::decode_vec(bcs.0.as_str())) - .transpose() - .map_err(|e| Error::msg(format!("Cannot decode Base64 package bcs bytes: {e}")))? + .transpose()? .map(|bcs| bcs::from_bytes::(&bcs)) - .transpose() - .map_err(|e| Error::msg(format!("Cannot decode bcs bytes into MovePackage: {e}"))) + .transpose()?) } /// Fetch all versions of package at address (packages that share this package's original ID), @@ -1228,7 +1197,7 @@ impl Client { pagination_filter: PaginationFilter, after_version: Option, before_version: Option, - ) -> Result, Error> { + ) -> Result> { let (after, before, first, last) = self.pagination_filter(pagination_filter).await; let operation = PackageVersionsQuery::build(PackageVersionsArgs { address, @@ -1245,7 +1214,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } if let Some(packages) = response.data { @@ -1259,15 +1228,11 @@ impl Client { b64.as_ref() .map(|b| base64ct::Base64::decode_vec(b.0.as_str())) }) - .collect::, base64ct::Error>>() - .map_err(|e| Error::msg(format!("Cannot decode Base64 package bcs bytes: {e}")))?; + .collect::, base64ct::Error>>()?; let packages = bcs .iter() .map(|b| bcs::from_bytes::(b)) - .collect::, bcs::Error>>() - .map_err(|e| { - Error::msg(format!("Cannot decode bcs bytes into MovePackage: {e}")) - })?; + .collect::, bcs::Error>>()?; Ok(Page::new(page_info, packages)) } else { @@ -1278,7 +1243,7 @@ impl Client { /// Fetch the latest version of the package at address. /// This corresponds to the package with the highest version that shares its original ID with /// the package at address. - pub async fn package_latest(&self, address: Address) -> Result, Error> { + pub async fn package_latest(&self, address: Address) -> Result> { let operation = LatestPackageQuery::build(PackageArgs { address, version: None, @@ -1287,7 +1252,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } let pkg = response @@ -1295,24 +1260,22 @@ impl Client { .and_then(|x| x.latest_package) .and_then(|x| x.package_bcs) .map(|bcs| base64ct::Base64::decode_vec(bcs.0.as_str())) - .transpose() - .map_err(|e| Error::msg(format!("Cannot decode Base64 package bcs bytes: {e}")))? + .transpose()? .map(|bcs| bcs::from_bytes::(&bcs)) - .transpose() - .map_err(|e| Error::msg(format!("Cannot decode bcs bytes into MovePackage: {e}")))?; + .transpose()?; Ok(pkg) } /// Fetch a package by its name (using Move Registry Service) - pub async fn package_by_name(&self, name: &str) -> Result, Error> { + pub async fn package_by_name(&self, name: &str) -> Result> { let operation = PackageByNameQuery::build(PackageByNameArgs { name }); let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { println!("{:?}", errors); - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } Ok(response @@ -1336,9 +1299,12 @@ impl Client { last: Option, after_checkpoint: Option, before_checkpoint: Option, - ) -> Result, Error> { + ) -> Result> { if first.is_some() && last.is_some() { - return Err(Error::msg("Cannot specify both first and last")); + return Err(Error::from_error( + Kind::Other, + "Conflicting arguments: either first or last can be provided, but not both.", + )); } let operation = PackagesQuery::build(PackagesQueryArgs { @@ -1355,7 +1321,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } if let Some(packages) = response.data { @@ -1369,15 +1335,11 @@ impl Client { b64.as_ref() .map(|b| base64ct::Base64::decode_vec(b.0.as_str())) }) - .collect::, base64ct::Error>>() - .map_err(|e| Error::msg(format!("Cannot decode Base64 package bcs bytes: {e}")))?; + .collect::, base64ct::Error>>()?; let packages = bcs .iter() .map(|b| bcs::from_bytes::(b)) - .collect::, bcs::Error>>() - .map_err(|e| { - Error::msg(format!("Cannot decode bcs bytes into MovePackage: {e}")) - })?; + .collect::, bcs::Error>>()?; Ok(Page::new(page_info, packages)) } else { @@ -1398,10 +1360,8 @@ impl Client { &self, tx: &Transaction, skip_checks: Option, - ) -> Result { - let tx_bytes = base64ct::Base64::encode_string( - &bcs::to_bytes(&tx).map_err(|_| Error::msg("Cannot encode Transaction as BCS"))?, - ); + ) -> Result { + let tx_bytes = base64ct::Base64::encode_string(&bcs::to_bytes(&tx)?); self.dry_run(tx_bytes, skip_checks, None).await } @@ -1417,10 +1377,8 @@ impl Client { tx_kind: &TransactionKind, skip_checks: Option, tx_meta: TransactionMetadata, - ) -> Result { - let tx_bytes = base64ct::Base64::encode_string( - &bcs::to_bytes(&tx_kind).map_err(|_| Error::msg("Cannot encode Transaction as BCS"))?, - ); + ) -> Result { + let tx_bytes = base64ct::Base64::encode_string(&bcs::to_bytes(&tx_kind)?); self.dry_run(tx_bytes, skip_checks, Some(tx_meta)).await } @@ -1430,7 +1388,7 @@ impl Client { tx_bytes: String, skip_checks: Option, tx_meta: Option, - ) -> Result { + ) -> Result { let skip_checks = skip_checks.unwrap_or(false); let operation = DryRunQuery::build(DryRunArgs { tx_bytes, @@ -1441,7 +1399,7 @@ impl Client { // Query errors if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } // Dry Run errors @@ -1457,11 +1415,9 @@ impl Client { .and_then(|tx| tx.effects) .and_then(|bcs| bcs.bcs) .map(|bcs| base64ct::Base64::decode_vec(bcs.0.as_str())) - .transpose() - .map_err(|_| Error::msg("Cannot decode bcs bytes from Base64 for transaction effects"))? + .transpose()? .map(|bcs| bcs::from_bytes::(&bcs)) - .transpose() - .map_err(|_| Error::msg("Cannot decode bcs bytes into TransactionEffects"))?; + .transpose()?; Ok(DryRunResult { effects, error }) } @@ -1471,25 +1427,28 @@ impl Client { // =========================================================================== /// Get a transaction by its digest. - pub async fn transaction(&self, digest: Digest) -> Result, Error> { + pub async fn transaction(&self, digest: Digest) -> Result> { let operation = TransactionBlockQuery::build(TransactionBlockArgs { digest: digest.to_string(), }); let response = self.run_query(&operation).await?; + if let Some(errors) = response.errors { + return Err(Error::graphql_error(errors)); + } + response .data .and_then(|d| d.transaction_block) .map(|tx| tx.try_into()) .transpose() - .map_err(|e| Error::msg(format!("Cannot decode transaction: {e}"))) } /// Get a transaction's effects by its digest. pub async fn transaction_effects( &self, digest: TransactionDigest, - ) -> Result, Error> { + ) -> Result> { let operation = TransactionBlockEffectsQuery::build(TransactionBlockArgs { digest: digest.to_string(), }); @@ -1500,7 +1459,6 @@ impl Client { .and_then(|d| d.transaction_block) .map(|tx| tx.try_into()) .transpose() - .map_err(|e| Error::msg(format!("Cannot decode transaction: {e}"))) } /// Get a page of transactions based on the provided filters. @@ -1508,7 +1466,7 @@ impl Client { &self, filter: Option>, pagination_filter: PaginationFilter, - ) -> Result, Error> { + ) -> Result> { let (after, before, first, last) = self.pagination_filter(pagination_filter).await; let operation = TransactionBlocksQuery::build(TransactionBlocksQueryArgs { @@ -1521,6 +1479,10 @@ impl Client { let response = self.run_query(&operation).await?; + if let Some(errors) = response.errors { + return Err(Error::graphql_error(errors)); + } + if let Some(txb) = response.data { let txc = txb.transaction_blocks; let page_info = txc.page_info; @@ -1542,7 +1504,7 @@ impl Client { &self, filter: Option>, pagination_filter: PaginationFilter, - ) -> Result, Error> { + ) -> Result> { let (after, before, first, last) = self.pagination_filter(pagination_filter).await; let operation = TransactionBlocksEffectsQuery::build(TransactionBlocksQueryArgs { @@ -1576,7 +1538,7 @@ impl Client { &'a self, filter: Option>, streaming_direction: Direction, - ) -> impl Stream> + 'a { + ) -> impl Stream> + 'a { stream_paginated_query( move |pag_filter| self.transactions(filter.clone(), pag_filter), streaming_direction, @@ -1588,7 +1550,7 @@ impl Client { &'a self, filter: Option>, streaming_direction: Direction, - ) -> impl Stream> + 'a { + ) -> impl Stream> + 'a { stream_paginated_query( move |pag_filter| self.transactions_effects(filter.clone(), pag_filter), streaming_direction, @@ -1600,7 +1562,7 @@ impl Client { &self, signatures: Vec, tx: &Transaction, - ) -> Result, Error> { + ) -> Result> { let operation = ExecuteTransactionQuery::build(ExecuteTransactionArgs { signatures: signatures.iter().map(|s| s.to_base64()).collect(), tx_bytes: base64ct::Base64::encode_string(bcs::to_bytes(tx).unwrap().as_ref()), @@ -1609,17 +1571,13 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } if let Some(data) = response.data { let result = data.execute_transaction_block; - let bcs = - base64ct::Base64::decode_vec(result.effects.bcs.0.as_str()).map_err(|_| { - Error::msg("Cannot decode bcs bytes from Base64 for transaction effects") - })?; - let effects: TransactionEffects = bcs::from_bytes(&bcs) - .map_err(|_| Error::msg("Cannot decode bcs bytes into TransactionEffects"))?; + let bcs = base64ct::Base64::decode_vec(result.effects.bcs.0.as_str())?; + let effects: TransactionEffects = bcs::from_bytes(&bcs)?; Ok(Some(effects)) } else { @@ -1637,7 +1595,7 @@ impl Client { module: &str, function: &str, version: Option, - ) -> Result, Error> { + ) -> Result> { let operation = NormalizedMoveFunctionQuery::build(NormalizedMoveFunctionQueryArgs { address: Address::from_str(package)?, module, @@ -1647,7 +1605,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } Ok(response @@ -1670,7 +1628,7 @@ impl Client { pagination_filter_friends: PaginationFilter, pagination_filter_functions: PaginationFilter, pagination_filter_structs: PaginationFilter, - ) -> Result, Error> { + ) -> Result> { let (after_enums, before_enums, first_enums, last_enums) = self.pagination_filter(pagination_filter_enums).await; let (after_friends, before_friends, first_friends, last_friends) = @@ -1703,7 +1661,7 @@ impl Client { let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } Ok(response.data.and_then(|p| p.package).and_then(|p| p.module)) @@ -1714,13 +1672,13 @@ impl Client { // =========================================================================== /// Get the address for the provided Suins domain name. - pub async fn resolve_suins_to_address(&self, domain: &str) -> Result, Error> { + pub async fn resolve_suins_to_address(&self, domain: &str) -> Result> { let operation = ResolveSuinsQuery::build(ResolveSuinsQueryArgs { name: domain }); let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } Ok(response .data @@ -1729,13 +1687,13 @@ impl Client { } /// Get the default Suins domain name for the provided address. - pub async fn default_suins_name(&self, address: Address) -> Result, Error> { + pub async fn default_suins_name(&self, address: Address) -> Result> { let operation = DefaultSuinsNameQuery::build(DefaultSuinsNameQueryArgs { address }); let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { - return Err(Error::msg(format!("{:?}", errors))); + return Err(Error::graphql_error(errors)); } Ok(response .data diff --git a/crates/sui-graphql-client/src/query_types/checkpoint.rs b/crates/sui-graphql-client/src/query_types/checkpoint.rs index bdd01d436..bc4b77237 100644 --- a/crates/sui-graphql-client/src/query_types/checkpoint.rs +++ b/crates/sui-graphql-client/src/query_types/checkpoint.rs @@ -1,13 +1,15 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use anyhow::Error; use chrono::DateTime as ChronoDT; use sui_types::types::CheckpointContentsDigest; use sui_types::types::CheckpointDigest; use sui_types::types::CheckpointSummary; use sui_types::types::GasCostSummary as NativeGasCostSummary; +use crate::error; +use crate::error::Error; +use crate::error::Kind; use crate::query_types::schema; use crate::query_types::Base64; use crate::query_types::BigInt; @@ -103,19 +105,23 @@ pub struct GasCostSummary { // TODO need bcs in GraphQL Checkpoint to avoid this conversion impl TryInto for Checkpoint { - type Error = anyhow::Error; + type Error = error::Error; fn try_into(self) -> Result { let epoch = self .epoch - .ok_or_else(|| Error::msg("Epoch is missing"))? + .ok_or_else(|| { + Error::from_error(Kind::Other, "Epoch is checkpoint summary is missing") + })? .epoch_id; - let network_total_transactions = self - .network_total_transactions - .ok_or_else(|| Error::msg("Network total transactions is missing"))?; + let network_total_transactions = self.network_total_transactions.ok_or_else(|| { + Error::from_error( + Kind::Other, + "Network total transactions in checkpoint summary is missing", + ) + })?; let sequence_number = self.sequence_number; - let timestamp_ms = ChronoDT::parse_from_rfc3339(&self.timestamp.0) - .map_err(|e| Error::msg(format!("Cannot parse DateTime: {e}")))? + let timestamp_ms = ChronoDT::parse_from_rfc3339(&self.timestamp.0)? .timestamp_millis() .try_into()?; let content_digest = CheckpointContentsDigest::from_base58(&self.digest)?; @@ -125,7 +131,12 @@ impl TryInto for Checkpoint { .transpose()?; let epoch_rolling_gas_cost_summary = self .rolling_gas_summary - .ok_or_else(|| Error::msg("Rolling gas summary is missing"))? + .ok_or_else(|| { + Error::from_error( + Kind::Other, + "Gas cost summary in checkpoint summary is missing", + ) + })? .try_into()?; Ok(CheckpointSummary { epoch, @@ -143,23 +154,23 @@ impl TryInto for Checkpoint { } impl TryInto for GasCostSummary { - type Error = anyhow::Error; + type Error = error::Error; fn try_into(self) -> Result { let computation_cost = self .computation_cost - .ok_or_else(|| Error::msg("Computation cost is missing"))? + .ok_or_else(|| Error::from_error(Kind::Other, "Computation cost is missing"))? .try_into()?; let non_refundable_storage_fee = self .non_refundable_storage_fee - .ok_or_else(|| Error::msg("Non-refundable storage fee is missing"))? + .ok_or_else(|| Error::from_error(Kind::Other, "Non-refundable storage fee is missing"))? .try_into()?; let storage_cost = self .storage_cost - .ok_or_else(|| Error::msg("Storage cost is missing"))? + .ok_or_else(|| Error::from_error(Kind::Other, "Storage cost is missing"))? .try_into()?; let storage_rebate = self .storage_rebate - .ok_or_else(|| Error::msg("Storage rebate is missing"))? + .ok_or_else(|| Error::from_error(Kind::Other, "Storage rebate is missing"))? .try_into()?; Ok(NativeGasCostSummary { computation_cost, diff --git a/crates/sui-graphql-client/src/query_types/dynamic_fields.rs b/crates/sui-graphql-client/src/query_types/dynamic_fields.rs index 4773ba3b4..3d19c1aff 100644 --- a/crates/sui-graphql-client/src/query_types/dynamic_fields.rs +++ b/crates/sui-graphql-client/src/query_types/dynamic_fields.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use base64ct::Encoding; use sui_types::types::TypeTag; +use crate::error; use crate::query_types::schema; use crate::query_types::Address; use crate::query_types::Base64; @@ -156,7 +157,7 @@ impl DynamicField { } impl TryFrom for DynamicFieldOutput { - type Error = anyhow::Error; + type Error = error::Error; fn try_from(val: DynamicField) -> Result { let typetag = TypeTag::from_str( @@ -166,8 +167,7 @@ impl TryFrom for DynamicFieldOutput { .type_ .repr .as_str(), - ) - .map_err(|_| anyhow::anyhow!("Invalid TypeTag"))?; + )?; Ok(DynamicFieldOutput { name: crate::DynamicFieldName { type_: typetag, diff --git a/crates/sui-graphql-client/src/query_types/mod.rs b/crates/sui-graphql-client/src/query_types/mod.rs index 2bf88140f..d58d2fbca 100644 --- a/crates/sui-graphql-client/src/query_types/mod.rs +++ b/crates/sui-graphql-client/src/query_types/mod.rs @@ -109,10 +109,11 @@ pub use transaction::TransactionsFilter; use sui_types::types::Address; -use anyhow::anyhow; use cynic::impl_scalar; use serde_json::Value as JsonValue; +use crate::error; + #[cynic::schema("rpc")] pub mod schema {} @@ -190,12 +191,9 @@ pub struct PageInfo { } impl TryFrom for u64 { - type Error = anyhow::Error; + type Error = error::Error; fn try_from(value: BigInt) -> Result { - value - .0 - .parse::() - .map_err(|e| anyhow!("Cannot convert BigInt into u64: {e}")) + Ok(value.0.parse::()?) } } diff --git a/crates/sui-graphql-client/src/query_types/transaction.rs b/crates/sui-graphql-client/src/query_types/transaction.rs index e95b4c6e5..38be0490f 100644 --- a/crates/sui-graphql-client/src/query_types/transaction.rs +++ b/crates/sui-graphql-client/src/query_types/transaction.rs @@ -1,13 +1,15 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use anyhow::Error; use base64ct::Encoding; use sui_types::types::SignedTransaction; use sui_types::types::Transaction; use sui_types::types::TransactionEffects; use sui_types::types::UserSignature; +use crate::error; +use crate::error::Error; +use crate::error::Kind; use crate::query_types::schema; use crate::query_types::Address; use crate::query_types::Base64; @@ -142,23 +144,20 @@ pub struct TransactionBlockEffectsConnection { } impl TryFrom for SignedTransaction { - type Error = anyhow::Error; + type Error = error::Error; fn try_from(value: TransactionBlock) -> Result { let transaction = value .bcs .map(|tx| base64ct::Base64::decode_vec(tx.0.as_str())) - .transpose() - .map_err(|_| Error::msg("Cannot decode Base64 transaction bcs bytes"))? + .transpose()? .map(|bcs| bcs::from_bytes::(&bcs)) - .transpose() - .map_err(|_| Error::msg("Cannot decode bcs bytes into Transaction"))?; + .transpose()?; let signatures = if let Some(sigs) = value.signatures { sigs.iter() .map(|s| UserSignature::from_base64(&s.0)) - .collect::, _>>() - .map_err(|_| Error::msg("Cannot decode Base64 signature"))? + .collect::, _>>()? } else { vec![] }; @@ -169,23 +168,29 @@ impl TryFrom for SignedTransaction { signatures, }) } else { - Err(Error::msg("Cannot decode transaction")) + Err(Error::from_error( + Kind::Other, + "Expected a deserialized transaction but got None", + )) } } } impl TryFrom for TransactionEffects { - type Error = anyhow::Error; + type Error = error::Error; fn try_from(value: TxBlockEffects) -> Result { let effects = value .effects .map(|fx| base64ct::Base64::decode_vec(fx.bcs.unwrap().0.as_str())) - .transpose() - .map_err(|_| Error::msg("Cannot decode Base64 effects bcs bytes"))? + .transpose()? .map(|bcs| bcs::from_bytes::(&bcs)) - .transpose() - .map_err(|_| Error::msg("Cannot decode bcs bytes into TransactionEffects"))?; - effects.ok_or_else(|| Error::msg("Cannot decode effects")) + .transpose()?; + effects.ok_or_else(|| { + Error::from_error( + Kind::Other, + "Cannot convert GraphQL TxBlockEffects into TransactionEffects", + ) + }) } } diff --git a/crates/sui-graphql-client/src/streams.rs b/crates/sui-graphql-client/src/streams.rs index 5b0c96e92..f16985e71 100644 --- a/crates/sui-graphql-client/src/streams.rs +++ b/crates/sui-graphql-client/src/streams.rs @@ -1,9 +1,9 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use crate::error; use crate::query_types::PageInfo; use crate::Direction; -use crate::Error; use crate::Page; use crate::PaginationFilter; @@ -41,9 +41,9 @@ where T: Clone + Unpin, F: Fn(PaginationFilter) -> Fut, F: Unpin, - Fut: Future, Error>>, + Fut: Future, error::Error>>, { - type Item = Result; + type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.finished { @@ -172,7 +172,7 @@ where pub fn stream_paginated_query(query_fn: F, direction: Direction) -> PageStream where F: Fn(PaginationFilter) -> Fut, - Fut: Future, Error>>, + Fut: Future, error::Error>>, { PageStream::new(query_fn, direction) } diff --git a/crates/sui-sdk-types/src/types/mod.rs b/crates/sui-sdk-types/src/types/mod.rs index 9fc30ff73..892efc1d6 100644 --- a/crates/sui-sdk-types/src/types/mod.rs +++ b/crates/sui-sdk-types/src/types/mod.rs @@ -14,6 +14,7 @@ mod type_tag; mod u256; pub use address::Address; +pub use address::AddressParseError; pub use checkpoint::CheckpointCommitment; pub use checkpoint::CheckpointContents; pub use checkpoint::CheckpointData;