diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 4b360698..e93308d8 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -72,4 +72,4 @@ jobs: name: Test for no_std with: command: test - args: --release --no-default-features --features embassy-rt,core,wallet,models,helpers,websocket,json-rpc + args: --release --no-default-features --features embassy-rt,core,utils,wallet,models,helpers,websocket,json-rpc diff --git a/Cargo.toml b/Cargo.toml index 30052acf..b9b7c78e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,6 @@ hashbrown = { version = "0.14.5", features = ["serde"] } fnv = { version = "1.0.7", default-features = false } derive-new = { version = "0.7.0", default-features = false } thiserror-no-std = "2.0.2" -anyhow = { version = "1.0.69", default-features = false } embassy-sync = "0.6.0" # networking @@ -95,6 +94,7 @@ bigdecimal = { version = "0.4.5", features = ["serde-json"] } criterion = "0.5.1" tokio = { version = "1.0", features = ["full"] } embedded-io-adapters = { version = "0.6.1", features = ["tokio-1"] } +anyhow = { version = "1.0.91", no-default-features = true } [[bench]] name = "benchmarks" @@ -109,42 +109,15 @@ default = [ "models", "utils", "helpers", - "websocket", "json-rpc", + "websocket", ] -models = [ - "transaction-models", - "request-models", - "ledger-models", - "result-models", -] -transaction-models = ["core"] -request-models = [] -result-models = ["request-models", "ledger-models"] -ledger-models = [] -helpers = [ - "account-helpers", - "ledger-helpers", - "transaction-helpers", - "wallet-helpers", -] -account-helpers = ["core", "request-models", "result-models"] -ledger-helpers = ["request-models", "result-models"] -wallet-helpers = ["wallet", "request-models", "result-models"] -transaction-helpers = [ - "wallet", - "account-helpers", - "ledger-helpers", - "request-models", - "result-models", - "transaction-models", - "ledger-models", -] +models = ["core"] +helpers = ["core", "models", "wallet"] wallet = ["core"] -json-rpc = ["request-models", "result-models", "reqwless", "embedded-nal-async"] +json-rpc = ["models", "reqwless", "embedded-io-async", "embedded-nal-async"] websocket = [ - "request-models", - "result-models", + "models", "futures", "embedded-io-async", "embedded-websocket-embedded-io", diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 42e85079..82c91b43 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -1,5 +1,5 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use xrpl::core::definitions::get_field_type_name; +use xrpl::core::binarycodec::definitions::get_field_type_name; use xrpl::utils::xrp_to_drops; pub fn bench_xrp_to_drops(c: &mut Criterion) { diff --git a/src/_anyhow/mod.rs b/src/_anyhow/mod.rs deleted file mode 100644 index 7b4ab3ec..00000000 --- a/src/_anyhow/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -/// Turns a `thiserror_no_std::Error` into a `anyhow::Error` -#[macro_export] -macro_rules! Err { - ($err:expr $(,)?) => {{ - use alloc::string::ToString; - - let error = $err.to_string().replace("\"", ""); - let boxed_error = ::alloc::boxed::Box::new(error); - let leaked_error: &'static str = ::alloc::boxed::Box::leak(boxed_error); - Err(anyhow::anyhow!(leaked_error)) - }}; -} diff --git a/src/account/mod.rs b/src/account/mod.rs index 69a102c7..334a6718 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -1,5 +1,4 @@ use alloc::borrow::Cow; -use anyhow::Result; use embassy_futures::block_on; use crate::{ @@ -12,6 +11,7 @@ use crate::{ get_xrp_balance as async_get_xrp_balance, }, clients::XRPLClient, + exceptions::XRPLHelperResult, }, models::{ledger::objects::AccountRoot, results::account_tx::AccountTx, XRPAmount}, }; @@ -20,7 +20,7 @@ pub fn does_account_exist( address: Cow<'_, str>, client: &C, ledger_index: Option>, -) -> Result +) -> XRPLHelperResult where C: XRPLClient, { @@ -31,7 +31,7 @@ pub fn get_next_valid_seq_number( address: Cow<'_, str>, client: &C, ledger_index: Option>, -) -> Result +) -> XRPLHelperResult where C: XRPLClient, { @@ -46,7 +46,7 @@ pub fn get_xrp_balance<'a: 'b, 'b, C>( address: Cow<'a, str>, client: &C, ledger_index: Option>, -) -> Result> +) -> XRPLHelperResult> where C: XRPLClient, { @@ -57,7 +57,7 @@ pub fn get_account_root<'a: 'b, 'b, C>( address: Cow<'a, str>, client: &C, ledger_index: Cow<'a, str>, -) -> Result> +) -> XRPLHelperResult> where C: XRPLClient, { @@ -67,7 +67,7 @@ where pub fn get_latest_transaction<'a: 'b, 'b, C>( address: Cow<'a, str>, client: &C, -) -> Result> +) -> XRPLHelperResult> where C: XRPLClient, { diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index a29a1c10..9b2bbb29 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -1,23 +1,22 @@ use alloc::borrow::Cow; -use anyhow::Result; use crate::{ core::addresscodec::{is_valid_xaddress, xaddress_to_classic_address}, models::{ ledger::objects::AccountRoot, requests::{account_info::AccountInfo, account_tx::AccountTx}, - results, XRPAmount, + results::{self}, + XRPAmount, }, - Err, }; -use super::clients::XRPLAsyncClient; +use super::{clients::XRPLAsyncClient, exceptions::XRPLHelperResult}; pub async fn does_account_exist( address: Cow<'_, str>, client: &C, ledger_index: Option>, -) -> Result +) -> XRPLHelperResult where C: XRPLAsyncClient, { @@ -31,7 +30,7 @@ pub async fn get_next_valid_seq_number( address: Cow<'_, str>, client: &impl XRPLAsyncClient, ledger_index: Option>, -) -> Result { +) -> XRPLHelperResult { let account_info = get_account_root(address, client, ledger_index.unwrap_or("current".into())).await?; Ok(account_info.sequence) @@ -41,7 +40,7 @@ pub async fn get_xrp_balance<'a: 'b, 'b, C>( address: Cow<'a, str>, client: &C, ledger_index: Option>, -) -> Result> +) -> XRPLHelperResult> where C: XRPLAsyncClient, { @@ -57,16 +56,13 @@ pub async fn get_account_root<'a: 'b, 'b, C>( address: Cow<'a, str>, client: &C, ledger_index: Cow<'a, str>, -) -> Result> +) -> XRPLHelperResult> where C: XRPLAsyncClient, { let mut classic_address = address; if is_valid_xaddress(&classic_address) { - classic_address = match xaddress_to_classic_address(&classic_address) { - Ok(addr) => addr.0.into(), - Err(e) => return Err!(e), - }; + classic_address = xaddress_to_classic_address(&classic_address)?.0.into(); } let request = AccountInfo::new( None, @@ -88,15 +84,12 @@ where pub async fn get_latest_transaction<'a: 'b, 'b, C>( mut address: Cow<'a, str>, client: &C, -) -> Result> +) -> XRPLHelperResult> where C: XRPLAsyncClient, { if is_valid_xaddress(&address) { - address = match xaddress_to_classic_address(&address) { - Ok((address, _, _)) => address.into(), - Err(e) => return Err!(e), - }; + address = xaddress_to_classic_address(&address)?.0.into(); } let account_tx = AccountTx::new( None, @@ -111,5 +104,6 @@ where None, ); let response = client.request(account_tx.into()).await?; - response.try_into_result::>() + + Ok(response.try_into_result::>()?) } diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs index 420ee328..3262ef8e 100644 --- a/src/asynch/clients/async_client.rs +++ b/src/asynch/clients/async_client.rs @@ -1,17 +1,19 @@ -use super::{client::XRPLClient, CommonFields}; +use super::{client::XRPLClient, exceptions::XRPLClientResult, CommonFields}; use crate::models::{ requests::{server_state::ServerState, XRPLRequest}, results::{server_state::ServerState as ServerStateResult, XRPLResponse}, }; -use anyhow::Result; #[allow(async_fn_in_trait)] pub trait XRPLAsyncClient: XRPLClient { - async fn request<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result> { + async fn request<'a: 'b, 'b>( + &self, + request: XRPLRequest<'a>, + ) -> XRPLClientResult> { self.request_impl(request).await } - async fn get_common_fields(&self) -> Result> { + async fn get_common_fields(&self) -> XRPLClientResult> { let server_state = self.request(ServerState::new(None).into()).await?; let state = server_state .try_into_result::>()? diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index 8b2aaa5f..9740a4d7 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -3,12 +3,16 @@ use crate::models::{ results::XRPLResponse, }; use alloc::borrow::Cow; -use anyhow::Result; use url::Url; +use super::exceptions::XRPLClientResult; + #[allow(async_fn_in_trait)] pub trait XRPLClient { - async fn request_impl<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result>; + async fn request_impl<'a: 'b, 'b>( + &self, + request: XRPLRequest<'a>, + ) -> XRPLClientResult>; fn get_host(&self) -> Url; diff --git a/src/asynch/clients/exceptions.rs b/src/asynch/clients/exceptions.rs new file mode 100644 index 00000000..0423e659 --- /dev/null +++ b/src/asynch/clients/exceptions.rs @@ -0,0 +1,65 @@ +use thiserror_no_std::Error; + +#[cfg(feature = "helpers")] +use crate::asynch::wallet::exceptions::XRPLFaucetException; +use crate::{models::XRPLModelException, XRPLSerdeJsonError}; + +#[cfg(feature = "json-rpc")] +use super::XRPLJsonRpcException; +#[cfg(feature = "websocket")] +use super::XRPLWebSocketException; + +pub type XRPLClientResult = core::result::Result; + +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum XRPLClientException { + #[error("serde_json error: {0}")] + XRPLSerdeJsonError(#[from] XRPLSerdeJsonError), + #[error("XRPL Model error: {0}")] + XRPLModelError(#[from] XRPLModelException), + #[cfg(feature = "helpers")] + #[error("XRPL Faucet error: {0}")] + XRPLFaucetError(#[from] XRPLFaucetException), + #[cfg(feature = "websocket")] + #[error("XRPL WebSocket error: {0}")] + XRPLWebSocketError(#[from] XRPLWebSocketException), + #[cfg(feature = "json-rpc")] + #[error("XRPL JSON-RPC error: {0}")] + XRPLJsonRpcError(#[from] XRPLJsonRpcException), + #[error("URL parse error: {0}")] + UrlParseError(#[from] url::ParseError), + #[cfg(feature = "std")] + #[error("I/O error: {0}")] + IoError(#[from] alloc::io::Error), +} + +impl From for XRPLClientException { + fn from(error: serde_json::Error) -> Self { + XRPLClientException::XRPLSerdeJsonError(XRPLSerdeJsonError::from(error)) + } +} + +#[cfg(all(not(feature = "std"), feature = "json-rpc"))] +impl From for XRPLClientException { + fn from(error: reqwless::Error) -> Self { + XRPLClientException::XRPLJsonRpcError(XRPLJsonRpcException::ReqwlessError(error)) + } +} + +#[cfg(all(feature = "std", feature = "websocket"))] +impl From for XRPLClientException { + fn from(error: tokio_tungstenite::tungstenite::Error) -> Self { + XRPLClientException::XRPLWebSocketError(XRPLWebSocketException::from(error)) + } +} + +#[cfg(all(feature = "std", feature = "json-rpc"))] +impl From for XRPLClientException { + fn from(error: reqwest::Error) -> Self { + XRPLClientException::XRPLJsonRpcError(XRPLJsonRpcException::ReqwestError(error)) + } +} + +#[cfg(feature = "std")] +impl alloc::error::Error for XRPLClientException {} diff --git a/src/asynch/clients/json_rpc/exceptions.rs b/src/asynch/clients/json_rpc/exceptions.rs index bc10b2d7..80e57394 100644 --- a/src/asynch/clients/json_rpc/exceptions.rs +++ b/src/asynch/clients/json_rpc/exceptions.rs @@ -1,10 +1,11 @@ use thiserror_no_std::Error; #[derive(Debug, Error)] +#[non_exhaustive] pub enum XRPLJsonRpcException { - #[error("Reqwless error")] - ReqwlessError, + #[error("Reqwless error: {0:?}")] + ReqwlessError(#[from] reqwless::Error), #[cfg(feature = "std")] - #[error("Request error: {0:?}")] - RequestError(reqwest::Response), + #[error("Reqwest error: {0:?}")] + ReqwestError(#[from] reqwest::Error), } diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index 3431a2ea..cca9a569 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -1,25 +1,33 @@ use alloc::{string::ToString, vec}; -use anyhow::Result; use serde::Serialize; use serde_json::{Map, Value}; -use crate::{models::results::XRPLResponse, Err}; +use crate::{models::results::XRPLResponse, XRPLSerdeJsonError}; mod exceptions; pub use exceptions::XRPLJsonRpcException; -use super::client::XRPLClient; +use super::{client::XRPLClient, exceptions::XRPLClientResult}; /// Renames the requests field `command` to `method` for JSON-RPC. -fn request_to_json_rpc(request: &impl Serialize) -> Result { +fn request_to_json_rpc(request: &impl Serialize) -> XRPLClientResult { let mut json_rpc_request = Map::new(); - let mut request = match serde_json::to_value(request) { - Ok(request) => match request.as_object().cloned() { - Some(request) => request, - None => todo!("Handle non-object requests"), - }, - Err(error) => return Err!(error), - }; + // let mut request = match serde_json::to_value(request) { + // Ok(request) => match request.as_object().cloned() { + // Some(request) => request, + // None => todo!("Handle non-object requests"), + // }, + // Err(error) => return Err!(error), + // }; + let request_value = serde_json::to_value(request)?; + let mut request = request_value + .clone() + .as_object() + .ok_or(XRPLSerdeJsonError::UnexpectedValueType { + expected: "Object".to_string(), + found: request_value, + })? + .clone(); if let Some(command) = request.remove("command") { json_rpc_request.insert("method".to_string(), command); json_rpc_request.insert( @@ -33,13 +41,13 @@ fn request_to_json_rpc(request: &impl Serialize) -> Result { #[cfg(all(feature = "json-rpc", feature = "std"))] mod _std { - use crate::{ - asynch::clients::XRPLFaucet, - models::requests::{FundFaucet, XRPLRequest}, - }; + use crate::models::requests::XRPLRequest; + #[cfg(feature = "helpers")] + use crate::{asynch::clients::XRPLFaucet, models::requests::FundFaucet}; + #[cfg(feature = "helpers")] + use alloc::string::ToString; use super::*; - use alloc::string::ToString; use reqwest::Client as HttpClient; use url::Url; @@ -57,7 +65,7 @@ mod _std { async fn request_impl<'a: 'b, 'b>( &self, request: XRPLRequest<'a>, - ) -> Result> { + ) -> XRPLClientResult> { let client = HttpClient::new(); let request_json_rpc = request_to_json_rpc(&request)?; let response = client @@ -70,9 +78,9 @@ mod _std { Ok(response) => { Ok(serde_json::from_str::>(&response).unwrap()) } - Err(error) => Err!(error), + Err(error) => Err(error.into()), }, - Err(error) => Err!(error), + Err(error) => Err(error.into()), } } @@ -81,8 +89,13 @@ mod _std { } } + #[cfg(feature = "helpers")] impl XRPLFaucet for AsyncJsonRpcClient { - async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()> { + async fn request_funding( + &self, + url: Option, + request: FundFaucet<'_>, + ) -> XRPLClientResult<()> { let faucet_url = self.get_faucet_url(url)?; let client = HttpClient::new(); let request_json_rpc = serde_json::to_value(&request).unwrap(); @@ -97,12 +110,9 @@ mod _std { Ok(()) } else { todo!() - // Err!(XRPLJsonRpcException::RequestError()) } } - Err(error) => { - Err!(error) - } + Err(error) => Err(error.into()), } } } @@ -110,10 +120,9 @@ mod _std { #[cfg(all(feature = "json-rpc", not(feature = "std")))] mod _no_std { - use crate::{ - asynch::clients::{SingleExecutorMutex, XRPLFaucet}, - models::requests::{FundFaucet, XRPLRequest}, - }; + use crate::{asynch::clients::SingleExecutorMutex, models::requests::XRPLRequest}; + #[cfg(feature = "helpers")] + use crate::{asynch::clients::XRPLFaucet, models::requests::FundFaucet}; use super::*; use alloc::sync::Arc; @@ -166,7 +175,7 @@ mod _no_std { async fn request_impl<'a: 'b, 'b>( &self, request: XRPLRequest<'a>, - ) -> Result> { + ) -> XRPLClientResult> { let request_json_rpc = request_to_json_rpc(&request)?; let request_string = request_json_rpc.to_string(); let request_buf = request_string.as_bytes(); @@ -174,21 +183,18 @@ mod _no_std { let mut client = self.client.lock().await; let response = match client.request(Method::POST, self.url.as_str()).await { Ok(client) => { - if let Err(_error) = client + if let Err(error) = client .body(request_buf) .content_type(ContentType::ApplicationJson) .send(&mut rx_buffer) .await { - Err!(XRPLJsonRpcException::ReqwlessError) + Err(error.into()) } else { - match serde_json::from_slice::>(&rx_buffer) { - Ok(response) => Ok(response), - Err(error) => Err!(error), - } + Ok(serde_json::from_slice::>(&rx_buffer)?) } } - Err(_error) => Err!(XRPLJsonRpcException::ReqwlessError), + Err(error) => Err(error.into()), }; response @@ -199,13 +205,18 @@ mod _no_std { } } + #[cfg(feature = "helpers")] impl<'a, const BUF: usize, T, D, M> XRPLFaucet for AsyncJsonRpcClient<'a, BUF, T, D, M> where M: RawMutex, T: TcpConnect + 'a, D: Dns + 'a, { - async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()> { + async fn request_funding( + &self, + url: Option, + request: FundFaucet<'_>, + ) -> XRPLClientResult<()> { let faucet_url = self.get_faucet_url(url)?; let request_json_rpc = serde_json::to_value(&request).unwrap(); let request_string = request_json_rpc.to_string(); @@ -214,31 +225,27 @@ mod _no_std { let mut client = self.client.lock().await; let response = match client.request(Method::POST, faucet_url.as_str()).await { Ok(client) => { - if let Err(_error) = client + if let Err(error) = client .body(request_buf) .content_type(ContentType::ApplicationJson) .send(&mut rx_buffer) .await { - Err!(XRPLJsonRpcException::ReqwlessError) + Err(error.into()) } else { - if let Ok(response) = serde_json::from_slice::>(&rx_buffer) - { - if response.is_success() { - Ok(()) - } else { - todo!() - // Err!(XRPLJsonRpcException::RequestError()) - } + let response = serde_json::from_slice::>(&rx_buffer)?; + if response.is_success() { + Ok(()) } else { - Err!(XRPLJsonRpcException::ReqwlessError) + todo!() + // Err!(XRPLJsonRpcException::RequestError()) } } } - Err(_error) => Err!(XRPLJsonRpcException::ReqwlessError), + Err(error) => Err(XRPLJsonRpcException::ReqwlessError(error)), }; - response + response.map_err(Into::into) } } } diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index c80e2088..49726674 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,14 +1,18 @@ pub mod async_client; pub mod client; +pub mod exceptions; #[cfg(feature = "json-rpc")] mod json_rpc; #[cfg(feature = "websocket")] mod websocket; use alloc::borrow::Cow; -use anyhow::Result; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; use serde::{Deserialize, Serialize}; + +#[cfg(feature = "helpers")] +use exceptions::XRPLClientResult; +#[cfg(feature = "helpers")] use url::Url; pub use async_client::*; @@ -24,10 +28,13 @@ pub type SingleExecutorMutex = NoopRawMutex; const TEST_FAUCET_URL: &str = "https://faucet.altnet.rippletest.net/accounts"; const DEV_FAUCET_URL: &str = "https://faucet.devnet.rippletest.net/accounts"; -use crate::{asynch::XRPLFaucetException, models::requests::FundFaucet, Err}; +#[cfg(feature = "helpers")] +use crate::{asynch::wallet::exceptions::XRPLFaucetException, models::requests::FundFaucet}; + +#[cfg(feature = "helpers")] #[allow(async_fn_in_trait)] pub trait XRPLFaucet: XRPLClient { - fn get_faucet_url(&self, url: Option) -> Result + fn get_faucet_url(&self, url: Option) -> XRPLClientResult where Self: Sized + XRPLClient, { @@ -37,24 +44,22 @@ pub trait XRPLFaucet: XRPLClient { let host = self.get_host(); let host_str = host.host_str().unwrap(); if host_str.contains("altnet") || host_str.contains("testnet") { - match Url::parse(TEST_FAUCET_URL) { - Ok(url) => Ok(url), - Err(error) => Err!(error), - } + Ok(Url::parse(TEST_FAUCET_URL)?) } else if host_str.contains("devnet") { - match Url::parse(DEV_FAUCET_URL) { - Ok(url) => Ok(url), - Err(error) => Err!(error), - } + Ok(Url::parse(DEV_FAUCET_URL)?) } else if host_str.contains("sidechain-net2") { - Err!(XRPLFaucetException::CannotFundSidechainAccount) + Err(XRPLFaucetException::CannotFundSidechainAccount.into()) } else { - Err!(XRPLFaucetException::CannotDeriveFaucetUrl) + Err(XRPLFaucetException::CannotDeriveFaucetUrl.into()) } } } - async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()>; + async fn request_funding( + &self, + url: Option, + request: FundFaucet<'_>, + ) -> XRPLClientResult<()>; } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/asynch/clients/websocket/_no_std.rs b/src/asynch/clients/websocket/_no_std.rs index 306dccbd..0363b558 100644 --- a/src/asynch/clients/websocket/_no_std.rs +++ b/src/asynch/clients/websocket/_no_std.rs @@ -4,7 +4,6 @@ use alloc::{ string::{String, ToString}, sync::Arc, }; -use anyhow::Result; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; use embedded_io_async::{ErrorType, Read, Write}; @@ -16,20 +15,19 @@ use rand::RngCore; use url::Url; use super::{WebSocketClosed, WebSocketOpen}; -use crate::{ - asynch::clients::SingleExecutorMutex, - models::requests::{Request, XRPLRequest}, -}; use crate::{ asynch::clients::{ client::XRPLClient as ClientTrait, websocket::websocket_base::{MessageHandler, WebsocketBase}, }, models::results::XRPLResponse, - Err, +}; +use crate::{ + asynch::clients::{exceptions::XRPLClientResult, SingleExecutorMutex}, + models::requests::{Request, XRPLRequest}, }; -use super::exceptions::XRPLWebsocketException; +use super::exceptions::XRPLWebSocketException; pub struct AsyncWebSocketClient< const BUF: usize, @@ -60,7 +58,7 @@ where rng: Rng, sub_protocols: Option<&[&str]>, additional_headers: Option<&[&str]>, - ) -> Result> { + ) -> XRPLClientResult> { // replace the scheme with http or https let scheme = match url.scheme() { "wss" => "https", @@ -79,7 +77,7 @@ where let path = url.path(); let host = match url.host_str() { Some(host) => host, - None => return Err!(XRPLWebsocketException::::Disconnected), + None => return Err(XRPLWebSocketException::Disconnected.into()), }; let origin = scheme.to_string() + "://" + host + ":" + &port + path; let websocket_options = WebSocketOptions { @@ -106,7 +104,7 @@ where // FramerError::WebSocket(embedded_websocket_embedded_io::Error::HttpResponseCodeInvalid( // Some(308), // )) => (), - error => return Err!(XRPLWebsocketException::from(error)), + error => return Err(XRPLWebSocketException::from(error).into()), } } @@ -127,7 +125,7 @@ where M: RawMutex, Tcp: Read + Write + Unpin, { - type Error = XRPLWebsocketException<::Error>; + type Error = XRPLWebSocketException; } impl AsyncWebSocketClient @@ -135,7 +133,7 @@ where M: RawMutex, Tcp: Read + Write + Unpin, { - async fn do_write(&self, buf: &[u8]) -> Result::Error> { + async fn do_write(&self, buf: &[u8]) -> XRPLClientResult::Error> { let mut inner = self.websocket.lock().await; let mut tcp = self.tcp.lock().await; let mut buffer = self.tx_buffer; @@ -150,11 +148,11 @@ where .await { Ok(()) => Ok(buf.len()), - Err(error) => Err(XRPLWebsocketException::from(error)), + Err(error) => Err(XRPLWebSocketException::from(error)), } } - async fn do_read(&self, buf: &mut [u8]) -> Result::Error> { + async fn do_read(&self, buf: &mut [u8]) -> XRPLClientResult::Error> { let mut inner = self.websocket.lock().await; let mut tcp = self.tcp.lock().await; match inner.read(tcp.deref_mut(), buf).await { @@ -162,9 +160,9 @@ where Some(Ok(ReadResult::Binary(b))) => Ok(b.len()), Some(Ok(ReadResult::Ping(_))) => Ok(0), Some(Ok(ReadResult::Pong(_))) => Ok(0), - Some(Ok(ReadResult::Close(_))) => Err(XRPLWebsocketException::Disconnected), - Some(Err(error)) => Err(XRPLWebsocketException::from(error)), - None => Err(XRPLWebsocketException::Disconnected), + Some(Ok(ReadResult::Close(_))) => Err(XRPLWebSocketException::Disconnected.into()), + Some(Err(error)) => Err(XRPLWebSocketException::from(error).into()), + None => Err(XRPLWebSocketException::Disconnected.into()), } } } @@ -175,7 +173,7 @@ where M: RawMutex, Tcp: Read + Write + Unpin, { - async fn write(&mut self, buf: &[u8]) -> Result { + async fn write(&mut self, buf: &[u8]) -> XRPLClientResult { self.do_write(buf).await } } @@ -186,7 +184,7 @@ where M: RawMutex, Tcp: Read + Write + Unpin, { - async fn read(&mut self, buf: &mut [u8]) -> Result { + async fn read(&mut self, buf: &mut [u8]) -> XRPLClientResult { self.do_read(buf).await } } @@ -202,7 +200,7 @@ where websocket_base.setup_request_future(id).await; } - async fn handle_message(&mut self, message: String) -> Result<()> { + async fn handle_message(&mut self, message: String) -> XRPLClientResult<()> { let mut websocket_base = self.websocket_base.lock().await; websocket_base.handle_message(message).await } @@ -212,7 +210,7 @@ where websocket_base.pop_message().await } - async fn try_recv_request(&mut self, id: String) -> Result> { + async fn try_recv_request(&mut self, id: String) -> XRPLClientResult> { let mut websocket_base = self.websocket_base.lock().await; websocket_base.try_recv_request(id).await } @@ -232,7 +230,7 @@ where async fn request_impl<'a: 'b, 'b>( &self, mut request: XRPLRequest<'a>, - ) -> Result> { + ) -> XRPLClientResult> { // setup request future self.set_request_id(&mut request); let request_id = request.get_common_fields().id.as_ref().unwrap(); @@ -241,13 +239,8 @@ where .setup_request_future(request_id.to_string()) .await; // send request - let request_string = match serde_json::to_string(&request) { - Ok(request_string) => request_string, - Err(error) => return Err!(error), - }; - if let Err(error) = self.do_write(request_string.as_bytes()).await { - return Err!(error); - } + let request_string = serde_json::to_string(&request)?; + self.do_write(request_string.as_bytes()).await?; // wait for response loop { let mut rx_buffer = [0; 1024]; @@ -259,9 +252,7 @@ where } let message_str = match core::str::from_utf8(&rx_buffer[..u_size]) { Ok(response_str) => response_str, - Err(error) => { - return Err!(XRPLWebsocketException::::Utf8(error)) - } + Err(error) => return Err(XRPLWebSocketException::Utf8(error).into()), }; websocket_base .handle_message(message_str.to_string()) @@ -270,14 +261,11 @@ where .try_recv_request(request_id.to_string()) .await?; if let Some(message) = message_opt { - let response = match serde_json::from_str(&message) { - Ok(response) => response, - Err(error) => return Err!(error), - }; + let response = serde_json::from_str(&message)?; return Ok(response); } } - Err(error) => return Err!(error), + Err(error) => return Err(error.into()), } } } diff --git a/src/asynch/clients/websocket/_std.rs b/src/asynch/clients/websocket/_std.rs index ba5d550c..42b31596 100644 --- a/src/asynch/clients/websocket/_std.rs +++ b/src/asynch/clients/websocket/_std.rs @@ -1,15 +1,14 @@ -use super::exceptions::XRPLWebsocketException; +use super::exceptions::XRPLWebSocketException; use super::{WebSocketClosed, WebSocketOpen}; use crate::asynch::clients::client::XRPLClient; +use crate::asynch::clients::exceptions::{XRPLClientException, XRPLClientResult}; use crate::asynch::clients::websocket::websocket_base::{MessageHandler, WebsocketBase}; use crate::asynch::clients::SingleExecutorMutex; use crate::models::requests::{Request, XRPLRequest}; use crate::models::results::XRPLResponse; -use crate::Err; use alloc::string::{String, ToString}; use alloc::sync::Arc; -use anyhow::Result; use core::marker::PhantomData; use core::{pin::Pin, task::Poll}; use embassy_futures::block_on; @@ -38,36 +37,36 @@ impl Sink for AsyncWebSocketClient where M: RawMutex, { - type Error = anyhow::Error; + type Error = XRPLClientException; fn poll_ready( self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { + ) -> core::task::Poll> { let mut guard = block_on(self.websocket.lock()); match Pin::new(&mut *guard).poll_ready(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Ready(Err(error)) => Poll::Ready(Err(error.into())), Poll::Pending => Poll::Pending, } } - fn start_send(self: core::pin::Pin<&mut Self>, item: String) -> Result<()> { + fn start_send(self: core::pin::Pin<&mut Self>, item: String) -> XRPLClientResult<()> { let mut guard = block_on(self.websocket.lock()); match Pin::new(&mut *guard).start_send(tungstenite::Message::Text(item)) { Ok(()) => Ok(()), - Err(error) => Err!(error), + Err(error) => Err(error.into()), } } fn poll_flush( self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { + ) -> core::task::Poll> { let mut guard = block_on(self.websocket.lock()); match Pin::new(&mut *guard).poll_flush(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Ready(Err(error)) => Poll::Ready(Err(error.into())), Poll::Pending => Poll::Pending, } } @@ -75,11 +74,11 @@ where fn poll_close( self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { + ) -> core::task::Poll> { let mut guard = block_on(self.websocket.lock()); match Pin::new(&mut *guard).poll_close(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Ready(Err(error)) => Poll::Ready(Err(error.into())), Poll::Pending => Poll::Pending, } } @@ -89,7 +88,7 @@ impl Stream for AsyncWebSocketClient where M: RawMutex, { - type Item = Result; + type Item = XRPLClientResult; fn poll_next( self: Pin<&mut Self>, @@ -104,27 +103,26 @@ where let response_string = match String::from_utf8(response) { Ok(string) => string, Err(error) => { - return Poll::Ready(Some(Err!(XRPLWebsocketException::< - anyhow::Error, - >::Utf8( - error.utf8_error() - )))); + return Poll::Ready(Some(Err(XRPLWebSocketException::Utf8( + error.utf8_error(), + ) + .into()))); } }; Poll::Ready(Some(Ok(response_string))) } - tungstenite::Message::Close(_) => Poll::Ready(Some(Err!( - XRPLWebsocketException::::Disconnected - ))), - _ => Poll::Ready(Some(Err!( - XRPLWebsocketException::::UnexpectedMessageType + tungstenite::Message::Close(_) => { + Poll::Ready(Some(Err(XRPLWebSocketException::Disconnected.into()))) + } + _ => Poll::Ready(Some(Err( + XRPLWebSocketException::UnexpectedMessageType.into() ))), }, - Err(error) => Poll::Ready(Some(Err!(error))), + Err(error) => Poll::Ready(Some(Err(error.into()))), }, - Poll::Ready(None) => Poll::Ready(Some(Err!( - XRPLWebsocketException::::Disconnected - ))), + Poll::Ready(None) => { + Poll::Ready(Some(Err(XRPLWebSocketException::Disconnected.into()))) + } Poll::Pending => Poll::Pending, } } @@ -134,10 +132,10 @@ impl AsyncWebSocketClient where M: RawMutex, { - pub async fn open(uri: Url) -> Result> { + pub async fn open(uri: Url) -> XRPLClientResult> { let stream = match tokio_tungstenite_connect_async(uri.to_string()).await { Ok((stream, _)) => stream, - Err(error) => return Err!(error), + Err(error) => return Err(error.into()), }; Ok(AsyncWebSocketClient { websocket: Arc::new(Mutex::new(stream)), @@ -152,13 +150,13 @@ impl AsyncWebSocketClient where M: RawMutex, { - pub async fn close(&self) -> Result<()> { + pub async fn close(&self) -> XRPLClientResult<()> { let mut websocket = self.websocket.lock().await; let mut websocket_base = self.websocket_base.lock().await; websocket_base.close(); match websocket.close(None).await { Ok(()) => Ok(()), - Err(error) => Err!(error), + Err(error) => Err(error.into()), } } } @@ -181,7 +179,7 @@ where websocket_base.setup_request_future(id).await; } - async fn handle_message(&mut self, message: String) -> Result<()> { + async fn handle_message(&mut self, message: String) -> XRPLClientResult<()> { let mut websocket_base = self.websocket_base.lock().await; websocket_base.handle_message(message).await } @@ -191,7 +189,7 @@ where websocket_base.pop_message().await } - async fn try_recv_request(&mut self, id: String) -> Result> { + async fn try_recv_request(&mut self, id: String) -> XRPLClientResult> { let mut websocket_base = self.websocket_base.lock().await; websocket_base.try_recv_request(id).await } @@ -208,7 +206,7 @@ where async fn request_impl<'a: 'b, 'b>( &self, mut request: XRPLRequest<'a>, - ) -> Result> { + ) -> XRPLClientResult> { // setup request future self.set_request_id(&mut request); let request_id = request.get_common_fields().id.as_ref().unwrap(); @@ -220,13 +218,13 @@ where let mut websocket = self.websocket.lock().await; let request_string = match serde_json::to_string(&request) { Ok(request_string) => request_string, - Err(error) => return Err!(error), + Err(error) => return Err(error.into()), }; if let Err(error) = websocket .send(tungstenite::Message::Text(request_string)) .await { - return Err!(error); + return Err(error.into()); } // wait for response loop { @@ -240,7 +238,7 @@ where if let Some(message) = message_opt { let response = match serde_json::from_str(&message) { Ok(response) => response, - Err(error) => return Err!(error), + Err(error) => return Err(error.into()), }; return Ok(response); } @@ -249,23 +247,21 @@ where let message = match String::from_utf8(response) { Ok(string) => string, Err(error) => { - return Err!(XRPLWebsocketException::::Utf8( - error.utf8_error() - )); + return Err(XRPLWebSocketException::Utf8(error.utf8_error()).into()); } }; match serde_json::from_str(&message) { Ok(response) => return Ok(response), - Err(error) => return Err!(error), + Err(error) => return Err(error.into()), } } Some(Ok(tungstenite::Message::Close(_))) => { - return Err!(XRPLWebsocketException::::Disconnected) + return Err(XRPLWebSocketException::Disconnected.into()); } Some(Ok(_)) => { - return Err!(XRPLWebsocketException::::UnexpectedMessageType); + return Err(XRPLWebSocketException::UnexpectedMessageType.into()); } - Some(Err(error)) => return Err!(error), + Some(Err(error)) => return Err(error.into()), None => continue, } } diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs index 491811e0..2d65beaf 100644 --- a/src/asynch/clients/websocket/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -1,16 +1,19 @@ +use alloc::string::String; use core::fmt::Debug; use core::str::Utf8Error; #[cfg(all(feature = "websocket", not(feature = "std")))] use embedded_io_async::{Error as EmbeddedIoError, ErrorKind}; #[cfg(all(feature = "websocket", not(feature = "std")))] use embedded_websocket_embedded_io::framer_async::FramerError; +use futures::channel::oneshot::Canceled; use thiserror_no_std::Error; #[derive(Debug, Error)] -pub enum XRPLWebsocketException { +#[non_exhaustive] +pub enum XRPLWebSocketException { // FramerError #[error("I/O error: {0:?}")] - Io(E), + Io(String), #[error("Frame too large (size: {0:?})")] FrameTooLarge(usize), #[error("Failed to interpret u8 to string (error: {0:?})")] @@ -35,32 +38,41 @@ pub enum XRPLWebsocketException { MissingRequestReceiver, #[error("Invalid message.")] InvalidMessage, + #[error("Failed to send message through channel: {0:?}")] + MessageChannelError(String), + #[error("Failed to receive message through channel: {0:?}")] + Canceled(#[from] Canceled), + #[cfg(feature = "std")] + #[error("Tungstenite error: {0:?}")] + TungsteniteError(#[from] tokio_tungstenite::tungstenite::Error), } #[cfg(all(feature = "websocket", not(feature = "std")))] -impl From> for XRPLWebsocketException { +impl From> for XRPLWebSocketException { fn from(value: FramerError) -> Self { + use alloc::format; + match value { - FramerError::Io(e) => XRPLWebsocketException::Io(e), - FramerError::FrameTooLarge(e) => XRPLWebsocketException::FrameTooLarge(e), - FramerError::Utf8(e) => XRPLWebsocketException::Utf8(e), - FramerError::HttpHeader(_) => XRPLWebsocketException::HttpHeader, - FramerError::WebSocket(e) => XRPLWebsocketException::WebSocket(e), - FramerError::Disconnected => XRPLWebsocketException::Disconnected, - FramerError::RxBufferTooSmall(e) => XRPLWebsocketException::RxBufferTooSmall(e), + FramerError::Io(e) => XRPLWebSocketException::Io(format!("{:?}", e)), + FramerError::FrameTooLarge(e) => XRPLWebSocketException::FrameTooLarge(e), + FramerError::Utf8(e) => XRPLWebSocketException::Utf8(e), + FramerError::HttpHeader(_) => XRPLWebSocketException::HttpHeader, + FramerError::WebSocket(e) => XRPLWebSocketException::WebSocket(e), + FramerError::Disconnected => XRPLWebSocketException::Disconnected, + FramerError::RxBufferTooSmall(e) => XRPLWebSocketException::RxBufferTooSmall(e), } } } #[cfg(all(feature = "websocket", not(feature = "std")))] -impl EmbeddedIoError for XRPLWebsocketException { +impl EmbeddedIoError for XRPLWebSocketException { fn kind(&self) -> ErrorKind { match self { - XRPLWebsocketException::EmbeddedIoError(e) => e.kind(), + XRPLWebSocketException::EmbeddedIoError(e) => e.kind(), _ => ErrorKind::Other, } } } #[cfg(feature = "std")] -impl alloc::error::Error for XRPLWebsocketException {} +impl alloc::error::Error for XRPLWebSocketException {} diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index 06924e03..96dbcb8a 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -1,16 +1,12 @@ -use crate::{ - models::{requests::XRPLRequest, results::XRPLResponse}, - Err, -}; +use crate::models::{requests::XRPLRequest, results::XRPLResponse}; #[cfg(feature = "std")] use alloc::string::String; #[cfg(not(feature = "std"))] use alloc::string::ToString; -use anyhow::Result; #[cfg(not(feature = "std"))] -use core::fmt::Display; +use embedded_io_async::Error; #[cfg(not(feature = "std"))] -use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; +use embedded_io_async::{Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; #[cfg(feature = "std")] use futures::{Sink, SinkExt, Stream, StreamExt}; @@ -29,34 +25,31 @@ pub use _no_std::*; #[cfg(all(feature = "websocket", feature = "std"))] pub use _std::*; +use super::exceptions::{XRPLClientException, XRPLClientResult}; + pub struct WebSocketOpen; pub struct WebSocketClosed; #[allow(async_fn_in_trait)] pub trait XRPLAsyncWebsocketIO { - async fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> Result<()>; + async fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> XRPLClientResult<()>; - async fn xrpl_receive(&mut self) -> Result>>; + async fn xrpl_receive(&mut self) -> XRPLClientResult>>; } #[cfg(not(feature = "std"))] -impl XRPLAsyncWebsocketIO for T -where - ::Error: Display, -{ - async fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> Result<()> { - let message = match serde_json::to_string(&message) { - Ok(message) => message, - Err(error) => return Err!(error), - }; +impl XRPLAsyncWebsocketIO for T { + async fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> XRPLClientResult<()> { + let message = serde_json::to_string(&message)?; let message_buffer = message.as_bytes(); - match self.write(message_buffer).await { - Ok(_) => Ok(()), - Err(e) => Err!(e), - } + self.write(message_buffer) + .await + .map_err(|e| XRPLWebSocketException::EmbeddedIoError(e.kind()))?; + + Ok(()) } - async fn xrpl_receive(&mut self) -> Result>> { + async fn xrpl_receive(&mut self) -> XRPLClientResult>> { let mut buffer = [0; 1024]; loop { match self.read(&mut buffer).await { @@ -65,20 +58,16 @@ where if u_size == 0 { continue; } - let response_str = match core::str::from_utf8(&buffer[..u_size]) { - Ok(response_str) => response_str, - Err(error) => { - return Err!(XRPLWebsocketException::::Utf8(error)) - } - }; + let response_str = core::str::from_utf8(&buffer[..u_size]) + .map_err(|e| XRPLWebSocketException::Utf8(e))?; self.handle_message(response_str.to_string()).await?; let message = self.pop_message().await; - match serde_json::from_str(&message) { - Ok(response) => return Ok(response), - Err(error) => return Err!(error), - } + + return Ok(serde_json::from_str(&message)?); + } + Err(error) => { + return Err(XRPLWebSocketException::EmbeddedIoError(error.kind()).into()) } - Err(error) => return Err!(error), } } } @@ -87,30 +76,26 @@ where #[cfg(feature = "std")] impl XRPLAsyncWebsocketIO for T where - T: Stream> + Sink + MessageHandler + Unpin, + T: Stream> + + Sink + + MessageHandler + + Unpin, { - async fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> Result<()> { - let message = match serde_json::to_string(&message) { - Ok(message) => message, - Err(error) => return Err!(error), - }; - match self.send(message).await { - Ok(()) => Ok(()), - Err(error) => Err!(error), - } + async fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> XRPLClientResult<()> { + let message = serde_json::to_string(&message)?; + + self.send(message).await } - async fn xrpl_receive(&mut self) -> Result>> { + async fn xrpl_receive(&mut self) -> XRPLClientResult>> { match self.next().await { Some(Ok(item)) => { self.handle_message(item).await?; let message = self.pop_message().await; - match serde_json::from_str(&message) { - Ok(response) => Ok(response), - Err(error) => Err!(error), - } + + Ok(serde_json::from_str(&message)?) } - Some(Err(error)) => Err!(error), + Some(Err(error)) => Err(error), None => Ok(None), } } diff --git a/src/asynch/clients/websocket/websocket_base.rs b/src/asynch/clients/websocket/websocket_base.rs index b8bc4307..d4a60b2f 100644 --- a/src/asynch/clients/websocket/websocket_base.rs +++ b/src/asynch/clients/websocket/websocket_base.rs @@ -1,12 +1,12 @@ use alloc::string::{String, ToString}; -use anyhow::Result; use embassy_sync::{blocking_mutex::raw::RawMutex, channel::Channel}; use futures::channel::oneshot::{self, Receiver, Sender}; use hashbrown::HashMap; use serde_json::Value; -use super::exceptions::XRPLWebsocketException; -use crate::Err; +use crate::asynch::clients::exceptions::XRPLClientResult; + +use super::exceptions::XRPLWebSocketException; const _MAX_CHANNEL_MSG_CNT: usize = 10; @@ -45,9 +45,9 @@ where pub trait MessageHandler { /// Setup an empty future for a request. async fn setup_request_future(&mut self, id: String); - async fn handle_message(&mut self, message: String) -> Result<()>; + async fn handle_message(&mut self, message: String) -> XRPLClientResult<()>; async fn pop_message(&mut self) -> String; - async fn try_recv_request(&mut self, id: String) -> Result>; + async fn try_recv_request(&mut self, id: String) -> XRPLClientResult>; } impl MessageHandler for WebsocketBase @@ -63,27 +63,23 @@ where self.request_senders.insert(id, sender); } - async fn handle_message(&mut self, message: String) -> Result<()> { - let message_value: Value = match serde_json::from_str(&message) { - Ok(value) => value, - Err(error) => return Err!(error), - }; + async fn handle_message(&mut self, message: String) -> XRPLClientResult<()> { + let message_value: Value = serde_json::from_str(&message)?; let id = match message_value.get("id") { Some(id) => match id.as_str() { Some(id) => id.to_string(), - None => return Err!(XRPLWebsocketException::::InvalidMessage), + None => return Err(XRPLWebSocketException::InvalidMessage.into()), }, None => String::new(), }; if let Some(_receiver) = self.pending_requests.get(&id) { let sender = match self.request_senders.remove(&id) { Some(sender) => sender, - None => return Err!(XRPLWebsocketException::::MissingRequestSender), - }; - match sender.send(message) { - Ok(()) => (), - Err(error) => return Err!(error), + None => return Err(XRPLWebSocketException::MissingRequestSender.into()), }; + sender + .send(message) + .map_err(|e| XRPLWebSocketException::MessageChannelError(e))?; } else { self.messages.send(message).await; } @@ -94,10 +90,10 @@ where self.messages.receive().await } - async fn try_recv_request(&mut self, id: String) -> Result> { + async fn try_recv_request(&mut self, id: String) -> XRPLClientResult> { let fut = match self.pending_requests.get_mut(&id) { Some(fut) => fut, - None => return Err!(XRPLWebsocketException::::MissingRequestReceiver), + None => return Err(XRPLWebSocketException::MissingRequestReceiver.into()), }; match fut.try_recv() { Ok(Some(message)) => { @@ -106,9 +102,7 @@ where Ok(Some(message)) } Ok(None) => Ok(None), - Err(error) => { - Err!(error) - } + Err(error) => Err(XRPLWebSocketException::Canceled(error).into()), } } } diff --git a/src/asynch/exceptions.rs b/src/asynch/exceptions.rs new file mode 100644 index 00000000..d37a9869 --- /dev/null +++ b/src/asynch/exceptions.rs @@ -0,0 +1,79 @@ +use thiserror_no_std::Error; + +#[cfg(any(feature = "json-rpc", feature = "websocket"))] +use super::clients::exceptions::XRPLClientException; +#[cfg(feature = "helpers")] +use super::{ + transaction::exceptions::{ + XRPLSignTransactionException, XRPLSubmitAndWaitException, XRPLTransactionHelperException, + }, + wallet::exceptions::XRPLFaucetException, +}; +#[cfg(feature = "helpers")] +use crate::{ + core::exceptions::XRPLCoreException, + models::transactions::exceptions::XRPLTransactionFieldException, + transaction::exceptions::XRPLMultisignException, utils::exceptions::XRPLUtilsException, + wallet::exceptions::XRPLWalletException, +}; +use crate::{models::XRPLModelException, XRPLSerdeJsonError}; + +pub type XRPLHelperResult = core::result::Result; + +#[derive(Debug, Error)] +pub enum XRPLHelperException { + #[cfg(feature = "helpers")] + #[error("XRPL Wallet error: {0}")] + XRPLWalletError(#[from] XRPLWalletException), + #[cfg(feature = "helpers")] + #[error("XRPL Faucet error: {0}")] + XRPLFaucetError(#[from] XRPLFaucetException), + #[cfg(feature = "helpers")] + #[error("XRPL Transaction Helper error: {0}")] + XRPLTransactionHelperError(#[from] XRPLTransactionHelperException), + #[error("XRPL Model error: {0}")] + XRPLModelError(#[from] XRPLModelException), + #[cfg(feature = "helpers")] + #[error("XRPL Core error: {0}")] + XRPLCoreError(#[from] XRPLCoreException), + #[cfg(feature = "helpers")] + #[error("XRPL Transaction Field error: {0}")] + XRPLTransactionFieldError(#[from] XRPLTransactionFieldException), + #[cfg(feature = "helpers")] + #[error("XRPL Utils error: {0}")] + XRPLUtilsError(#[from] XRPLUtilsException), + #[cfg(feature = "helpers")] + #[error("XRPL MultiSign error: {0}")] + XRPLMultiSignError(#[from] XRPLMultisignException), + #[cfg(any(feature = "json-rpc", feature = "websocket"))] + #[error("XRPL Client error: {0}")] + XRPLClientError(#[from] XRPLClientException), + #[error("serde_json error: {0}")] + XRPLSerdeJsonError(#[from] XRPLSerdeJsonError), + #[error("From hex error: {0}")] + FromHexError(#[from] hex::FromHexError), +} + +impl From for XRPLHelperException { + fn from(error: serde_json::Error) -> Self { + XRPLHelperException::XRPLSerdeJsonError(XRPLSerdeJsonError::SerdeJsonError(error)) + } +} + +#[cfg(feature = "helpers")] +impl From for XRPLHelperException { + fn from(error: XRPLSignTransactionException) -> Self { + XRPLHelperException::XRPLTransactionHelperError( + XRPLTransactionHelperException::XRPLSignTransactionError(error), + ) + } +} + +#[cfg(feature = "helpers")] +impl From for XRPLHelperException { + fn from(error: XRPLSubmitAndWaitException) -> Self { + XRPLHelperException::XRPLTransactionHelperError( + XRPLTransactionHelperException::XRPLSubmitAndWaitError(error), + ) + } +} diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs index 61eea2ea..d0a87b15 100644 --- a/src/asynch/ledger/mod.rs +++ b/src/asynch/ledger/mod.rs @@ -1,7 +1,6 @@ use core::{cmp::min, convert::TryInto}; use alloc::string::ToString; -use anyhow::Result; use crate::models::{ requests::{fee::Fee, ledger::Ledger}, @@ -9,9 +8,11 @@ use crate::models::{ XRPAmount, }; -use super::clients::XRPLAsyncClient; +use super::{clients::XRPLAsyncClient, exceptions::XRPLHelperResult}; -pub async fn get_latest_validated_ledger_sequence(client: &impl XRPLAsyncClient) -> Result { +pub async fn get_latest_validated_ledger_sequence( + client: &impl XRPLAsyncClient, +) -> XRPLHelperResult { let ledger_response = client .request( Ledger::new( @@ -35,7 +36,9 @@ pub async fn get_latest_validated_ledger_sequence(client: &impl XRPLAsyncClient) .ledger_index) } -pub async fn get_latest_open_ledger_sequence(client: &impl XRPLAsyncClient) -> Result { +pub async fn get_latest_open_ledger_sequence( + client: &impl XRPLAsyncClient, +) -> XRPLHelperResult { let ledger_response = client .request( Ledger::new( @@ -69,24 +72,20 @@ pub async fn get_fee( client: &impl XRPLAsyncClient, max_fee: Option, fee_type: Option, -) -> Result> { +) -> XRPLHelperResult> { let fee_request = Fee::new(None); - match client.request(fee_request.into()).await { - Ok(response) => { - let drops = response.try_into_result::>()?.drops; - let fee = match_fee_type(fee_type, drops)?; + let response = client.request(fee_request.into()).await?; + let drops = response.try_into_result::>()?.drops; + let fee = match_fee_type(fee_type, drops)?; - if let Some(max_fee) = max_fee { - Ok(XRPAmount::from(min(max_fee, fee).to_string())) - } else { - Ok(XRPAmount::from(fee.to_string())) - } - } - Err(err) => Err(err), + if let Some(max_fee) = max_fee { + Ok(XRPAmount::from(min(max_fee, fee).to_string())) + } else { + Ok(XRPAmount::from(fee.to_string())) } } -fn match_fee_type(fee_type: Option, drops: Drops<'_>) -> Result { +fn match_fee_type(fee_type: Option, drops: Drops<'_>) -> XRPLHelperResult { match fee_type { None | Some(FeeType::Open) => Ok(drops.open_ledger_fee.try_into()?), Some(FeeType::Minimum) => Ok(drops.minimum_fee.try_into()?), diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index 8db200d2..1138dc0d 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,40 +1,16 @@ -#[cfg(all( - feature = "account-helpers", - any(feature = "websocket", feature = "json-rpc") -))] +pub mod exceptions; + +#[cfg(feature = "helpers")] pub mod account; #[cfg(any(feature = "websocket", feature = "json-rpc"))] pub mod clients; -#[cfg(all( - feature = "ledger-helpers", - any(feature = "websocket", feature = "json-rpc") -))] +#[cfg(feature = "helpers")] pub mod ledger; -#[cfg(all( - feature = "transaction-helpers", - any(feature = "websocket", feature = "json-rpc") -))] +#[cfg(feature = "helpers")] pub mod transaction; -#[cfg(all( - feature = "wallet-helpers", - any(feature = "websocket", feature = "json-rpc") -))] +#[cfg(feature = "helpers")] pub mod wallet; -use thiserror_no_std::Error; - -#[derive(Error, Debug)] -pub enum XRPLFaucetException { - #[error( - "Cannot fund an account on an issuing chain. Accounts must be created via the bridge." - )] - CannotFundSidechainAccount, - #[error("Cannot derive a faucet URL from the client host.")] - CannotDeriveFaucetUrl, - #[error("Funding request timed out.")] - FundingTimeout, -} - #[allow(unused_imports)] #[allow(clippy::needless_return)] async fn wait_seconds(seconds: u64) { diff --git a/src/asynch/transaction/exceptions.rs b/src/asynch/transaction/exceptions.rs index 49ccb3fc..5fe4922f 100644 --- a/src/asynch/transaction/exceptions.rs +++ b/src/asynch/transaction/exceptions.rs @@ -1,40 +1,45 @@ use core::num::ParseIntError; -use alloc::borrow::Cow; +use alloc::string::String; use thiserror_no_std::Error; -use crate::models::XRPAmount; - #[derive(Error, Debug, PartialEq)] -pub enum XRPLTransactionException<'a> { +#[non_exhaustive] +pub enum XRPLTransactionHelperException { #[error("Fee of {0:?} Drops is much higher than a typical XRP transaction fee. This may be a mistake. If intentional, please use `check_fee = false`")] - FeeUnusuallyHigh(XRPAmount<'a>), + FeeUnusuallyHigh(String), #[error("Unable to parse rippled version: {0}")] ParseRippledVersionError(ParseIntError), #[error("Invalid rippled version: {0}")] - InvalidRippledVersion(Cow<'a, str>), + InvalidRippledVersion(String), + #[error("XRPL Sign Transaction error: {0}")] + XRPLSignTransactionError(#[from] XRPLSignTransactionException), + #[error("XRPL Submit and Wait error: {0}")] + XRPLSubmitAndWaitError(#[from] XRPLSubmitAndWaitException), } #[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLSignTransactionException<'a> { +#[non_exhaustive] +pub enum XRPLSignTransactionException { #[error("{0:?} value does not match X-Address tag")] - TagFieldMismatch(&'a str), + TagFieldMismatch(String), #[error("Fee value of {0:?} is likely entered incorrectly, since it is much larger than the typical XRP transaction cost. If this is intentional, use `check_fee=Some(false)`.")] - FeeTooHigh(Cow<'a, str>), + FeeTooHigh(String), #[error("Wallet is required to sign transaction")] WalletRequired, } #[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLSubmitAndWaitException<'a> { +#[non_exhaustive] +pub enum XRPLSubmitAndWaitException { #[error("Transaction submission failed: {0}")] - SubmissionFailed(Cow<'a, str>), + SubmissionFailed(String), #[error("The latest validated ledger sequence {validated_ledger_sequence} is greater than the LastLedgerSequence {last_ledger_sequence} in the Transaction. Prelim result: {prelim_result}")] SubmissionTimeout { last_ledger_sequence: u32, validated_ledger_sequence: u32, - prelim_result: Cow<'a, str>, + prelim_result: String, }, #[error("Expected field in the transaction metadata: {0}")] - ExpectedFieldInTxMeta(Cow<'a, str>), + ExpectedFieldInTxMeta(String), } diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index b65f6acb..5f0d3b53 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -1,6 +1,7 @@ pub mod exceptions; mod submit_and_wait; +use bigdecimal::{BigDecimal, RoundingMode}; pub use submit_and_wait::*; use crate::{ @@ -27,28 +28,27 @@ use crate::{ get_transaction_field_value, set_transaction_field_value, validate_transaction_has_field, }, wallet::Wallet, - Err, }; +use alloc::string::String; use alloc::string::ToString; use alloc::vec::Vec; use alloc::{borrow::Cow, vec}; -use alloc::string::String; -use anyhow::Result; use core::convert::TryInto; use core::fmt::Debug; -use exceptions::XRPLTransactionException; -use rust_decimal::Decimal; +use exceptions::XRPLTransactionHelperException; use serde::Serialize; use serde::{de::DeserializeOwned, Deserialize}; use strum::IntoEnumIterator; +use super::exceptions::XRPLHelperResult; + const OWNER_RESERVE: &str = "2000000"; // 2 XRP const RESTRICTED_NETWORKS: u16 = 1024; const REQUIRED_NETWORKID_VERSION: &str = "1.11.0"; const LEDGER_OFFSET: u8 = 20; -pub fn sign<'a, T, F>(transaction: &mut T, wallet: &Wallet, multisign: bool) -> Result<()> +pub fn sign<'a, T, F>(transaction: &mut T, wallet: &Wallet, multisign: bool) -> XRPLHelperResult<()> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, @@ -56,14 +56,8 @@ where if multisign { let serialized_for_signing = encode_for_multisigning(transaction, wallet.classic_address.clone().into())?; - let serialized_bytes = match hex::decode(serialized_for_signing) { - Ok(bytes) => bytes, - Err(e) => return Err!(e), - }; - let signature = match keypairs_sign(&serialized_bytes, &wallet.private_key) { - Ok(signature) => signature, - Err(e) => return Err!(e), - }; + let serialized_bytes = hex::decode(serialized_for_signing)?; + let signature = keypairs_sign(&serialized_bytes, &wallet.private_key)?; let signer = Signer::new( wallet.classic_address.clone().into(), signature.into(), @@ -75,14 +69,8 @@ where } else { prepare_transaction(transaction, wallet)?; let serialized_for_signing = encode_for_signing(transaction)?; - let serialized_bytes = match hex::decode(serialized_for_signing) { - Ok(bytes) => bytes, - Err(e) => return Err!(e), - }; - let signature = match keypairs_sign(&serialized_bytes, &wallet.private_key) { - Ok(signature) => signature, - Err(e) => return Err!(e), - }; + let serialized_bytes = hex::decode(serialized_for_signing)?; + let signature = keypairs_sign(&serialized_bytes, &wallet.private_key)?; transaction.get_mut_common_fields().txn_signature = Some(signature.into()); Ok(()) @@ -95,7 +83,7 @@ pub async fn sign_and_submit<'a, 'b, T, F, C>( wallet: &Wallet, autofill: bool, check_fee: bool, -) -> Result> +) -> XRPLHelperResult> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, @@ -116,7 +104,7 @@ pub async fn autofill<'a, 'b, F, T, C>( transaction: &mut T, client: &'b C, signers_count: Option, -) -> Result<()> +) -> XRPLHelperResult<()> where T: Transaction<'a, F> + Model + Clone, F: IntoEnumIterator + Serialize + Debug + PartialEq, @@ -149,7 +137,7 @@ pub async fn autofill_and_sign<'a, 'b, T, F, C>( client: &'b C, wallet: &Wallet, check_fee: bool, -) -> Result<()> +) -> XRPLHelperResult<()> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, @@ -164,7 +152,7 @@ where Ok(()) } -pub async fn submit<'a, T, F, C>(transaction: &T, client: &C) -> Result> +pub async fn submit<'a, T, F, C>(transaction: &T, client: &C) -> XRPLHelperResult> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, @@ -174,20 +162,15 @@ where let txn_blob = encode(transaction)?; let req = Submit::new(None, txn_blob.into(), None); let res = client.request(req.into()).await?; - match res.try_into_result::>() { - Ok(value) => { - let submit_result = value; - Ok(submit_result) - } - Err(e) => Err!(e), - } + + Ok(res.try_into_result::>()?) } pub async fn calculate_fee_per_transaction_type<'a, 'b, 'c, T, F, C>( transaction: &T, client: Option<&'b C>, signers_count: Option, -) -> Result> +) -> XRPLHelperResult> where T: Transaction<'a, F>, F: IntoEnumIterator + Serialize + Debug + PartialEq, @@ -221,17 +204,21 @@ where _ => net_fee.clone(), }; } - let mut base_fee_decimal: Decimal = base_fee.try_into()?; + let mut base_fee_decimal: BigDecimal = base_fee.try_into()?; if let Some(signers_count) = signers_count { - let net_fee_decimal: Decimal = net_fee.try_into()?; - let signer_count_fee_decimal: Decimal = (1 + signers_count).into(); + let net_fee_decimal: BigDecimal = net_fee.try_into()?; + let signer_count_fee_decimal: BigDecimal = (1 + signers_count).into(); base_fee_decimal += &(net_fee_decimal * signer_count_fee_decimal); } - Ok(base_fee_decimal.ceil().into()) + Ok(base_fee_decimal + .with_scale_round(0, RoundingMode::Down) + .into()) } -async fn get_owner_reserve_from_response(client: &impl XRPLAsyncClient) -> Result> { +async fn get_owner_reserve_from_response( + client: &impl XRPLAsyncClient, +) -> XRPLHelperResult> { let owner_reserve_response = client.request(ServerState::new(None).into()).await?; match owner_reserve_response .try_into_result::>()? @@ -239,14 +226,14 @@ async fn get_owner_reserve_from_response(client: &impl XRPLAsyncClient) -> Resul .validated_ledger { Some(validated_ledger) => Ok(validated_ledger.reserve_base), - None => Err!(XRPLModelException::MissingField("validated_ledger")), + None => Err(XRPLModelException::MissingField("validated_ledger".to_string()).into()), } } fn calculate_base_fee_for_escrow_finish<'a: 'b, 'b>( net_fee: XRPAmount<'a>, fulfillment: Option>, -) -> Result> { +) -> XRPLHelperResult> { if let Some(fulfillment) = fulfillment { calculate_based_on_fulfillment(fulfillment, net_fee) } else { @@ -257,18 +244,20 @@ fn calculate_base_fee_for_escrow_finish<'a: 'b, 'b>( fn calculate_based_on_fulfillment<'a>( fulfillment: Cow, net_fee: XRPAmount<'_>, -) -> Result> { +) -> XRPLHelperResult> { let fulfillment_bytes: Vec = fulfillment.chars().map(|c| c as u8).collect(); let net_fee_f64: f64 = net_fee.try_into()?; let base_fee_string = (net_fee_f64 * (33.0 + (fulfillment_bytes.len() as f64 / 16.0))).to_string(); let base_fee: XRPAmount = base_fee_string.into(); - let base_fee_decimal: Decimal = base_fee.try_into()?; + let base_fee_decimal: BigDecimal = base_fee.try_into()?; - Ok(base_fee_decimal.ceil().into()) + Ok(base_fee_decimal + .with_scale_round(0, RoundingMode::Down) + .into()) } -fn txn_needs_network_id(common_fields: CommonFields<'_>) -> Result { +fn txn_needs_network_id(common_fields: CommonFields<'_>) -> XRPLHelperResult { let is_higher_restricted_networks = if let Some(network_id) = common_fields.network_id { network_id > RESTRICTED_NETWORKS as u32 } else { @@ -280,17 +269,14 @@ fn txn_needs_network_id(common_fields: CommonFields<'_>) -> Result { Ok(is_not_later_rippled_version) => { Ok(is_higher_restricted_networks && is_not_later_rippled_version) } - Err(e) => Err!(e), + Err(e) => Err(e.into()), } } else { Ok(false) } } -fn is_not_later_rippled_version<'a>( - source: String, - target: String, -) -> Result> { +fn is_not_later_rippled_version<'a>(source: String, target: String) -> XRPLHelperResult { if source == target { Ok(true) } else { @@ -305,18 +291,18 @@ fn is_not_later_rippled_version<'a>( let (source_major, source_minor) = ( source_decomp[0] .parse::() - .map_err(XRPLTransactionException::ParseRippledVersionError)?, + .map_err(XRPLTransactionHelperException::ParseRippledVersionError)?, source_decomp[1] .parse::() - .map_err(XRPLTransactionException::ParseRippledVersionError)?, + .map_err(XRPLTransactionHelperException::ParseRippledVersionError)?, ); let (target_major, target_minor) = ( target_decomp[0] .parse::() - .map_err(XRPLTransactionException::ParseRippledVersionError)?, + .map_err(XRPLTransactionHelperException::ParseRippledVersionError)?, target_decomp[1] .parse::() - .map_err(XRPLTransactionException::ParseRippledVersionError)?, + .map_err(XRPLTransactionHelperException::ParseRippledVersionError)?, ); if source_major != target_major { Ok(source_major < target_major) @@ -333,19 +319,23 @@ fn is_not_later_rippled_version<'a>( .collect::>(); let source_patch_version = source_patch[0] .parse::() - .map_err(XRPLTransactionException::ParseRippledVersionError)?; + .map_err(XRPLTransactionHelperException::ParseRippledVersionError)?; let target_patch_version = target_patch[0] .parse::() - .map_err(XRPLTransactionException::ParseRippledVersionError)?; + .map_err(XRPLTransactionHelperException::ParseRippledVersionError)?; if source_patch_version != target_patch_version { Ok(source_patch_version < target_patch_version) } else if source_patch.len() != target_patch.len() { Ok(source_patch.len() < target_patch.len()) } else if source_patch.len() == 2 { if source_patch[1].chars().next().ok_or( - XRPLTransactionException::InvalidRippledVersion("source patch version".into()), + XRPLTransactionHelperException::InvalidRippledVersion( + "source patch version".into(), + ), )? != target_patch[1].chars().next().ok_or( - XRPLTransactionException::InvalidRippledVersion("target patch version".into()), + XRPLTransactionHelperException::InvalidRippledVersion( + "target patch version".into(), + ), )? { Ok(source_patch[1] < target_patch[1]) } else if source_patch[1].starts_with('b') { @@ -366,7 +356,7 @@ enum AccountFieldType { Destination, } -async fn check_txn_fee<'a, 'b, T, F, C>(transaction: &mut T, client: &'b C) -> Result<()> +async fn check_txn_fee<'a, 'b, T, F, C>(transaction: &mut T, client: &'b C) -> XRPLHelperResult<()> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone, @@ -381,14 +371,13 @@ where .clone() .unwrap_or(XRPAmount::from("0")); if transaction_fee > expected_fee { - return Err!(XRPLSignTransactionException::FeeTooHigh( - transaction_fee.try_into()? - )); + Err(XRPLSignTransactionException::FeeTooHigh(transaction_fee.to_string()).into()) + } else { + Ok(()) } - Ok(()) } -fn prepare_transaction<'a, T, F>(transaction: &mut T, wallet: &Wallet) -> Result<()> +fn prepare_transaction<'a, T, F>(transaction: &mut T, wallet: &Wallet) -> XRPLHelperResult<()> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone, @@ -415,7 +404,7 @@ where fn validate_account_xaddress<'a, T, F>( prepared_transaction: &mut T, account_field: AccountFieldType, -) -> Result<()> +) -> XRPLHelperResult<()> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone, @@ -428,10 +417,13 @@ where } else if name_str == "\"Destination\"" { ("Destination", "DestinationTag") } else { - return Err!(XRPLTransactionFieldException::UnknownAccountField(name_str)); + return Err(XRPLTransactionFieldException::UnknownAccountField( + name_str.to_string(), + ) + .into()); } } - Err(error) => return Err!(error), + Err(error) => return Err(error.into()), }; let account_address = match account_field { AccountFieldType::Account => prepared_transaction.get_common_fields().account.clone(), @@ -443,7 +435,7 @@ where if is_valid_xaddress(&account_address) { let (address, tag, _) = match xaddress_to_classic_address(&account_address) { Ok(t) => t, - Err(error) => return Err!(error), + Err(error) => return Err(error.into()), }; validate_transaction_has_field(prepared_transaction, account_field_name)?; set_transaction_field_value(prepared_transaction, account_field_name, address)?; @@ -452,9 +444,7 @@ where && get_transaction_field_value(prepared_transaction, tag_field_name).unwrap_or(Some(0)) != tag { - Err!(XRPLSignTransactionException::TagFieldMismatch( - tag_field_name - )) + Err(XRPLSignTransactionException::TagFieldMismatch(tag_field_name.to_string()).into()) } else { set_transaction_field_value(prepared_transaction, tag_field_name, tag)?; @@ -465,7 +455,10 @@ where } } -fn convert_to_classic_address<'a, T, F>(transaction: &mut T, field_name: &str) -> Result<()> +fn convert_to_classic_address<'a, T, F>( + transaction: &mut T, + field_name: &str, +) -> XRPLHelperResult<()> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone, @@ -474,9 +467,13 @@ where if is_valid_xaddress(&address) { let classic_address = match xaddress_to_classic_address(&address) { Ok(t) => t.0, - Err(error) => return Err!(error), + Err(error) => return Err(error.into()), }; - set_transaction_field_value(transaction, field_name, classic_address) + Ok(set_transaction_field_value( + transaction, + field_name, + classic_address, + )?) } else { Ok(()) } @@ -487,16 +484,18 @@ where mod test_autofill { use super::autofill; use crate::{ - asynch::clients::{AsyncWebSocketClient, SingleExecutorMutex}, + asynch::{ + clients::{AsyncWebSocketClient, SingleExecutorMutex}, + exceptions::XRPLHelperResult, + }, models::{ transactions::{offer_create::OfferCreate, Transaction}, IssuedCurrencyAmount, XRPAmount, }, }; - use anyhow::Result; #[tokio::test] - async fn test_autofill_txn() -> Result<()> { + async fn test_autofill_txn() -> XRPLHelperResult<()> { let mut txn = OfferCreate::new( "r9mhdWo1NXVZr2pDnCtC1xwxE85kFtSzYR".into(), None, @@ -534,7 +533,7 @@ mod test_autofill { } } -#[cfg(all(feature = "websocket", feature = "std"))] +#[cfg(all(feature = "json-rpc", feature = "std"))] #[cfg(test)] mod test_sign { use alloc::borrow::Cow; diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs index f7d7825b..dce9f93c 100644 --- a/src/asynch/transaction/submit_and_wait.rs +++ b/src/asynch/transaction/submit_and_wait.rs @@ -1,7 +1,6 @@ use core::fmt::Debug; use alloc::{borrow::Cow, format}; -use anyhow::{Ok, Result}; use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value; use strum::IntoEnumIterator; @@ -9,6 +8,7 @@ use strum::IntoEnumIterator; use crate::{ asynch::{ clients::XRPLAsyncClient, + exceptions::XRPLHelperResult, ledger::get_latest_validated_ledger_sequence, transaction::{ autofill, check_txn_fee, @@ -17,9 +17,8 @@ use crate::{ }, wait_seconds, }, - models::{requests, results, transactions::Transaction, Model}, + models::{requests, results::tx::Tx, transactions::Transaction, Model}, wallet::Wallet, - Err, }; pub async fn submit_and_wait<'a: 'b, 'b, T, F, C>( @@ -28,7 +27,7 @@ pub async fn submit_and_wait<'a: 'b, 'b, T, F, C>( wallet: Option<&Wallet>, check_fee: Option, autofill: Option, -) -> Result> +) -> XRPLHelperResult> where T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone + 'a, @@ -41,7 +40,7 @@ where async fn send_reliable_submission<'a: 'b, 'b, T, F, C>( transaction: &'b mut T, client: &C, -) -> Result> +) -> XRPLHelperResult> where T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone + 'a, @@ -55,7 +54,7 @@ where "{}: {}", prelim_result, submit_response.engine_result_message ); - Err!(XRPLSubmitAndWaitException::SubmissionFailed(message.into())) + Err(XRPLSubmitAndWaitException::SubmissionFailed(message).into()) } else { wait_for_final_transaction_result( tx_hash, @@ -73,7 +72,7 @@ async fn wait_for_final_transaction_result<'a: 'b, 'b, C>( tx_hash: Cow<'a, str>, client: &C, last_ledger_sequence: u32, -) -> Result> +) -> XRPLHelperResult> where C: XRPLAsyncClient, { @@ -95,28 +94,31 @@ where if error == "txnNotFound" { continue; } else { - return Err!(XRPLSubmitAndWaitException::SubmissionFailed( + return Err(XRPLSubmitAndWaitException::SubmissionFailed( format!("{}: {}", error, response.error_message.unwrap_or("".into())) - .into() - )); + .into(), + ) + .into()); } } else { - let opt_result = response.try_into_opt_result::()?; + let opt_result = response.try_into_opt_result::()?; let validated = opt_result.try_get_typed("validated")?; if validated { let result = opt_result.try_into_result()?; let return_code = match result.meta.get("TransactionResult") { Some(Value::String(s)) => s, _ => { - return Err!(XRPLSubmitAndWaitException::ExpectedFieldInTxMeta( - "TransactionResult".into() - )); + return Err(XRPLSubmitAndWaitException::ExpectedFieldInTxMeta( + "TransactionResult".into(), + ) + .into()); } }; if return_code != "tesSUCCESS" { - return Err!(XRPLSubmitAndWaitException::SubmissionFailed( - return_code.into() - )); + return Err(XRPLSubmitAndWaitException::SubmissionFailed( + return_code.into(), + ) + .into()); } else { return Ok(result); } @@ -124,9 +126,10 @@ where } } } - Err!(XRPLSubmitAndWaitException::SubmissionFailed( - "Transaction not included in ledger".into() - )) + Err( + XRPLSubmitAndWaitException::SubmissionFailed("Transaction not included in ledger".into()) + .into(), + ) } async fn get_signed_transaction<'a, T, F, C>( @@ -135,7 +138,7 @@ async fn get_signed_transaction<'a, T, F, C>( wallet: Option<&Wallet>, do_check_fee: Option, do_autofill: Option, -) -> Result<()> +) -> XRPLHelperResult<()> where T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone, @@ -161,14 +164,15 @@ where sign(transaction, wallet, false) } } else { - Err!(XRPLSignTransactionException::WalletRequired) + Err(XRPLSignTransactionException::WalletRequired.into()) } } #[cfg(all( feature = "std", feature = "json-rpc", - feature = "wallet-helpers", + feature = "helpers", + feature = "models", feature = "tokio-rt" ))] #[cfg(test)] diff --git a/src/asynch/wallet/exceptions.rs b/src/asynch/wallet/exceptions.rs new file mode 100644 index 00000000..afbcca42 --- /dev/null +++ b/src/asynch/wallet/exceptions.rs @@ -0,0 +1,14 @@ +use thiserror_no_std::Error; + +#[derive(Error, PartialEq, Debug)] +#[non_exhaustive] +pub enum XRPLFaucetException { + #[error( + "Cannot fund an account on an issuing chain. Accounts must be created via the bridge." + )] + CannotFundSidechainAccount, + #[error("Cannot derive a faucet URL from the client host.")] + CannotDeriveFaucetUrl, + #[error("Funding request timed out.")] + FundingTimeout, +} diff --git a/src/asynch/wallet/mod.rs b/src/asynch/wallet/mod.rs index 39f2d197..419f3a21 100644 --- a/src/asynch/wallet/mod.rs +++ b/src/asynch/wallet/mod.rs @@ -1,17 +1,19 @@ +pub mod exceptions; + use alloc::borrow::Cow; -use anyhow::Result; +use exceptions::XRPLFaucetException; use url::Url; use crate::{ - asynch::{account::get_next_valid_seq_number, wait_seconds, XRPLFaucetException}, + asynch::{account::get_next_valid_seq_number, wait_seconds}, models::{requests::FundFaucet, XRPAmount}, wallet::Wallet, - Err, }; use super::{ account::get_xrp_balance, clients::{XRPLClient, XRPLFaucet}, + exceptions::XRPLHelperResult, }; const TIMEOUT_SECS: u8 = 40; @@ -22,17 +24,14 @@ pub async fn generate_faucet_wallet<'a, C>( faucet_host: Option, usage_context: Option>, user_agent: Option>, -) -> Result +) -> XRPLHelperResult where C: XRPLFaucet + XRPLClient, { let faucet_url = get_faucet_url(client, faucet_host)?; let wallet = match wallet { Some(wallet) => wallet, - None => match Wallet::create(None) { - Ok(wallet) => wallet, - Err(error) => return Err!(error), - }, + None => Wallet::create(None)?, }; let address = &wallet.classic_address; let starting_balance = check_balance(client, address.into()).await; @@ -65,14 +64,14 @@ where } } - Err!(XRPLFaucetException::FundingTimeout) + Err(XRPLFaucetException::FundingTimeout.into()) } -pub fn get_faucet_url(client: &C, url: Option) -> Result +pub fn get_faucet_url(client: &C, url: Option) -> XRPLHelperResult where C: XRPLFaucet + XRPLClient, { - client.get_faucet_url(url) + Ok(client.get_faucet_url(url)?) } async fn check_balance<'a: 'b, 'b, C>(client: &C, address: Cow<'a, str>) -> XRPAmount<'b> @@ -90,7 +89,7 @@ async fn fund_wallet<'a: 'b, 'b, C>( address: Cow<'a, str>, usage_context: Option>, user_agent: Option>, -) -> Result<()> +) -> XRPLHelperResult<()> where C: XRPLFaucet + XRPLClient, { diff --git a/src/clients/mod.rs b/src/clients/mod.rs index 03a932dc..86af3ab2 100644 --- a/src/clients/mod.rs +++ b/src/clients/mod.rs @@ -1,33 +1,29 @@ -use anyhow::Result; - use crate::{ - asynch::clients::{CommonFields, XRPLClient}, + asynch::clients::{exceptions::XRPLClientResult, CommonFields, XRPLClient}, models::{requests::XRPLRequest, results::XRPLResponse}, }; -pub use crate::asynch::clients::{SingleExecutorMutex, XRPLFaucet}; +pub use crate::asynch::clients::SingleExecutorMutex; pub trait XRPLSyncClient: XRPLClient { - fn request<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result>; + fn request<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> XRPLClientResult>; - fn get_common_fields(&self) -> Result>; + fn get_common_fields(&self) -> XRPLClientResult>; } #[cfg(all(feature = "json-rpc", feature = "std"))] pub mod json_rpc { - use anyhow::Result; use tokio::runtime::Runtime; use url::Url; + #[cfg(feature = "helpers")] + use crate::{asynch::clients::XRPLFaucet, models::requests::FundFaucet}; use crate::{ asynch::clients::{ - AsyncJsonRpcClient, CommonFields, XRPLAsyncClient, XRPLClient, XRPLFaucet, - }, - models::{ - requests::{FundFaucet, XRPLRequest}, - results::XRPLResponse, + exceptions::XRPLClientResult, AsyncJsonRpcClient, CommonFields, XRPLAsyncClient, + XRPLClient, }, - Err, + models::{requests::XRPLRequest, results::XRPLResponse}, }; use super::XRPLSyncClient; @@ -43,7 +39,7 @@ pub mod json_rpc { async fn request_impl<'a: 'b, 'b>( &self, request: XRPLRequest<'a>, - ) -> Result> { + ) -> XRPLClientResult> { self.0.request_impl(request).await } @@ -57,23 +53,31 @@ pub mod json_rpc { } impl XRPLSyncClient for JsonRpcClient { - fn request<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result> { + fn request<'a: 'b, 'b>( + &self, + request: XRPLRequest<'a>, + ) -> XRPLClientResult> { match Runtime::new() { Ok(rt) => rt.block_on(self.0.request_impl(request)), - Err(e) => Err!(e), + Err(e) => Err(e.into()), } } - fn get_common_fields(&self) -> Result> { + fn get_common_fields(&self) -> XRPLClientResult> { match Runtime::new() { Ok(rt) => rt.block_on(self.0.get_common_fields()), - Err(e) => Err!(e), + Err(e) => Err(e.into()), } } } + #[cfg(feature = "helpers")] impl XRPLFaucet for JsonRpcClient { - async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()> { + async fn request_funding( + &self, + url: Option, + request: FundFaucet<'_>, + ) -> XRPLClientResult<()> { self.0.request_funding(url, request).await } } @@ -81,17 +85,15 @@ pub mod json_rpc { #[cfg(all(feature = "json-rpc", not(feature = "std")))] pub mod json_rpc { - use anyhow::Result; use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_nal_async::{Dns, TcpConnect}; use url::Url; + #[cfg(feature = "helpers")] + use crate::{asynch::clients::XRPLFaucet, models::requests::FundFaucet}; use crate::{ - asynch::clients::{AsyncJsonRpcClient, XRPLClient, XRPLFaucet}, - models::{ - requests::{FundFaucet, XRPLRequest}, - results::XRPLResponse, - }, + asynch::clients::{exceptions::XRPLClientResult, AsyncJsonRpcClient, XRPLClient}, + models::{requests::XRPLRequest, results::XRPLResponse}, }; pub struct JsonRpcClient<'a, const BUF: usize, T, D, M>( @@ -122,7 +124,7 @@ pub mod json_rpc { async fn request_impl<'a: 'b, 'b>( &self, request: XRPLRequest<'a>, - ) -> Result> { + ) -> XRPLClientResult> { self.0.request_impl(request).await } @@ -131,27 +133,31 @@ pub mod json_rpc { } } + #[cfg(feature = "helpers")] impl<'a, const BUF: usize, T, D, M> XRPLFaucet for JsonRpcClient<'a, BUF, T, D, M> where M: RawMutex, T: TcpConnect + 'a, D: Dns + 'a, { - async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()> { + async fn request_funding( + &self, + url: Option, + request: FundFaucet<'_>, + ) -> XRPLClientResult<()> { self.0.request_funding(url, request).await } } } pub trait XRPLSyncWebsocketIO { - fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> Result<()>; + fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> XRPLClientResult<()>; - fn xrpl_receive(&mut self) -> Result>>; + fn xrpl_receive(&mut self) -> XRPLClientResult>>; } #[cfg(all(feature = "websocket", feature = "std"))] pub mod websocket { - use anyhow::Result; use embassy_sync::blocking_mutex::raw::RawMutex; use tokio::runtime::Runtime; use url::Url; @@ -159,10 +165,10 @@ pub mod websocket { use super::{XRPLSyncClient, XRPLSyncWebsocketIO}; use crate::{ asynch::clients::{ - AsyncWebSocketClient, CommonFields, XRPLAsyncClient, XRPLAsyncWebsocketIO, XRPLClient, + exceptions::XRPLClientResult, AsyncWebSocketClient, CommonFields, XRPLAsyncClient, + XRPLAsyncWebsocketIO, XRPLClient, }, models::{requests::XRPLRequest, results::XRPLResponse}, - Err, }; pub use crate::asynch::clients::{WebSocketClosed, WebSocketOpen}; @@ -173,7 +179,7 @@ pub mod websocket { } impl WebSocketClient { - pub fn open(url: Url) -> Result> { + pub fn open(url: Url) -> XRPLClientResult> { match Runtime::new() { Ok(rt) => { let client: AsyncWebSocketClient = @@ -181,7 +187,7 @@ pub mod websocket { Ok(WebSocketClient { inner: client, rt }) } - Err(e) => Err!(e), + Err(e) => Err(e.into()), } } } @@ -197,10 +203,10 @@ pub mod websocket { async fn request_impl<'a: 'b, 'b>( &self, request: XRPLRequest<'a>, - ) -> Result> { + ) -> XRPLClientResult> { match Runtime::new() { Ok(rt) => rt.block_on(self.inner.request_impl(request)), - Err(e) => Err!(e), + Err(e) => Err(e.into()), } } } @@ -209,11 +215,14 @@ pub mod websocket { where M: RawMutex, { - fn request<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result> { + fn request<'a: 'b, 'b>( + &self, + request: XRPLRequest<'a>, + ) -> XRPLClientResult> { self.rt.block_on(self.inner.request_impl(request)) } - fn get_common_fields(&self) -> Result> { + fn get_common_fields(&self) -> XRPLClientResult> { self.rt.block_on(self.inner.get_common_fields()) } } @@ -222,11 +231,11 @@ pub mod websocket { where M: RawMutex, { - fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> Result<()> { + fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> XRPLClientResult<()> { self.rt.block_on(self.inner.xrpl_send(message)) } - fn xrpl_receive(&mut self) -> Result>> { + fn xrpl_receive(&mut self) -> XRPLClientResult>> { self.rt.block_on(self.inner.xrpl_receive()) } } @@ -235,7 +244,6 @@ pub mod websocket { #[cfg(all(feature = "websocket", not(feature = "std")))] pub mod websocket { use super::XRPLSyncWebsocketIO; - use anyhow::Result; use embassy_futures::block_on; use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_io_async::{Read, Write}; @@ -243,7 +251,10 @@ pub mod websocket { use url::Url; use crate::{ - asynch::clients::{AsyncWebSocketClient, WebSocketOpen, XRPLAsyncWebsocketIO, XRPLClient}, + asynch::clients::{ + exceptions::XRPLClientResult, AsyncWebSocketClient, WebSocketOpen, + XRPLAsyncWebsocketIO, XRPLClient, + }, models::{requests::XRPLRequest, results::XRPLResponse}, }; @@ -268,7 +279,7 @@ pub mod websocket { async fn request_impl<'a: 'b, 'b>( &self, request: XRPLRequest<'a>, - ) -> Result> { + ) -> XRPLClientResult> { block_on(self.0.request_impl(request)) } } @@ -280,11 +291,16 @@ pub mod websocket { Rng: RngCore, M: RawMutex, { - fn xrpl_send(&mut self, message: crate::models::requests::XRPLRequest<'_>) -> Result<()> { + fn xrpl_send( + &mut self, + message: crate::models::requests::XRPLRequest<'_>, + ) -> XRPLClientResult<()> { block_on(self.0.xrpl_send(message)) } - fn xrpl_receive(&mut self) -> Result>> { + fn xrpl_receive( + &mut self, + ) -> XRPLClientResult>> { block_on(self.0.xrpl_receive()) } } diff --git a/src/core/addresscodec/exceptions.rs b/src/core/addresscodec/exceptions.rs index 0d3dc68a..1f24a8e9 100644 --- a/src/core/addresscodec/exceptions.rs +++ b/src/core/addresscodec/exceptions.rs @@ -1,65 +1,36 @@ //! General XRPL Address Codec Exception. -use crate::core::binarycodec::exceptions::XRPLBinaryCodecException; -use crate::utils::exceptions::ISOCodeException; -use strum_macros::Display; +use thiserror_no_std::Error; -#[derive(Debug, Clone, PartialEq, Display)] +#[derive(Debug, Clone, PartialEq, Error)] #[non_exhaustive] pub enum XRPLAddressCodecException { + #[error("Invalid XAddress prefix")] InvalidXAddressPrefix, + #[error("Invalid XAddress zero tag")] InvalidXAddressZeroNoTag, + #[error("Invalid XAddress zero remain")] InvalidXAddressZeroRemain, + #[error("Invalid classic address length (length: {length})")] InvalidCAddressIdLength { length: usize }, + #[error("Invalid classic address tag")] InvalidCAddressTag, + #[error("Invalid seed prefix encoding type")] InvalidSeedPrefixEncodingType, + #[error("Invalid encoding prefix length")] InvalidEncodingPrefixLength, + #[error("Invalid classic address value")] InvalidClassicAddressValue, + #[error("Unsupported XAddress")] UnsupportedXAddress, + #[error("Unknown seed encoding")] UnknownSeedEncoding, + #[error("Unknown payload lenght (expected: {expected}, found: {found})")] UnexpectedPayloadLength { expected: usize, found: usize }, - FromHexError, - Base58DecodeError(bs58::decode::Error), - XRPLBinaryCodecError(XRPLBinaryCodecException), - ISOError(ISOCodeException), - SerdeJsonError(serde_json::error::Category), - VecResizeError(alloc::vec::Vec), -} - -impl From for XRPLAddressCodecException { - fn from(err: ISOCodeException) -> Self { - XRPLAddressCodecException::ISOError(err) - } -} - -impl From for XRPLAddressCodecException { - fn from(err: XRPLBinaryCodecException) -> Self { - XRPLAddressCodecException::XRPLBinaryCodecError(err) - } -} - -impl From for XRPLAddressCodecException { - fn from(err: bs58::decode::Error) -> Self { - XRPLAddressCodecException::Base58DecodeError(err) - } -} - -impl From for XRPLAddressCodecException { - fn from(_: hex::FromHexError) -> Self { - XRPLAddressCodecException::FromHexError - } -} - -impl From for XRPLAddressCodecException { - fn from(err: serde_json::Error) -> Self { - XRPLAddressCodecException::SerdeJsonError(err.classify()) - } -} - -impl From> for XRPLAddressCodecException { - fn from(err: alloc::vec::Vec) -> Self { - XRPLAddressCodecException::VecResizeError(err) - } + #[error("Base58 decode error: {0}")] + Base58DecodeError(#[from] bs58::decode::Error), + #[error("Vec resize error")] + VecResizeError(#[from] alloc::vec::Vec), } #[cfg(feature = "std")] diff --git a/src/core/addresscodec/mod.rs b/src/core/addresscodec/mod.rs index 7aa8ed8a..e54e514f 100644 --- a/src/core/addresscodec/mod.rs +++ b/src/core/addresscodec/mod.rs @@ -14,6 +14,8 @@ use alloc::vec::Vec; use core::convert::TryInto; use strum::IntoEnumIterator; +use super::exceptions::XRPLCoreResult; + /// Map the algorithm to the prefix. fn _algorithm_to_prefix<'a>(algo: &CryptoAlgorithm) -> &'a [u8] { match algo { @@ -23,23 +25,23 @@ fn _algorithm_to_prefix<'a>(algo: &CryptoAlgorithm) -> &'a [u8] { } /// Returns whether a decoded X-Address is a test address. -fn _is_test_address(prefix: &[u8]) -> Result { +fn _is_test_address(prefix: &[u8]) -> XRPLCoreResult { if ADDRESS_PREFIX_BYTES_MAIN == prefix { Ok(false) } else if ADDRESS_PREFIX_BYTES_TEST == prefix { Ok(true) } else { - Err(XRPLAddressCodecException::InvalidXAddressPrefix) + Err(XRPLAddressCodecException::InvalidXAddressPrefix.into()) } } /// Returns the destination tag extracted from the suffix /// of the X-Address. -fn _get_tag_from_buffer(buffer: &[u8]) -> Result, XRPLAddressCodecException> { +fn _get_tag_from_buffer(buffer: &[u8]) -> XRPLCoreResult> { let flag = &buffer[0]; if flag >= &2 { - Err(XRPLAddressCodecException::UnsupportedXAddress) + Err(XRPLAddressCodecException::UnsupportedXAddress.into()) } else if flag == &1 { // Little-endian to big-endian Ok(Some( @@ -50,9 +52,9 @@ fn _get_tag_from_buffer(buffer: &[u8]) -> Result, XRPLAddressCodecEx )) // inverse of what happens in encode } else if flag != &0 { - Err(XRPLAddressCodecException::InvalidXAddressZeroNoTag) + Err(XRPLAddressCodecException::InvalidXAddressZeroNoTag.into()) } else if hex::decode("0000000000000000")? != buffer[1..9] { - Err(XRPLAddressCodecException::InvalidXAddressZeroRemain) + Err(XRPLAddressCodecException::InvalidXAddressZeroRemain.into()) } else { Ok(None) } @@ -69,6 +71,7 @@ fn _get_tag_from_buffer(buffer: &[u8]) -> Result, XRPLAddressCodecEx /// use xrpl::core::addresscodec::exceptions::XRPLAddressCodecException; /// use xrpl::constants::CryptoAlgorithm; /// use xrpl::core::addresscodec::utils::SEED_LENGTH; +/// use xrpl::core::exceptions::XRPLCoreException; /// /// let entropy: [u8; SEED_LENGTH] = [ /// 207, 45, 227, 120, 251, 221, 126, 46, 232, @@ -83,7 +86,7 @@ fn _get_tag_from_buffer(buffer: &[u8]) -> Result, XRPLAddressCodecEx /// ) { /// Ok(seed) => Some(seed), /// Err(e) => match e { -/// XRPLAddressCodecException::UnknownSeedEncoding => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::UnknownSeedEncoding) => None, /// _ => None, /// } /// }; @@ -93,12 +96,12 @@ fn _get_tag_from_buffer(buffer: &[u8]) -> Result, XRPLAddressCodecEx pub fn encode_seed( entropy: [u8; SEED_LENGTH], encoding_type: CryptoAlgorithm, -) -> Result { - encode_base58( +) -> XRPLCoreResult { + Ok(encode_base58( &entropy, _algorithm_to_prefix(&encoding_type), Some(SEED_LENGTH), - ) + )?) } /// Returns an encoded seed. @@ -112,6 +115,7 @@ pub fn encode_seed( /// use xrpl::core::addresscodec::exceptions::XRPLAddressCodecException; /// use xrpl::core::addresscodec::utils::SEED_LENGTH; /// use xrpl::constants::CryptoAlgorithm; +/// use xrpl::core::exceptions::XRPLCoreException; /// extern crate alloc; /// use alloc::vec; /// @@ -124,17 +128,15 @@ pub fn encode_seed( /// let decoding: Option<([u8; SEED_LENGTH], CryptoAlgorithm)> = match decode_seed(seed) { /// Ok((bytes, algorithm)) => Some((bytes, algorithm)), /// Err(e) => match e { -/// XRPLAddressCodecException::UnknownSeedEncoding => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::UnknownSeedEncoding) => None, /// _ => None, /// } /// }; /// /// assert_eq!(Some(tuple), decoding); /// ``` -pub fn decode_seed( - seed: &str, -) -> Result<([u8; SEED_LENGTH], CryptoAlgorithm), XRPLAddressCodecException> { - let mut result: Option, XRPLAddressCodecException>> = None; +pub fn decode_seed(seed: &str) -> XRPLCoreResult<([u8; SEED_LENGTH], CryptoAlgorithm)> { + let mut result: Option>> = None; let mut algo: Option = None; for a in CryptoAlgorithm::iter() { @@ -145,10 +147,12 @@ pub fn decode_seed( match result { Some(Ok(val)) => { - let decoded: [u8; SEED_LENGTH] = val.try_into()?; + let decoded: [u8; SEED_LENGTH] = val + .try_into() + .map_err(|err| XRPLAddressCodecException::VecResizeError(err))?; Ok((decoded, algo.expect("decode_seed"))) } - Some(Err(_)) | None => Err(XRPLAddressCodecException::UnknownSeedEncoding), + Some(Err(_)) | None => Err(XRPLAddressCodecException::UnknownSeedEncoding.into()), } } @@ -161,6 +165,7 @@ pub fn decode_seed( /// ``` /// use xrpl::core::addresscodec::classic_address_to_xaddress; /// use xrpl::core::addresscodec::exceptions::XRPLAddressCodecException; +/// use xrpl::core::exceptions::XRPLCoreException; /// /// let classic_address: &str = "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59".into(); /// let tag: Option = None; @@ -174,14 +179,14 @@ pub fn decode_seed( /// ) { /// Ok(address) => Some(address), /// Err(e) => match e { -/// XRPLAddressCodecException::InvalidXAddressPrefix => None, -/// XRPLAddressCodecException::UnsupportedXAddress => None, -/// XRPLAddressCodecException::InvalidXAddressZeroNoTag => None, -/// XRPLAddressCodecException::InvalidXAddressZeroRemain => None, -/// XRPLAddressCodecException::UnexpectedPayloadLength { +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::InvalidXAddressPrefix) => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::UnsupportedXAddress) => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::InvalidXAddressZeroNoTag) => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::InvalidXAddressZeroRemain) => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::UnexpectedPayloadLength { /// expected: _, /// found: _, -/// } => None, +/// }) => None, /// _ => None, /// } /// }; @@ -192,7 +197,7 @@ pub fn classic_address_to_xaddress( classic_address: &str, tag: Option, is_test_network: bool, -) -> Result { +) -> XRPLCoreResult { let classic_address_bytes = decode_classic_address(classic_address)?; let flag: bool = tag.is_some(); let tag_val: u64; @@ -200,9 +205,10 @@ pub fn classic_address_to_xaddress( if classic_address_bytes.len() != CLASSIC_ADDRESS_ID_LENGTH { Err(XRPLAddressCodecException::InvalidCAddressIdLength { length: CLASSIC_ADDRESS_ID_LENGTH, - }) + } + .into()) } else if tag.is_some() && tag > Some(u32::MAX.into()) { - Err(XRPLAddressCodecException::InvalidCAddressTag) + Err(XRPLAddressCodecException::InvalidCAddressTag.into()) } else { if let Some(tval) = tag { tag_val = tval; @@ -250,6 +256,7 @@ pub fn classic_address_to_xaddress( /// ``` /// use xrpl::core::addresscodec::xaddress_to_classic_address; /// use xrpl::core::addresscodec::exceptions::XRPLAddressCodecException; +/// use xrpl::core::exceptions::XRPLCoreException; /// /// let xaddress: &str = "X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ"; /// let classic: (String, Option, bool) = ( @@ -261,23 +268,21 @@ pub fn classic_address_to_xaddress( /// let conversion: Option<(String, Option, bool)> = match xaddress_to_classic_address(xaddress) { /// Ok((address, tag, is_test_network)) => Some((address, tag, is_test_network)), /// Err(e) => match e { -/// XRPLAddressCodecException::InvalidXAddressPrefix => None, -/// XRPLAddressCodecException::UnsupportedXAddress => None, -/// XRPLAddressCodecException::InvalidXAddressZeroNoTag => None, -/// XRPLAddressCodecException::InvalidXAddressZeroRemain => None, -/// XRPLAddressCodecException::UnexpectedPayloadLength { +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::InvalidXAddressPrefix) => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::UnsupportedXAddress) => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::InvalidXAddressZeroNoTag) => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::InvalidXAddressZeroRemain) => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::UnexpectedPayloadLength { /// expected: _, /// found: _, -/// } => None, +/// }) => None, /// _ => None, /// } /// }; /// /// assert_eq!(Some(classic), conversion); /// ``` -pub fn xaddress_to_classic_address( - xaddress: &str, -) -> Result<(String, Option, bool), XRPLAddressCodecException> { +pub fn xaddress_to_classic_address(xaddress: &str) -> XRPLCoreResult<(String, Option, bool)> { // Convert b58 to bytes let decoded = bs58::decode(xaddress) .with_alphabet(&XRPL_ALPHABET) @@ -303,6 +308,7 @@ pub fn xaddress_to_classic_address( /// ``` /// use xrpl::core::addresscodec::encode_classic_address; /// use xrpl::core::addresscodec::exceptions::XRPLAddressCodecException; +/// use xrpl::core::exceptions::XRPLCoreException; /// /// let bytes: &[u8] = &[ /// 94, 123, 17, 37, 35, 246, 141, 47, 94, 135, 157, 180, @@ -313,22 +319,22 @@ pub fn xaddress_to_classic_address( /// let encoding: Option = match encode_classic_address(bytes) { /// Ok(address) => Some(address), /// Err(e) => match e { -/// XRPLAddressCodecException::UnexpectedPayloadLength { +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::UnexpectedPayloadLength { /// expected: _, /// found: _, -/// } => None, +/// }) => None, /// _ => None, /// } /// }; /// /// assert_eq!(Some(address), encoding); /// ``` -pub fn encode_classic_address(bytestring: &[u8]) -> Result { - encode_base58( +pub fn encode_classic_address(bytestring: &[u8]) -> XRPLCoreResult { + Ok(encode_base58( bytestring, &CLASSIC_ADDRESS_PREFIX, Some(CLASSIC_ADDRESS_LENGTH.into()), - ) + )?) } /// Returns the decoded bytes of the classic address. @@ -340,6 +346,7 @@ pub fn encode_classic_address(bytestring: &[u8]) -> Result Result> = match decode_classic_address(key) { /// Ok(bytes) => Some(bytes), /// Err(e) => match e { -/// XRPLAddressCodecException::InvalidEncodingPrefixLength => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::InvalidEncodingPrefixLength) => None, /// _ => None, /// } /// }; /// /// assert_eq!(Some(bytes), decoding); /// ``` -pub fn decode_classic_address(classic_address: &str) -> Result, XRPLAddressCodecException> { - decode_base58(classic_address, &CLASSIC_ADDRESS_PREFIX) +pub fn decode_classic_address(classic_address: &str) -> XRPLCoreResult> { + Ok(decode_base58(classic_address, &CLASSIC_ADDRESS_PREFIX)?) } /// Returns the node public key encoding of these bytes @@ -373,6 +380,7 @@ pub fn decode_classic_address(classic_address: &str) -> Result, XRPLAddr /// ``` /// use xrpl::core::addresscodec::encode_node_public_key; /// use xrpl::core::addresscodec::exceptions::XRPLAddressCodecException; +/// use xrpl::core::exceptions::XRPLCoreException; /// /// let bytes: &[u8] = &[ /// 3, 136, 229, 186, 135, 160, 0, 203, 128, 114, 64, 223, @@ -384,22 +392,22 @@ pub fn decode_classic_address(classic_address: &str) -> Result, XRPLAddr /// let encoding: Option = match encode_node_public_key(bytes) { /// Ok(key) => Some(key), /// Err(e) => match e { -/// XRPLAddressCodecException::UnexpectedPayloadLength { +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::UnexpectedPayloadLength { /// expected: _, /// found: _, -/// } => None, +/// }) => None, /// _ => None, /// } /// }; /// /// assert_eq!(Some(key), encoding); /// ``` -pub fn encode_node_public_key(bytestring: &[u8]) -> Result { - encode_base58( +pub fn encode_node_public_key(bytestring: &[u8]) -> XRPLCoreResult { + Ok(encode_base58( bytestring, &NODE_PUBLIC_KEY_PREFIX, Some(NODE_PUBLIC_KEY_LENGTH.into()), - ) + )?) } /// Returns the decoded bytes of the node public key. @@ -411,6 +419,7 @@ pub fn encode_node_public_key(bytestring: &[u8]) -> Result Result> = match decode_node_public_key(key) { /// Ok(bytes) => Some(bytes), /// Err(e) => match e { -/// XRPLAddressCodecException::InvalidEncodingPrefixLength => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::InvalidEncodingPrefixLength) => None, /// _ => None, /// } /// }; /// /// assert_eq!(Some(bytes), decoding); /// ``` -pub fn decode_node_public_key(node_public_key: &str) -> Result, XRPLAddressCodecException> { - decode_base58(node_public_key, &NODE_PUBLIC_KEY_PREFIX) +pub fn decode_node_public_key(node_public_key: &str) -> XRPLCoreResult> { + Ok(decode_base58(node_public_key, &NODE_PUBLIC_KEY_PREFIX)?) } /// Returns the account public key encoding of these @@ -445,6 +454,7 @@ pub fn decode_node_public_key(node_public_key: &str) -> Result, XRPLAddr /// ``` /// use xrpl::core::addresscodec::encode_account_public_key; /// use xrpl::core::addresscodec::exceptions::XRPLAddressCodecException; +/// use xrpl::core::exceptions::XRPLCoreException; /// /// let bytes: &[u8] = &[ /// 2, 54, 147, 241, 89, 103, 174, 53, 125, 3, 39, 151, @@ -456,22 +466,22 @@ pub fn decode_node_public_key(node_public_key: &str) -> Result, XRPLAddr /// let encoding: Option = match encode_account_public_key(bytes) { /// Ok(key) => Some(key), /// Err(e) => match e { -/// XRPLAddressCodecException::UnexpectedPayloadLength { +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::UnexpectedPayloadLength { /// expected: _, /// found: _, -/// } => None, +/// }) => None, /// _ => None, /// } /// }; /// /// assert_eq!(Some(key), encoding); /// ``` -pub fn encode_account_public_key(bytestring: &[u8]) -> Result { - encode_base58( +pub fn encode_account_public_key(bytestring: &[u8]) -> XRPLCoreResult { + Ok(encode_base58( bytestring, &ACCOUNT_PUBLIC_KEY_PREFIX, Some(ACCOUNT_PUBLIC_KEY_LENGTH.into()), - ) + )?) } /// Returns the decoded bytes of the node public key. @@ -483,6 +493,7 @@ pub fn encode_account_public_key(bytestring: &[u8]) -> Result Result> = match decode_account_public_key(key) { /// Ok(bytes) => Some(bytes), /// Err(e) => match e { -/// XRPLAddressCodecException::InvalidEncodingPrefixLength => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::InvalidEncodingPrefixLength) => None, /// _ => None, /// } /// }; /// /// assert_eq!(Some(bytes), decoding); /// ``` -pub fn decode_account_public_key( - account_public_key: &str, -) -> Result, XRPLAddressCodecException> { - decode_base58(account_public_key, &ACCOUNT_PUBLIC_KEY_PREFIX) +pub fn decode_account_public_key(account_public_key: &str) -> XRPLCoreResult> { + Ok(decode_base58( + account_public_key, + &ACCOUNT_PUBLIC_KEY_PREFIX, + )?) } /// Returns whether `classic_address` is a valid classic address. diff --git a/src/core/binarycodec/binary_wrappers.rs b/src/core/binarycodec/binary_wrappers.rs index d8085bd7..3b152307 100644 --- a/src/core/binarycodec/binary_wrappers.rs +++ b/src/core/binarycodec/binary_wrappers.rs @@ -1,7 +1,9 @@ +use super::definitions::*; +use super::types::TryFromParser; use crate::core::binarycodec::exceptions::XRPLBinaryCodecException; use crate::core::binarycodec::utils::*; -use crate::core::definitions::*; -use crate::core::types::TryFromParser; +use crate::core::exceptions::XRPLCoreException; +use crate::core::exceptions::XRPLCoreResult; use crate::utils::ToBytes; use alloc::borrow::ToOwned; use alloc::vec; @@ -44,14 +46,18 @@ pub struct BinaryParser(Vec); /// /// See Length Prefixing: /// `` -fn _encode_variable_length_prefix(length: &usize) -> Result, XRPLBinaryCodecException> { +fn _encode_variable_length_prefix(length: &usize) -> XRPLCoreResult> { if length <= &MAX_SINGLE_BYTE_LENGTH { Ok([*length as u8].to_vec()) } else if length < &MAX_DOUBLE_BYTE_LENGTH { let mut bytes = vec![]; let b_length = *length - (MAX_SINGLE_BYTE_LENGTH + 1); - let val_a: u8 = ((b_length >> 8) + (MAX_SINGLE_BYTE_LENGTH + 1)).try_into()?; - let val_b: u8 = (b_length & 0xFF).try_into()?; + let val_a: u8 = ((b_length >> 8) + (MAX_SINGLE_BYTE_LENGTH + 1)) + .try_into() + .map_err(XRPLBinaryCodecException::TryFromIntError)?; + let val_b: u8 = (b_length & 0xFF) + .try_into() + .map_err(XRPLBinaryCodecException::TryFromIntError)?; bytes.extend_from_slice(&[val_a]); bytes.extend_from_slice(&[val_b]); @@ -60,9 +66,15 @@ fn _encode_variable_length_prefix(length: &usize) -> Result, XRPLBinaryC } else if length <= &MAX_LENGTH_VALUE { let mut bytes = vec![]; let b_length = *length - MAX_DOUBLE_BYTE_LENGTH; - let val_a: u8 = ((MAX_SECOND_BYTE_VALUE + 1) + (b_length >> 16)).try_into()?; - let val_b: u8 = ((b_length >> 8) & 0xFF).try_into()?; - let val_c: u8 = (b_length & 0xFF).try_into()?; + let val_a: u8 = ((MAX_SECOND_BYTE_VALUE + 1) + (b_length >> 16)) + .try_into() + .map_err(XRPLBinaryCodecException::TryFromIntError)?; + let val_b: u8 = ((b_length >> 8) & 0xFF) + .try_into() + .map_err(XRPLBinaryCodecException::TryFromIntError)?; + let val_c: u8 = (b_length & 0xFF) + .try_into() + .map_err(XRPLBinaryCodecException::TryFromIntError)?; bytes.extend_from_slice(&[val_a]); bytes.extend_from_slice(&[val_b]); @@ -72,7 +84,8 @@ fn _encode_variable_length_prefix(length: &usize) -> Result, XRPLBinaryC } else { Err(XRPLBinaryCodecException::InvalidVariableLengthTooLarge { max: MAX_LENGTH_VALUE, - }) + } + .into()) } } @@ -106,6 +119,7 @@ pub trait Parser { /// use xrpl::core::binarycodec::BinaryParser; /// use xrpl::core::Parser; /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// use xrpl::core::exceptions::XRPLCoreException; /// /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); @@ -113,15 +127,15 @@ pub trait Parser { /// match binary_parser.skip_bytes(4) { /// Ok(parser) => assert_eq!(*parser, test_bytes[4..]), /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + /// XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::UnexpectedParserSkipOverflow { /// max: _, /// found: _, - /// } => assert!(false), + /// }) => assert!(false), /// _ => assert!(false) /// } /// } /// ``` - fn skip_bytes(&mut self, n: usize) -> Result<&Self, XRPLBinaryCodecException>; + fn skip_bytes(&mut self, n: usize) -> XRPLCoreResult<&Self>; /// Consume and return the first n bytes of the BinaryParser. /// @@ -133,6 +147,7 @@ pub trait Parser { /// use xrpl::core::binarycodec::BinaryParser; /// use xrpl::core::Parser; /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// use xrpl::core::exceptions::XRPLCoreException; /// /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); @@ -140,15 +155,15 @@ pub trait Parser { /// match binary_parser.read(5) { /// Ok(data) => assert_eq!(test_bytes[..5], data), /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + /// XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::UnexpectedParserSkipOverflow { /// max: _, /// found: _, - /// } => assert!(false), + /// }) => assert!(false), /// _ => assert!(false) /// } /// } /// ``` - fn read(&mut self, n: usize) -> Result, XRPLBinaryCodecException>; + fn read(&mut self, n: usize) -> XRPLCoreResult>; /// Read 1 byte from parser and return as unsigned int. /// @@ -160,6 +175,7 @@ pub trait Parser { /// use xrpl::core::binarycodec::BinaryParser; /// use xrpl::core::Parser; /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// use xrpl::core::exceptions::XRPLCoreException; /// /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); @@ -167,15 +183,15 @@ pub trait Parser { /// match binary_parser.read_uint8() { /// Ok(data) => assert_eq!(0, data), /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + /// XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::UnexpectedParserSkipOverflow { /// max: _, /// found: _, - /// } => assert!(false), + /// }) => assert!(false), /// _ => assert!(false) /// } /// } /// ``` - fn read_uint8(&mut self) -> Result; + fn read_uint8(&mut self) -> XRPLCoreResult; /// Read 2 bytes from parser and return as unsigned int. /// @@ -187,6 +203,7 @@ pub trait Parser { /// use xrpl::core::binarycodec::BinaryParser; /// use xrpl::core::Parser; /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// use xrpl::core::exceptions::XRPLCoreException; /// /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); @@ -194,15 +211,15 @@ pub trait Parser { /// match binary_parser.read_uint16() { /// Ok(data) => assert_eq!(17, data), /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + /// XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::UnexpectedParserSkipOverflow { /// max: _, /// found: _, - /// } => assert!(false), + /// }) => assert!(false), /// _ => assert!(false) /// } /// } /// ``` - fn read_uint16(&mut self) -> Result; + fn read_uint16(&mut self) -> XRPLCoreResult; /// Read 4 bytes from parser and return as unsigned int. /// @@ -214,6 +231,7 @@ pub trait Parser { /// use xrpl::core::binarycodec::BinaryParser; /// use xrpl::core::Parser; /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// use xrpl::core::exceptions::XRPLCoreException; /// /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); @@ -221,15 +239,15 @@ pub trait Parser { /// match binary_parser.read_uint32() { /// Ok(data) => assert_eq!(1122867, data), /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + /// XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::UnexpectedParserSkipOverflow { /// max: _, /// found: _, - /// } => assert!(false), + /// }) => assert!(false), /// _ => assert!(false) /// } /// } /// ``` - fn read_uint32(&mut self) -> Result; + fn read_uint32(&mut self) -> XRPLCoreResult; /// Returns whether the binary parser has finished /// parsing (e.g. there is nothing left in the buffer @@ -243,6 +261,7 @@ pub trait Parser { /// use xrpl::core::binarycodec::BinaryParser; /// use xrpl::core::Parser; /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// use xrpl::core::exceptions::XRPLCoreException; /// extern crate alloc; /// use alloc::vec; /// @@ -255,10 +274,10 @@ pub trait Parser { /// match binary_parser.read(1) { /// Ok(data) => buffer.extend_from_slice(&data), /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + /// XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::UnexpectedParserSkipOverflow { /// max: _, /// found: _, - /// } => assert!(false), + /// }) => assert!(false), /// _ => assert!(false) /// } /// } @@ -286,6 +305,7 @@ pub trait Parser { /// use xrpl::core::binarycodec::BinaryParser; /// use xrpl::core::Parser; /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// use xrpl::core::exceptions::XRPLCoreException; /// /// let test_bytes: &[u8] = &[6, 17, 34, 51, 68, 85, 102]; /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); @@ -293,32 +313,35 @@ pub trait Parser { /// match binary_parser.read_length_prefix() { /// Ok(data) => assert_eq!(6, data), /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedLengthPrefixRange { + /// XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::UnexpectedLengthPrefixRange { /// min: _, max: _ - /// } => assert!(false), + /// }) => assert!(false), /// _ => assert!(false) /// } /// } - fn read_length_prefix(&mut self) -> Result; + fn read_length_prefix(&mut self) -> XRPLCoreResult; /// Reads field ID from BinaryParser and returns as /// a FieldHeader object. - fn read_field_header(&mut self) -> Result; + fn read_field_header(&mut self) -> XRPLCoreResult; /// Read the field ordinal at the head of the /// BinaryParser and return a FieldInstance object /// representing information about the field /// containedin the following bytes. - fn read_field(&mut self) -> Result; + fn read_field(&mut self) -> XRPLCoreResult; /// Read next bytes from BinaryParser as the given type. - fn read_type(&mut self) -> Result; + fn read_type(&mut self) -> XRPLCoreResult; /// Read value of the type specified by field from /// the BinaryParser. - fn read_field_value(&mut self, field: &FieldInstance) -> Result + fn read_field_value( + &mut self, + field: &FieldInstance, + ) -> XRPLCoreResult where - T::Error: From; + T::Error: From; } pub trait Serialization { @@ -369,9 +392,9 @@ pub trait Serialization { /// ``` /// use xrpl::core::binarycodec::BinarySerializer; /// use xrpl::core::binarycodec::Serialization; - /// use xrpl::core::definitions::FieldInstance; - /// use xrpl::core::definitions::FieldInfo; - /// use xrpl::core::definitions::FieldHeader; + /// use xrpl::core::binarycodec::definitions::FieldInstance; + /// use xrpl::core::binarycodec::definitions::FieldInfo; + /// use xrpl::core::binarycodec::definitions::FieldHeader; /// /// let field_header: FieldHeader = FieldHeader { /// type_code: -2, @@ -451,40 +474,41 @@ impl Parser for BinaryParser { } } - fn skip_bytes(&mut self, n: usize) -> Result<&Self, XRPLBinaryCodecException> { + fn skip_bytes(&mut self, n: usize) -> XRPLCoreResult<&Self> { if n > self.0.len() { Err(XRPLBinaryCodecException::UnexpectedParserSkipOverflow { max: self.0.len(), found: n, - }) + } + .into()) } else { self.0 = self.0[n..].to_vec(); Ok(self) } } - fn read(&mut self, n: usize) -> Result, XRPLBinaryCodecException> { + fn read(&mut self, n: usize) -> XRPLCoreResult> { let first_n_bytes = self.0[..n].to_owned(); self.skip_bytes(n)?; Ok(first_n_bytes) } - fn read_uint8(&mut self) -> Result { + fn read_uint8(&mut self) -> XRPLCoreResult { let result = self.read(1)?; Ok(u8::from_be_bytes(result.try_into().or(Err( XRPLBinaryCodecException::InvalidReadFromBytesValue, ))?)) } - fn read_uint16(&mut self) -> Result { + fn read_uint16(&mut self) -> XRPLCoreResult { let result = self.read(2)?; Ok(u16::from_be_bytes(result.try_into().or(Err( XRPLBinaryCodecException::InvalidReadFromBytesValue, ))?)) } - fn read_uint32(&mut self) -> Result { + fn read_uint32(&mut self) -> XRPLCoreResult { let result = self.read(4)?; Ok(u32::from_be_bytes(result.try_into().or(Err( XRPLBinaryCodecException::InvalidReadFromBytesValue, @@ -499,7 +523,7 @@ impl Parser for BinaryParser { } } - fn read_length_prefix(&mut self) -> Result { + fn read_length_prefix(&mut self) -> XRPLCoreResult { let byte1: usize = self.read_uint8()? as usize; match byte1 { @@ -529,11 +553,13 @@ impl Parser for BinaryParser { + (byte2 * MAX_BYTE_VALUE) + byte3) } - _ => Err(XRPLBinaryCodecException::UnexpectedLengthPrefixRange { min: 1, max: 3 }), + _ => { + Err(XRPLBinaryCodecException::UnexpectedLengthPrefixRange { min: 1, max: 3 }.into()) + } } } - fn read_field_header(&mut self) -> Result { + fn read_field_header(&mut self) -> XRPLCoreResult { let mut type_code: i16 = self.read_uint8()? as i16; let mut field_code: i16 = type_code & 15; @@ -543,7 +569,9 @@ impl Parser for BinaryParser { type_code = self.read_uint8()? as i16; if type_code == 0 || type_code < 16 { - return Err(XRPLBinaryCodecException::UnexpectedTypeCodeRange { min: 1, max: 16 }); + return Err( + XRPLBinaryCodecException::UnexpectedTypeCodeRange { min: 1, max: 16 }.into(), + ); }; }; @@ -551,7 +579,9 @@ impl Parser for BinaryParser { field_code = self.read_uint8()? as i16; if field_code == 0 || field_code < 16 { - return Err(XRPLBinaryCodecException::UnexpectedFieldCodeRange { min: 1, max: 16 }); + return Err( + XRPLBinaryCodecException::UnexpectedFieldCodeRange { min: 1, max: 16 }.into(), + ); }; }; @@ -561,7 +591,7 @@ impl Parser for BinaryParser { }) } - fn read_field(&mut self) -> Result { + fn read_field(&mut self) -> XRPLCoreResult { let field_header = self.read_field_header()?; let field_name = get_field_name_from_header(&field_header); @@ -571,16 +601,19 @@ impl Parser for BinaryParser { }; }; - Err(XRPLBinaryCodecException::UnknownFieldName) + Err(XRPLBinaryCodecException::UnknownFieldName.into()) } - fn read_type(&mut self) -> Result { + fn read_type(&mut self) -> XRPLCoreResult { T::from_parser(self, None) } - fn read_field_value(&mut self, field: &FieldInstance) -> Result + fn read_field_value( + &mut self, + field: &FieldInstance, + ) -> XRPLCoreResult where - T::Error: From, + T::Error: From, { if field.is_vl_encoded { let length = self.read_length_prefix()?; @@ -604,9 +637,9 @@ impl From> for BinaryParser { } impl TryFrom<&str> for BinaryParser { - type Error = XRPLBinaryCodecException; + type Error = XRPLCoreException; - fn try_from(hex_bytes: &str) -> Result { + fn try_from(hex_bytes: &str) -> XRPLCoreResult { Ok(BinaryParser(hex::decode(hex_bytes)?)) } } diff --git a/src/core/definitions/definitions.json b/src/core/binarycodec/definitions/definitions.json similarity index 100% rename from src/core/definitions/definitions.json rename to src/core/binarycodec/definitions/definitions.json diff --git a/src/core/definitions/mod.rs b/src/core/binarycodec/definitions/mod.rs similarity index 93% rename from src/core/definitions/mod.rs rename to src/core/binarycodec/definitions/mod.rs index 15880922..1eaa850e 100644 --- a/src/core/definitions/mod.rs +++ b/src/core/binarycodec/definitions/mod.rs @@ -25,7 +25,7 @@ pub const CODE_MAX_VALUE: i16 = u8::MAX as i16; /// ## Basic usage /// /// ``` -/// use xrpl::core::definitions::FieldHeader; +/// use xrpl::core::binarycodec::definitions::FieldHeader; /// /// let field_header = FieldHeader { /// type_code: -2, @@ -46,9 +46,9 @@ pub struct FieldHeader { /// ## Basic usage /// /// ``` -/// use xrpl::core::definitions::FieldInfo; -/// use xrpl::core::definitions::FieldHeader; -/// use xrpl::core::definitions::FieldInstance; +/// use xrpl::core::binarycodec::definitions::FieldInfo; +/// use xrpl::core::binarycodec::definitions::FieldHeader; +/// use xrpl::core::binarycodec::definitions::FieldInstance; /// /// let field_header: FieldHeader = FieldHeader { /// type_code: -2, @@ -86,7 +86,7 @@ pub struct FieldInstance { /// ## Basic usage /// /// ``` -/// use xrpl::core::definitions::FieldInfo; +/// use xrpl::core::binarycodec::definitions::FieldInfo; /// /// let field_info = FieldInfo { /// nth: 0, diff --git a/src/core/definitions/types.rs b/src/core/binarycodec/definitions/types.rs similarity index 99% rename from src/core/definitions/types.rs rename to src/core/binarycodec/definitions/types.rs index 99e66d04..e3c96693 100644 --- a/src/core/definitions/types.rs +++ b/src/core/binarycodec/definitions/types.rs @@ -1,9 +1,9 @@ //! Maps and helpers providing serialization-related //! information about fields. -use crate::core::definitions::FieldHeader; -use crate::core::definitions::FieldInfo; -use crate::core::definitions::FieldInstance; +use super::FieldHeader; +use super::FieldInfo; +use super::FieldInstance; use alloc::borrow::ToOwned; use alloc::string::String; use alloc::string::ToString; diff --git a/src/core/binarycodec/exceptions.rs b/src/core/binarycodec/exceptions.rs index 64d429ea..682f9a11 100644 --- a/src/core/binarycodec/exceptions.rs +++ b/src/core/binarycodec/exceptions.rs @@ -1,71 +1,51 @@ //! General XRPL Binary Codec Exceptions. -use crate::utils::exceptions::ISOCodeException; use crate::utils::exceptions::XRPRangeException; -use strum_macros::Display; -#[derive(Debug, Clone, PartialEq, Display)] +use super::types::exceptions::XRPLTypeException; +use thiserror_no_std::Error; + +#[derive(Debug, PartialEq, Error)] #[non_exhaustive] pub enum XRPLBinaryCodecException { + #[error("Unexpected parser skip overflow: max: {max}, found: {found}")] UnexpectedParserSkipOverflow { max: usize, found: usize }, + #[error("Unexpected length prefix range: min: {min}, max: {max}")] UnexpectedLengthPrefixRange { min: usize, max: usize }, + #[error("Unexpected type code range(min: {min}, max: {max})")] UnexpectedTypeCodeRange { min: usize, max: usize }, + #[error("Unexpected field code range(min: {min}, max: {max})")] UnexpectedFieldCodeRange { min: usize, max: usize }, + #[error("Unexpected field id byte range(min: {min}, max: {max})")] UnexpectedFieldIdByteRange { min: usize, max: usize }, + #[error("Unknown field name")] UnknownFieldName, + #[error("Invalid read from bytes value")] InvalidReadFromBytesValue, + #[error("Invalid variable length too large: max: {max}")] InvalidVariableLengthTooLarge { max: usize }, + #[error("Invalid hash length (expected: {expected}, found: {found})")] InvalidHashLength { expected: usize, found: usize }, + #[error("Invalid path set from value")] InvalidPathSetFromValue, + #[error("Try from slice error")] TryFromSliceError, - TryFromIntError, - FromUtf8Error, - ParseIntError, - FromHexError, - XRPRangeError(XRPRangeException), - SerdeJsonError(serde_json::error::Category), - DecimalError(rust_decimal::Error), - BigDecimalError(bigdecimal::ParseBigDecimalError), - ISOCodeError(ISOCodeException), + #[error("Field has no associated tag")] FieldHasNoAssiciatedTag, + #[error("XAddress tag mismatch")] XAddressTagMismatch, + #[error("Field is not account or destination")] FieldIsNotAccountOrDestination, -} - -impl From for XRPLBinaryCodecException { - fn from(err: XRPRangeException) -> Self { - XRPLBinaryCodecException::XRPRangeError(err) - } -} - -impl From for XRPLBinaryCodecException { - fn from(err: ISOCodeException) -> Self { - XRPLBinaryCodecException::ISOCodeError(err) - } -} - -impl From for XRPLBinaryCodecException { - fn from(err: rust_decimal::Error) -> Self { - XRPLBinaryCodecException::DecimalError(err) - } -} - -impl From for XRPLBinaryCodecException { - fn from(_: hex::FromHexError) -> Self { - XRPLBinaryCodecException::FromHexError - } -} - -impl From for XRPLBinaryCodecException { - fn from(err: serde_json::Error) -> Self { - XRPLBinaryCodecException::SerdeJsonError(err.classify()) - } -} - -impl From for XRPLBinaryCodecException { - fn from(_: core::num::TryFromIntError) -> Self { - XRPLBinaryCodecException::TryFromIntError - } + #[error("Try from int error: {0}")] + TryFromIntError(#[from] core::num::TryFromIntError), + #[error("From utf8 error: {0}")] + FromUtf8Error(#[from] alloc::string::FromUtf8Error), + #[error("Parse int error: {0}")] + ParseIntError(#[from] core::num::ParseIntError), + #[error("XRPL Type error: {0}")] + XRPLTypeError(#[from] XRPLTypeException), + #[error("XRP Range error: {0}")] + XRPRangeError(#[from] XRPRangeException), } impl From for XRPLBinaryCodecException { @@ -74,23 +54,5 @@ impl From for XRPLBinaryCodecException { } } -impl From for XRPLBinaryCodecException { - fn from(_: core::num::ParseIntError) -> Self { - XRPLBinaryCodecException::ParseIntError - } -} - -impl From for XRPLBinaryCodecException { - fn from(_: alloc::string::FromUtf8Error) -> Self { - XRPLBinaryCodecException::FromUtf8Error - } -} - -impl From for XRPLBinaryCodecException { - fn from(err: bigdecimal::ParseBigDecimalError) -> Self { - XRPLBinaryCodecException::BigDecimalError(err) - } -} - #[cfg(feature = "std")] impl alloc::error::Error for XRPLBinaryCodecException {} diff --git a/src/core/binarycodec/mod.rs b/src/core/binarycodec/mod.rs index 0045c57f..9b88ccfe 100644 --- a/src/core/binarycodec/mod.rs +++ b/src/core/binarycodec/mod.rs @@ -1,11 +1,12 @@ //! Functions for encoding objects into the XRP Ledger's //! canonical binary format and decoding them. -use super::types::{AccountId, STObject}; -use crate::Err; +pub mod definitions; +pub mod types; + +use types::{AccountId, STObject}; use alloc::{borrow::Cow, string::String, vec::Vec}; -use anyhow::Result; use core::convert::TryFrom; use hex::ToHex; use serde::Serialize; @@ -17,17 +18,21 @@ pub mod utils; pub use binary_wrappers::*; +use crate::XRPLSerdeJsonError; + +use super::exceptions::XRPLCoreResult; + const TRANSACTION_SIGNATURE_PREFIX: i32 = 0x53545800; const TRANSACTION_MULTISIG_PREFIX: i32 = 0x534D5400; -pub fn encode(signed_transaction: &T) -> Result +pub fn encode(signed_transaction: &T) -> XRPLCoreResult where T: Serialize, { serialize_json(signed_transaction, None, None, false) } -pub fn encode_for_signing(prepared_transaction: &T) -> Result +pub fn encode_for_signing(prepared_transaction: &T) -> XRPLCoreResult where T: Serialize, { @@ -42,7 +47,7 @@ where pub fn encode_for_multisigning( prepared_transaction: &T, signing_account: Cow<'_, str>, -) -> Result +) -> XRPLCoreResult where T: Serialize, { @@ -61,7 +66,7 @@ fn serialize_json( prefix: Option<&[u8]>, suffix: Option<&[u8]>, signing_only: bool, -) -> Result +) -> XRPLCoreResult where T: Serialize, { @@ -70,12 +75,8 @@ where buffer.extend(p); } - let json_value = match serde_json::to_value(prepared_transaction) { - Ok(v) => v, - Err(e) => { - return Err!(e); - } - }; + let json_value = + serde_json::to_value(prepared_transaction).map_err(XRPLSerdeJsonError::from)?; let st_object = STObject::try_from_value(json_value, signing_only)?; buffer.extend(st_object.as_ref()); diff --git a/src/core/binarycodec/test_cases.rs b/src/core/binarycodec/test_cases.rs index fd9f0ea7..2805e090 100644 --- a/src/core/binarycodec/test_cases.rs +++ b/src/core/binarycodec/test_cases.rs @@ -47,9 +47,9 @@ pub struct TestDefinitions { } fn _load_tests() -> &'static Option { - pub const DATA_DRIVEN_TESTS: &str = include_str!("../test_data/data-driven-tests.json"); - pub const CODEC_TEST_FIXTURES: &str = include_str!("../test_data/codec-fixtures.json"); - pub const X_CODEC_TEST_FIXTURES: &str = include_str!("../test_data/x-codec-fixtures.json"); + pub const DATA_DRIVEN_TESTS: &str = include_str!("./test_data/data-driven-tests.json"); + pub const CODEC_TEST_FIXTURES: &str = include_str!("./test_data/codec-fixtures.json"); + pub const X_CODEC_TEST_FIXTURES: &str = include_str!("./test_data/x-codec-fixtures.json"); lazy_static! { static ref TEST_CASES: Option = diff --git a/src/core/test_data/codec-fixtures.json b/src/core/binarycodec/test_data/codec-fixtures.json similarity index 100% rename from src/core/test_data/codec-fixtures.json rename to src/core/binarycodec/test_data/codec-fixtures.json diff --git a/src/core/test_data/data-driven-tests.json b/src/core/binarycodec/test_data/data-driven-tests.json similarity index 100% rename from src/core/test_data/data-driven-tests.json rename to src/core/binarycodec/test_data/data-driven-tests.json diff --git a/src/core/test_data/iou-tests.json b/src/core/binarycodec/test_data/iou-tests.json similarity index 100% rename from src/core/test_data/iou-tests.json rename to src/core/binarycodec/test_data/iou-tests.json diff --git a/src/core/test_data/path-set-test.json b/src/core/binarycodec/test_data/path-set-test.json similarity index 100% rename from src/core/test_data/path-set-test.json rename to src/core/binarycodec/test_data/path-set-test.json diff --git a/src/core/test_data/path-test.json b/src/core/binarycodec/test_data/path-test.json similarity index 100% rename from src/core/test_data/path-test.json rename to src/core/binarycodec/test_data/path-test.json diff --git a/src/core/test_data/x-codec-fixtures.json b/src/core/binarycodec/test_data/x-codec-fixtures.json similarity index 100% rename from src/core/test_data/x-codec-fixtures.json rename to src/core/binarycodec/test_data/x-codec-fixtures.json diff --git a/src/core/types/account_id.rs b/src/core/binarycodec/types/account_id.rs similarity index 89% rename from src/core/types/account_id.rs rename to src/core/binarycodec/types/account_id.rs index bbaa8e31..449d4530 100644 --- a/src/core/types/account_id.rs +++ b/src/core/binarycodec/types/account_id.rs @@ -1,14 +1,18 @@ //! Codec for currency property inside an XRPL //! issued currency amount json. +use super::Hash160; +use super::TryFromParser; +use super::XRPLType; use crate::constants::ACCOUNT_ID_LENGTH; use crate::core::addresscodec::exceptions::XRPLAddressCodecException; use crate::core::addresscodec::*; -use crate::core::types::exceptions::XRPLHashException; -use crate::core::types::*; +use crate::core::exceptions::XRPLCoreException; +use crate::core::exceptions::XRPLCoreResult; use crate::core::BinaryParser; use crate::utils::is_hex_address; use core::convert::TryFrom; +use core::fmt::Display; use serde::ser::Error; use serde::Serializer; use serde::{Deserialize, Serialize}; @@ -24,7 +28,7 @@ use serde::{Deserialize, Serialize}; pub struct AccountId(Hash160); impl XRPLType for AccountId { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Construct an AccountID from given bytes. /// If buffer is not provided, default to 20 zero bytes. @@ -35,7 +39,7 @@ impl XRPLType for AccountId { } impl TryFromParser for AccountId { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Build AccountId from a BinaryParser. fn from_parser( @@ -64,11 +68,11 @@ impl Serialize for AccountId { } impl TryFrom<&str> for AccountId { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Construct an AccountId from a hex string or /// a base58 r-Address. - fn try_from(value: &str) -> Result { + fn try_from(value: &str) -> XRPLCoreResult { if is_hex_address(value) { Self::new(Some(&hex::decode(value)?)) } else if is_valid_classic_address(value) { @@ -77,7 +81,7 @@ impl TryFrom<&str> for AccountId { let (classic_address, _, _) = xaddress_to_classic_address(value)?; Self::new(Some(&decode_classic_address(&classic_address)?)) } else { - Err(XRPLHashException::XRPLAddressCodecError( + Err(XRPLCoreException::XRPLAddressCodecError( XRPLAddressCodecException::InvalidClassicAddressValue, )) } @@ -105,7 +109,7 @@ impl AsRef<[u8]> for AccountId { #[cfg(test)] mod test { use super::*; - use alloc::format; + use alloc::{format, string::ToString}; const HEX_ENCODING: &str = "5E7B112523F68D2F5E879DB4EAC51C6698A69304"; const BASE58_ENCODING: &str = "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"; diff --git a/src/core/types/amount.rs b/src/core/binarycodec/types/amount.rs similarity index 78% rename from src/core/types/amount.rs rename to src/core/binarycodec/types/amount.rs index 8aced60d..3a724d24 100644 --- a/src/core/types/amount.rs +++ b/src/core/binarycodec/types/amount.rs @@ -3,14 +3,19 @@ //! See Amount Fields: //! `` +use super::exceptions::XRPLTypeException; +use super::AccountId; +use super::Currency; +use super::TryFromParser; +use super::XRPLType; use crate::core::binarycodec::exceptions::XRPLBinaryCodecException; -use crate::core::types::exceptions::XRPLTypeException; -use crate::core::types::*; +use crate::core::exceptions::XRPLCoreException; +use crate::core::exceptions::XRPLCoreResult; use crate::core::BinaryParser; use crate::core::Parser; -use crate::utils::exceptions::JSONParseException; use crate::utils::exceptions::XRPRangeException; use crate::utils::*; +use crate::XRPLSerdeJsonError; use alloc::string::String; use alloc::string::ToString; use alloc::vec; @@ -18,6 +23,7 @@ use alloc::vec::Vec; use bigdecimal::{BigDecimal, Signed, Zero}; use core::convert::TryFrom; use core::convert::TryInto; +use core::fmt::Display; use core::str::FromStr; use rust_decimal::prelude::ToPrimitive; use serde::ser::Error; @@ -36,7 +42,10 @@ const _CURRENCY_AMOUNT_BYTE_LENGTH: u8 = 48; /// Normally when using bigdecimal "serde_json" feature a `1` will be serialized as `1.000000000000000`. /// This function normalizes a `BigDecimal` before serializing to a string. -pub fn serialize_bigdecimal(value: &BigDecimal, s: S) -> Result { +pub fn serialize_bigdecimal( + value: &BigDecimal, + s: S, +) -> XRPLCoreResult { let trimmed_str = value.normalized().to_string(); s.serialize_str(&trimmed_str) } @@ -66,8 +75,9 @@ fn _contains_decimal(string: &str) -> bool { /// Serializes the value field of an issued currency amount /// to its bytes representation. -fn _serialize_issued_currency_value(decimal: BigDecimal) -> Result<[u8; 8], XRPRangeException> { - verify_valid_ic_value(&decimal.to_scientific_notation())?; +fn _serialize_issued_currency_value(decimal: BigDecimal) -> XRPLCoreResult<[u8; 8]> { + verify_valid_ic_value(&decimal.to_scientific_notation()) + .map_err(|e| XRPLCoreException::XRPLUtilsError(e.to_string()))?; if decimal.is_zero() { return Ok((_ZERO_CURRENCY_AMOUNT_HEX).to_be_bytes()); @@ -85,10 +95,13 @@ fn _serialize_issued_currency_value(decimal: BigDecimal) -> Result<[u8; 8], XRPR while mantissa > _MAX_MANTISSA { if exp >= MAX_IOU_EXPONENT { - return Err(XRPRangeException::UnexpectedICAmountOverflow { - max: MAX_IOU_EXPONENT as usize, - found: exp as usize, - }); + return Err(XRPLBinaryCodecException::from( + XRPRangeException::UnexpectedICAmountOverflow { + max: MAX_IOU_EXPONENT as usize, + found: exp as usize, + }, + ) + .into()); } else { mantissa /= 10; exp += 1; @@ -99,10 +112,13 @@ fn _serialize_issued_currency_value(decimal: BigDecimal) -> Result<[u8; 8], XRPR // Round to zero Ok((_ZERO_CURRENCY_AMOUNT_HEX).to_be_bytes()) } else if exp > MAX_IOU_EXPONENT || mantissa > _MAX_MANTISSA { - Err(XRPRangeException::UnexpectedICAmountOverflow { - max: MAX_IOU_EXPONENT as usize, - found: exp as usize, - }) + Err( + XRPLBinaryCodecException::from(XRPRangeException::UnexpectedICAmountOverflow { + max: MAX_IOU_EXPONENT as usize, + found: exp as usize, + }) + .into(), + ) } else { // "Not XRP" bit set let mut serial: i128 = _ZERO_CURRENCY_AMOUNT_HEX as i128; @@ -122,24 +138,26 @@ fn _serialize_issued_currency_value(decimal: BigDecimal) -> Result<[u8; 8], XRPR } /// Serializes an XRP amount. -fn _serialize_xrp_amount(value: &str) -> Result<[u8; 8], XRPRangeException> { - verify_valid_xrp_value(value)?; +fn _serialize_xrp_amount(value: &str) -> XRPLCoreResult<[u8; 8]> { + verify_valid_xrp_value(value).map_err(|e| XRPLCoreException::XRPLUtilsError(e.to_string()))?; - let decimal = rust_decimal::Decimal::from_str(value)?.normalize(); + let decimal = bigdecimal::BigDecimal::from_str(value) + .map_err(XRPLTypeException::BigDecimalError)? + .normalized(); if let Some(result) = decimal.to_i64() { let value_with_pos_bit = result | _POS_SIGN_BIT_MASK; Ok(value_with_pos_bit.to_be_bytes()) } else { // Safety, should never occur - Err(XRPRangeException::InvalidXRPAmount) + Err(XRPLCoreException::XRPLUtilsError( + XRPRangeException::InvalidXRPAmount.to_string(), + )) } } /// Serializes an issued currency amount. -fn _serialize_issued_currency_amount( - issused_currency: IssuedCurrency, -) -> Result<[u8; 48], XRPRangeException> { +fn _serialize_issued_currency_amount(issused_currency: IssuedCurrency) -> XRPLCoreResult<[u8; 48]> { let mut bytes = vec![]; let amount_bytes = _serialize_issued_currency_value(issused_currency.value)?; let currency_bytes: &[u8] = issused_currency.currency.as_ref(); @@ -150,10 +168,13 @@ fn _serialize_issued_currency_amount( bytes.extend_from_slice(issuer_bytes); if bytes.len() != 48 { - Err(XRPRangeException::InvalidICSerializationLength { - expected: 48, - found: bytes.len(), - }) + Err( + XRPLBinaryCodecException::from(XRPRangeException::InvalidICSerializationLength { + expected: 48, + found: bytes.len(), + }) + .into(), + ) } else { Ok(bytes.try_into().expect("_serialize_issued_currency_amount")) } @@ -184,7 +205,7 @@ impl IssuedCurrency { /// Deserialize the issued currency amount. fn _deserialize_issued_currency_amount( parser: &mut BinaryParser, - ) -> Result { + ) -> XRPLCoreResult { let mut value: BigDecimal; let bytes = parser.read(8)?; @@ -195,7 +216,8 @@ impl IssuedCurrency { value = BigDecimal::from(0); } else { let hex_mantissa = hex::encode([&[bytes[1] & 0x3F], &bytes[2..]].concat()); - let int_mantissa = i128::from_str_radix(&hex_mantissa, 16)?; + let int_mantissa = i128::from_str_radix(&hex_mantissa, 16) + .map_err(XRPLBinaryCodecException::ParseIntError)?; // Adjust scale using the exponent let scale = exp.unsigned_abs(); @@ -210,8 +232,9 @@ impl IssuedCurrency { value = -value.abs(); } } + verify_valid_ic_value(&value.to_string()) + .map_err(|e| XRPLCoreException::XRPLUtilsError(e.to_string()))?; - verify_valid_ic_value(&value.to_string())?; Ok(value) } } @@ -220,7 +243,7 @@ impl XRPLType for Amount { type Error = hex::FromHexError; /// Construct an Amount from given bytes. - fn new(buffer: Option<&[u8]>) -> Result { + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult { if let Some(data) = buffer { Ok(Amount(data.to_vec())) } else { @@ -230,13 +253,13 @@ impl XRPLType for Amount { } impl TryFromParser for Amount { - type Error = XRPLBinaryCodecException; + type Error = XRPLCoreException; /// Build Amount from a BinaryParser. fn from_parser( parser: &mut BinaryParser, _length: Option, - ) -> Result { + ) -> XRPLCoreResult { let parser_first_byte = parser.peek(); let num_bytes = match parser_first_byte { None => _CURRENCY_AMOUNT_BYTE_LENGTH, @@ -248,13 +271,13 @@ impl TryFromParser for Amount { } impl TryFromParser for IssuedCurrency { - type Error = XRPLTypeException; + type Error = XRPLCoreException; /// Build IssuedCurrency from a BinaryParser. fn from_parser( parser: &mut BinaryParser, _length: Option, - ) -> Result { + ) -> XRPLCoreResult { Ok(IssuedCurrency { value: IssuedCurrency::_deserialize_issued_currency_amount(parser)?, currency: Currency::from_parser(parser, None)?, @@ -265,7 +288,7 @@ impl TryFromParser for IssuedCurrency { impl Serialize for Amount { /// Construct a JSON object representing this Amount. - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: S) -> XRPLCoreResult where S: Serializer, { @@ -291,55 +314,58 @@ impl Serialize for Amount { } impl TryFrom<&str> for Amount { - type Error = XRPLTypeException; + type Error = XRPLCoreException; /// Construct an Amount object from a hex string. - fn try_from(value: &str) -> Result { + fn try_from(value: &str) -> XRPLCoreResult { let serialized = _serialize_xrp_amount(value)?; Ok(Amount::new(Some(&serialized))?) } } impl TryFrom for Amount { - type Error = XRPLTypeException; + type Error = XRPLCoreException; /// Construct an Amount object from an IssuedCurrency. - fn try_from(value: IssuedCurrency) -> Result { + fn try_from(value: IssuedCurrency) -> XRPLCoreResult { let serialized = _serialize_issued_currency_amount(value)?; Ok(Amount::new(Some(&serialized))?) } } impl TryFrom for Amount { - type Error = XRPLTypeException; + type Error = XRPLCoreException; /// Construct an Amount object from a Serde JSON Value. - fn try_from(value: serde_json::Value) -> Result { + fn try_from(value: serde_json::Value) -> XRPLCoreResult { if value.is_string() { - Self::try_from(value.as_str().ok_or(XRPLTypeException::InvalidNoneValue)?) + let xrp_value = value.as_str().ok_or(XRPLTypeException::InvalidNoneValue)?; + Self::try_from(xrp_value) } else if value.is_object() { Ok(Self::try_from(IssuedCurrency::try_from(value)?)?) } else { - Err(XRPLTypeException::JSONParseError( - JSONParseException::InvalidSerdeValue { + Err( + XRPLCoreException::SerdeJsonError(XRPLSerdeJsonError::UnexpectedValueType { expected: "String/Object".into(), found: value, - }, - )) + }) + .into(), + ) } } } impl TryFrom for IssuedCurrency { - type Error = XRPLTypeException; + type Error = XRPLCoreException; /// Construct an IssuedCurrency object from a Serde JSON Value. - fn try_from(json: serde_json::Value) -> Result { + fn try_from(json: serde_json::Value) -> XRPLCoreResult { let value = BigDecimal::from_str( json["value"] .as_str() .ok_or(XRPLTypeException::InvalidNoneValue)?, - )?; + ) + .map_err(XRPLTypeException::BigDecimalError)?; let currency = Currency::try_from( json["currency"] .as_str() @@ -376,8 +402,8 @@ impl AsRef<[u8]> for Amount { mod test { use super::*; use crate::core::binarycodec::test_cases::load_data_tests; - use crate::core::types::test_cases::IOUCase; - use crate::core::types::test_cases::TEST_XRP_CASES; + use crate::core::binarycodec::types::test_cases::IOUCase; + use crate::core::binarycodec::types::test_cases::TEST_XRP_CASES; use alloc::format; const IOU_TEST: &str = include_str!("../test_data/iou-tests.json"); diff --git a/src/core/types/blob.rs b/src/core/binarycodec/types/blob.rs similarity index 81% rename from src/core/types/blob.rs rename to src/core/binarycodec/types/blob.rs index 90954396..59b8db88 100644 --- a/src/core/types/blob.rs +++ b/src/core/binarycodec/types/blob.rs @@ -3,14 +3,16 @@ //! See Blob Fields: //! `` -use crate::core::binarycodec::exceptions::XRPLBinaryCodecException; -use crate::core::types::*; +use crate::core::exceptions::{XRPLCoreException, XRPLCoreResult}; use alloc::vec; use alloc::vec::Vec; use core::convert::TryFrom; +use core::fmt::Display; use serde::Serializer; use serde::{Deserialize, Serialize}; +use super::XRPLType; + /// Codec for serializing and deserializing blob fields. /// /// See Blob Fields: @@ -20,9 +22,9 @@ use serde::{Deserialize, Serialize}; pub struct Blob(Vec); impl XRPLType for Blob { - type Error = XRPLBinaryCodecException; + type Error = XRPLCoreException; - fn new(buffer: Option<&[u8]>) -> Result { + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult { if let Some(data) = buffer { Ok(Blob(data.to_vec())) } else { @@ -32,7 +34,7 @@ impl XRPLType for Blob { } impl Serialize for Blob { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: S) -> XRPLCoreResult where S: Serializer, { @@ -41,10 +43,10 @@ impl Serialize for Blob { } impl TryFrom<&str> for Blob { - type Error = XRPLBinaryCodecException; + type Error = XRPLCoreException; /// Construct a Blob from a hex string. - fn try_from(value: &str) -> Result { + fn try_from(value: &str) -> XRPLCoreResult { Self::new(Some(&hex::decode(value)?)) } } diff --git a/src/core/types/currency.rs b/src/core/binarycodec/types/currency.rs similarity index 94% rename from src/core/types/currency.rs rename to src/core/binarycodec/types/currency.rs index fc27c14b..23fd6927 100644 --- a/src/core/types/currency.rs +++ b/src/core/binarycodec/types/currency.rs @@ -1,9 +1,11 @@ //! Codec for currency property inside an XRPL //! issued currency amount json. -use crate::core::types::exceptions::XRPLHashException; -use crate::core::types::utils::CURRENCY_CODE_LENGTH; -use crate::core::types::*; +use super::utils::CURRENCY_CODE_LENGTH; +use super::Hash160; +use super::TryFromParser; +use super::XRPLType; +use crate::core::exceptions::XRPLCoreException; use crate::core::BinaryParser; use crate::utils::exceptions::ISOCodeException; use crate::utils::*; @@ -13,6 +15,7 @@ use alloc::vec; use alloc::vec::Vec; use core::convert::TryFrom; use core::convert::TryInto; +use core::fmt::Display; use serde::Serializer; use serde::{Deserialize, Serialize}; @@ -65,7 +68,7 @@ fn _iso_to_bytes(value: &str) -> Result<[u8; CURRENCY_CODE_LENGTH], ISOCodeExcep } impl XRPLType for Currency { - type Error = XRPLHashException; + type Error = XRPLCoreException; fn new(buffer: Option<&[u8]>) -> Result { let hash160 = Hash160::new(buffer.or(Some(&[0; CURRENCY_CODE_LENGTH])))?; @@ -74,7 +77,7 @@ impl XRPLType for Currency { } impl TryFromParser for Currency { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Build Currency from a BinaryParser. fn from_parser( @@ -96,7 +99,7 @@ impl Serialize for Currency { } impl TryFrom<&str> for Currency { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Construct a Currency object from a string /// representation of a currency. @@ -108,9 +111,7 @@ impl TryFrom<&str> for Currency { } else if is_iso_hex(value) { Ok(Currency(Hash160::new(Some(&hex::decode(value)?))?)) } else { - Err(XRPLHashException::ISOCodeError( - ISOCodeException::UnsupportedCurrencyRepresentation, - )) + Err(ISOCodeException::UnsupportedCurrencyRepresentation.into()) } } } diff --git a/src/core/binarycodec/types/exceptions.rs b/src/core/binarycodec/types/exceptions.rs new file mode 100644 index 00000000..b802b8bc --- /dev/null +++ b/src/core/binarycodec/types/exceptions.rs @@ -0,0 +1,104 @@ +//! Exception for invalid XRP Ledger type data. + +use crate::utils::exceptions::XRPRangeException; +use alloc::string::String; +use thiserror_no_std::Error; + +#[derive(Debug, PartialEq, Error)] +#[non_exhaustive] +pub enum XRPLTypeException { + #[error("Invalid None value")] + InvalidNoneValue, + #[error("Unknown XRPL type")] + UnknownXRPLType, + #[error("Unexpected JSON type")] + UnexpectedJSONType, + #[error("Try from str error")] + TryFromStrError, + #[error("Failed to parse type from issued currency")] + TryFromIssuedCurrencyError, + #[error("XRPL Serialize Map error: {0}")] + XRPLSerializeMapException(#[from] XRPLSerializeMapException), + #[error("XRPL Serialize Array error: {0}")] + XRPLSerializeArrayException(#[from] XRPLSerializeArrayException), + #[error("XRPL Hash error: {0}")] + XRPLHashError(#[from] XRPLHashException), + #[error("XRPL Range error: {0}")] + XRPLRangeError(#[from] XRPRangeException), + #[error("XRPL XChain Bridge error: {0}")] + XRPLXChainBridgeError(#[from] XRPLXChainBridgeException), + #[error("XRPL Vector error: {0}")] + XRPLVectorError(#[from] XRPLVectorException), + #[error("Decimal error: {0}")] + DecimalError(#[from] rust_decimal::Error), + #[error("Big Decimal error: {0}")] + BigDecimalError(#[from] bigdecimal::ParseBigDecimalError), + #[error("Missing field: {0}")] + MissingField(String), + #[error("Parse int error: {0}")] + ParseIntError(#[from] core::num::ParseIntError), +} + +#[derive(Debug, Clone, PartialEq, Error)] +#[non_exhaustive] +pub enum XRPLSerializeArrayException { + #[error("Expected `Value` to be an array.")] + ExpectedArray, + #[error("Expected `Value` to be an array of objects.")] + ExpectedObjectArray, +} + +#[derive(Debug, Clone, PartialEq, Error)] +#[non_exhaustive] +pub enum XRPLSerializeMapException { + #[error("Expected `Value` to be an object.")] + ExpectedObject, + #[error("Field `{field}` is not allowed to have an associated tag.")] + DisallowedTag { field: String }, + #[error("Cannot have mismatched Account X-Address and SourceTag")] + AccountMismatchingTags, + #[error("Cannot have mismatched Destination X-Address and DestinationTag")] + DestinationMismatchingTags, + #[error("Unknown transaction type: {0}")] + UnknownTransactionType(String), + #[error("Unknown transaction result: {0}")] + UnknownTransactionResult(String), + #[error("Unknown ledger entry type: {0}")] + UnknownLedgerEntryType(String), +} + +#[derive(Debug, Clone, PartialEq, Error)] +pub enum XRPLXChainBridgeException { + #[error("Invalid XChainBridge type")] + InvalidXChainBridgeType, +} + +#[derive(Debug, Clone, PartialEq, Error)] +pub enum XRPLHashException { + #[error("Invalid hash length (expected {expected}, found {found})")] + InvalidHashLength { expected: usize, found: usize }, +} + +#[derive(Debug, Clone, PartialEq, Error)] +pub enum XRPLVectorException { + #[error("Invalid vector 256 bytes")] + InvalidVector256Bytes, +} + +#[cfg(feature = "std")] +impl alloc::error::Error for XRPLTypeException {} + +#[cfg(feature = "std")] +impl alloc::error::Error for XRPLSerializeArrayException {} + +#[cfg(feature = "std")] +impl alloc::error::Error for XRPLSerializeMapException {} + +#[cfg(feature = "std")] +impl alloc::error::Error for XRPLXChainBridgeException {} + +#[cfg(feature = "std")] +impl alloc::error::Error for XRPLHashException {} + +#[cfg(feature = "std")] +impl alloc::error::Error for XRPLVectorException {} diff --git a/src/core/types/hash.rs b/src/core/binarycodec/types/hash.rs similarity index 82% rename from src/core/types/hash.rs rename to src/core/binarycodec/types/hash.rs index cfffd4b8..0eb2ce3c 100644 --- a/src/core/types/hash.rs +++ b/src/core/binarycodec/types/hash.rs @@ -3,13 +3,19 @@ //! See Hash Fields: //! `` -use crate::core::types::exceptions::XRPLHashException; -use crate::core::types::utils::*; -use crate::core::types::*; +use super::exceptions::XRPLHashException; +use super::utils::HASH128_LENGTH; +use super::utils::HASH160_LENGTH; +use super::utils::HASH256_LENGTH; +use super::TryFromParser; +use super::XRPLType; +use crate::core::exceptions::XRPLCoreException; +use crate::core::exceptions::XRPLCoreResult; use crate::core::BinaryParser; use crate::core::Parser; use alloc::vec::Vec; use core::convert::TryFrom; +use core::fmt::Display; use serde::Deserialize; /// Codec for serializing and deserializing a hash field @@ -49,7 +55,7 @@ pub struct Hash256(Vec); /// ## Basic usage /// /// ``` -/// use xrpl::core::types::hash::Hash; +/// use xrpl::core::binarycodec::types::hash::Hash; /// /// #[derive(Debug)] /// pub struct Hash256(Vec); @@ -78,9 +84,9 @@ impl dyn Hash { /// ## Basic usage /// /// ``` - /// use xrpl::core::types::exceptions::XRPLHashException; - /// use xrpl::core::types::hash::Hash; - /// use xrpl::core::types::hash::Hash160; + /// use xrpl::core::binarycodec::types::exceptions::XRPLHashException; + /// use xrpl::core::binarycodec::types::hash::Hash; + /// use xrpl::core::binarycodec::types::hash::Hash160; /// /// fn handle_success_case(hash: Vec) { /// assert!(true) @@ -107,7 +113,7 @@ impl dyn Hash { /// Err(e) => handle_hash_error(e), /// }; /// ``` - pub fn make(bytes: Option<&[u8]>) -> Result, XRPLHashException> { + pub fn make(bytes: Option<&[u8]>) -> XRPLCoreResult, XRPLHashException> { let byte_value: &[u8] = bytes.unwrap_or(&[]); let hash_length: usize = T::get_length(); @@ -128,29 +134,30 @@ impl dyn Hash { /// ## Basic usage /// /// ``` - /// use xrpl::core::types::exceptions::XRPLHashException; + /// use xrpl::core::binarycodec::types::exceptions::XRPLHashException; /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; /// use xrpl::core::binarycodec::BinaryParser; - /// use xrpl::core::types::TryFromParser; - /// use xrpl::core::types::hash::Hash; - /// use xrpl::core::types::hash::Hash128; + /// use xrpl::core::binarycodec::types::TryFromParser; + /// use xrpl::core::binarycodec::types::hash::Hash; + /// use xrpl::core::binarycodec::types::hash::Hash128; + /// use xrpl::core::exceptions::{XRPLCoreResult, XRPLCoreException}; /// /// fn handle_success_case(hash: Hash128) { /// // Success Conditions /// assert!(true); /// } /// - /// fn handle_parser_error(error: XRPLHashException) { + /// fn handle_parser_error(error: XRPLCoreException) { /// // Error Conditions /// match error { - /// XRPLHashException::XRPLBinaryCodecError(_e) => assert!(false), + /// XRPLCoreException::XRPLBinaryCodecError(_e) => assert!(false), /// _ => assert!(false), /// } /// } /// /// fn handle_hash128_from_parser(data: &[u8]) { /// let mut parser: BinaryParser = BinaryParser::from(data); - /// let result: Result = + /// let result: XRPLCoreResult = /// Hash128::from_parser(&mut parser, None); /// /// match result { @@ -167,7 +174,7 @@ impl dyn Hash { pub fn parse( parser: &mut BinaryParser, length: Option, - ) -> Result, XRPLHashException> { + ) -> XRPLCoreResult> { let read_length = length.or_else(|| Some(T::get_length())).unwrap(); Ok(parser.read(read_length)?) } @@ -192,88 +199,88 @@ impl Hash for Hash256 { } impl XRPLType for Hash128 { - type Error = XRPLHashException; + type Error = XRPLCoreException; - fn new(buffer: Option<&[u8]>) -> Result { + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult { Ok(Hash128(::make::(buffer)?)) } } impl XRPLType for Hash160 { - type Error = XRPLHashException; + type Error = XRPLCoreException; - fn new(buffer: Option<&[u8]>) -> Result { + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult { Ok(Hash160(::make::(buffer)?)) } } impl XRPLType for Hash256 { - type Error = XRPLHashException; + type Error = XRPLCoreException; - fn new(buffer: Option<&[u8]>) -> Result { + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult { Ok(Hash256(::make::(buffer)?)) } } impl TryFromParser for Hash128 { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Build Hash128 from a BinaryParser. fn from_parser( parser: &mut BinaryParser, length: Option, - ) -> Result { + ) -> XRPLCoreResult { Ok(Hash128(::parse::(parser, length)?)) } } impl TryFromParser for Hash160 { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Build Hash160 from a BinaryParser. fn from_parser( parser: &mut BinaryParser, length: Option, - ) -> Result { + ) -> XRPLCoreResult { Ok(Hash160(::parse::(parser, length)?)) } } impl TryFromParser for Hash256 { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Build Hash256 from a BinaryParser. fn from_parser( parser: &mut BinaryParser, length: Option, - ) -> Result { + ) -> XRPLCoreResult { Ok(Hash256(::parse::(parser, length)?)) } } impl TryFrom<&str> for Hash128 { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Construct a Hash object from a hex string. - fn try_from(value: &str) -> Result { + fn try_from(value: &str) -> XRPLCoreResult { Hash128::new(Some(&hex::decode(value)?)) } } impl TryFrom<&str> for Hash160 { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Construct a Hash object from a hex string. - fn try_from(value: &str) -> Result { + fn try_from(value: &str) -> XRPLCoreResult { Hash160::new(Some(&hex::decode(value)?)) } } impl TryFrom<&str> for Hash256 { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Construct a Hash object from a hex string. - fn try_from(value: &str) -> Result { + fn try_from(value: &str) -> XRPLCoreResult { Hash256::new(Some(&hex::decode(value)?)) } } @@ -322,6 +329,8 @@ impl AsRef<[u8]> for Hash256 { #[cfg(test)] mod test { + use alloc::string::ToString; + use super::*; const HASH128_HEX_TEST: &str = "10000000002000000000300000000012"; diff --git a/src/core/types/issue.rs b/src/core/binarycodec/types/issue.rs similarity index 73% rename from src/core/types/issue.rs rename to src/core/binarycodec/types/issue.rs index 91fa144c..065dee1e 100644 --- a/src/core/types/issue.rs +++ b/src/core/binarycodec/types/issue.rs @@ -2,7 +2,8 @@ use alloc::string::ToString; use serde_json::Value; use crate::core::{ - types::{AccountId, Currency}, + binarycodec::types::{AccountId, Currency}, + exceptions::{XRPLCoreException, XRPLCoreResult}, BinaryParser, Parser, }; @@ -12,9 +13,9 @@ use super::{exceptions::XRPLTypeException, SerializedType, TryFromParser, XRPLTy pub struct Issue(SerializedType); impl XRPLType for Issue { - type Error = XRPLTypeException; + type Error = XRPLCoreException; - fn new(buffer: Option<&[u8]>) -> anyhow::Result + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult where Self: Sized, { @@ -23,9 +24,12 @@ impl XRPLType for Issue { } impl TryFromParser for Issue { - type Error = XRPLTypeException; + type Error = XRPLCoreException; - fn from_parser(parser: &mut BinaryParser, length: Option) -> Result { + fn from_parser( + parser: &mut BinaryParser, + length: Option, + ) -> XRPLCoreResult { let currency = Currency::from_parser(parser, length)?; let mut currency_bytes = currency.as_ref().to_vec(); if currency.to_string() == "XRP" { @@ -40,27 +44,27 @@ impl TryFromParser for Issue { } impl TryFrom for Issue { - type Error = XRPLTypeException; + type Error = XRPLCoreException; - fn try_from(value: Value) -> Result { + fn try_from(value: Value) -> XRPLCoreResult { if value.get("currency") == Some(&Value::String("XRP".to_string())) { let currency = Currency::try_from("XRP")?; Ok(Issue(SerializedType::from(currency.as_ref().to_vec()))) } else if let Some(issued_currency) = value.as_object() { let cur = issued_currency["currency"] .as_str() - .ok_or(XRPLTypeException::MissingField("currency"))?; + .ok_or(XRPLTypeException::MissingField("currency".to_string()))?; let currency = Currency::try_from(cur)?; let issuer = issued_currency["issuer"] .as_str() - .ok_or(XRPLTypeException::MissingField("issuer"))?; + .ok_or(XRPLTypeException::MissingField("issuer".to_string()))?; let account = AccountId::try_from(issuer)?; let mut currency_bytes = currency.as_ref().to_vec(); currency_bytes.extend_from_slice(account.as_ref()); Ok(Issue(SerializedType::from(currency_bytes))) } else { - Err(XRPLTypeException::UnexpectedJSONType) + Err(XRPLTypeException::UnexpectedJSONType.into()) } } } diff --git a/src/core/types/mod.rs b/src/core/binarycodec/types/mod.rs similarity index 78% rename from src/core/types/mod.rs rename to src/core/binarycodec/types/mod.rs index 2b2addc7..bbc58d22 100644 --- a/src/core/types/mod.rs +++ b/src/core/binarycodec/types/mod.rs @@ -35,12 +35,12 @@ pub use self::vector256::Vector256; pub use self::xchain_bridge::XChainBridge; use crate::core::binarycodec::binary_wrappers::Serialization; -use crate::core::definitions::get_field_instance; -use crate::core::definitions::get_transaction_result_code; -use crate::core::definitions::get_transaction_type_code; -use crate::core::definitions::FieldInstance; +use crate::core::binarycodec::definitions::get_field_instance; +use crate::core::binarycodec::definitions::get_transaction_result_code; +use crate::core::binarycodec::definitions::get_transaction_type_code; +use crate::core::binarycodec::definitions::FieldInstance; +use crate::core::exceptions::XRPLCoreResult; use crate::core::BinaryParser; -use crate::Err; use alloc::borrow::Cow; use alloc::borrow::ToOwned; use alloc::string::String; @@ -48,14 +48,14 @@ use alloc::string::ToString; use alloc::vec; use alloc::vec::Vec; use amount::IssuedCurrency; -use anyhow::Result; +use exceptions::XRPLTypeException; use serde::Deserialize; use serde_json::Map; use serde_json::Value; -use super::addresscodec::is_valid_xaddress; -use super::addresscodec::xaddress_to_classic_address; use super::BinarySerializer; +use crate::core::addresscodec::is_valid_xaddress; +use crate::core::addresscodec::xaddress_to_classic_address; const ACCOUNT: &str = "Account"; const SOURCE_TAG: &str = "SourceTag"; @@ -91,7 +91,7 @@ pub enum XRPLTypes { } impl XRPLTypes { - pub fn from_value(name: &str, value: Value) -> Result { + pub fn from_value(name: &str, value: Value) -> XRPLCoreResult { let mut value = value; if value.is_null() { value = Value::Number(0.into()); @@ -106,11 +106,27 @@ impl XRPLTypes { "Hash160" => Ok(XRPLTypes::Hash160(Self::type_from_str(value)?)), "Hash256" => Ok(XRPLTypes::Hash256(Self::type_from_str(value)?)), "XChainClaimID" => Ok(XRPLTypes::Hash256(Self::type_from_str(value)?)), - "UInt8" => Ok(XRPLTypes::UInt8(value.parse::()?)), - "UInt16" => Ok(XRPLTypes::UInt16(value.parse::()?)), - "UInt32" => Ok(XRPLTypes::UInt32(value.parse::()?)), - "UInt64" => Ok(XRPLTypes::UInt64(value.parse::()?)), - _ => Err!(exceptions::XRPLTypeException::UnknownXRPLType), + "UInt8" => Ok(XRPLTypes::UInt8( + value + .parse::() + .map_err(XRPLTypeException::ParseIntError)?, + )), + "UInt16" => Ok(XRPLTypes::UInt16( + value + .parse::() + .map_err(XRPLTypeException::ParseIntError)?, + )), + "UInt32" => Ok(XRPLTypes::UInt32( + value + .parse::() + .map_err(XRPLTypeException::ParseIntError)?, + )), + "UInt64" => Ok(XRPLTypes::UInt64( + value + .parse::() + .map_err(XRPLTypeException::ParseIntError)?, + )), + _ => Err(exceptions::XRPLTypeException::UnknownXRPLType.into()), } } else if let Some(value) = value.as_u64() { match name { @@ -118,7 +134,7 @@ impl XRPLTypes { "UInt16" => Ok(XRPLTypes::UInt16(value as u16)), "UInt32" => Ok(XRPLTypes::UInt32(value as u32)), "UInt64" => Ok(XRPLTypes::UInt64(value)), - _ => Err!(exceptions::XRPLTypeException::UnknownXRPLType), + _ => Err(exceptions::XRPLTypeException::UnknownXRPLType.into()), } } else if let Some(value) = value.as_object() { match name { @@ -127,46 +143,43 @@ impl XRPLTypes { Value::Object(value.to_owned()), false, )?)), - "XChainBridge" => Ok(XRPLTypes::XChainBridge( - XChainBridge::try_from(Value::Object(value.to_owned())) - .map_err(|e| anyhow::anyhow!(e))?, - )), - _ => Err!(exceptions::XRPLTypeException::UnknownXRPLType), + "XChainBridge" => Ok(XRPLTypes::XChainBridge(XChainBridge::try_from( + Value::Object(value.to_owned()), + )?)), + _ => Err(exceptions::XRPLTypeException::UnknownXRPLType.into()), } } else if let Some(value) = value.as_array() { match name { "STArray" => Ok(XRPLTypes::STArray(STArray::try_from_value(Value::Array( value.to_owned(), ))?)), - _ => Err!(exceptions::XRPLTypeException::UnknownXRPLType), + _ => Err(exceptions::XRPLTypeException::UnknownXRPLType.into()), } } else { - Err!(exceptions::XRPLTypeException::UnknownXRPLType) + Err(exceptions::XRPLTypeException::UnknownXRPLType.into()) } } - fn type_from_str<'a, T>(value: &'a str) -> Result + fn type_from_str<'a, T>(value: &'a str) -> XRPLCoreResult where T: TryFrom<&'a str>, >::Error: Display, { - match value.try_into() { - Ok(value) => Ok(value), - Err(error) => Err!(error), - } + value + .try_into() + .map_err(|_| XRPLTypeException::TryFromStrError.into()) } - fn amount_from_map(value: Map) -> Result + fn amount_from_map(value: Map) -> XRPLCoreResult where T: TryFrom, >::Error: Display, { match IssuedCurrency::try_from(Value::Object(value)) { - Ok(value) => match value.try_into() { - Ok(value) => Ok(value), - Err(error) => Err!(error), - }, - Err(error) => Err!(error), + Ok(value) => value + .try_into() + .map_err(|_| XRPLTypeException::TryFromIssuedCurrencyError.into()), + Err(error) => Err(error), } } } @@ -213,7 +226,7 @@ impl STArray { /// Create a SerializedArray from a serde_json::Value. /// /// ``` - /// use xrpl::core::types::STArray; + /// use xrpl::core::binarycodec::types::STArray; /// use serde_json::Value; /// use hex::ToHex; /// @@ -232,18 +245,18 @@ impl STArray { /// /// assert_eq!(actual_hex, expected_hex); /// ``` - pub fn try_from_value(value: Value) -> Result { + pub fn try_from_value(value: Value) -> XRPLCoreResult { if let Some(array) = value.as_array() { if !array.is_empty() && array.iter().filter(|v| v.is_object()).count() != array.len() { - Err!(exceptions::XRPLSerializeArrayException::ExpectedObjectArray) + Err(exceptions::XRPLSerializeArrayException::ExpectedObjectArray.into()) } else { let mut serializer = BinarySerializer::new(); for object in array { let obj = match object { Value::Object(map) => map, _ => { - return Err!( - exceptions::XRPLSerializeArrayException::ExpectedObjectArray + return Err( + exceptions::XRPLSerializeArrayException::ExpectedObjectArray.into(), ) } }; @@ -254,15 +267,15 @@ impl STArray { Ok(STArray(serializer.into())) } } else { - Err!(exceptions::XRPLSerializeArrayException::ExpectedArray) + Err(exceptions::XRPLSerializeArrayException::ExpectedArray.into()) } } } impl XRPLType for STArray { - type Error = anyhow::Error; + type Error = XRPLTypeException; - fn new(buffer: Option<&[u8]>) -> Result { + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult { if let Some(data) = buffer { Ok(STArray(SerializedType(data.to_vec()))) } else { @@ -288,7 +301,7 @@ impl STObject { /// Create a SerializedMap from a serde_json::Value. /// /// ``` - /// use xrpl::core::types::STObject; + /// use xrpl::core::binarycodec::types::STObject; /// /// let expected_json = r#"{ /// "Account": "raD5qJMAShLeHZXf9wjUmo6vRK4arj9cF3", @@ -319,10 +332,10 @@ impl STObject { /// let hex = hex::encode_upper(serialized_map.as_ref()); /// assert_eq!(hex, buffer); /// ``` - pub fn try_from_value(value: Value, signing_only: bool) -> Result { + pub fn try_from_value(value: Value, signing_only: bool) -> XRPLCoreResult { let object = match value { Value::Object(map) => map, - _ => return Err!(exceptions::XRPLSerializeMapException::ExpectedObject), + _ => return Err(exceptions::XRPLSerializeMapException::ExpectedObject.into()), }; let mut serializer = BinarySerializer::new(); let mut value_xaddress_handled = Map::new(); @@ -333,8 +346,9 @@ impl STObject { if let Some(handled_tag) = handled_xaddress.get(SOURCE_TAG) { if let Some(object_tag) = object.get(SOURCE_TAG) { if handled_tag != object_tag { - return Err!( + return Err( exceptions::XRPLSerializeMapException::AccountMismatchingTags + .into(), ); } } @@ -342,8 +356,8 @@ impl STObject { if let Some(handled_tag) = handled_xaddress.get(DESTINATION_TAG) { if let Some(object_tag) = object.get(DESTINATION_TAG) { if handled_tag != object_tag { - return Err!( - exceptions::XRPLSerializeMapException::DestinationMismatchingTags + return Err( + exceptions::XRPLSerializeMapException::DestinationMismatchingTags.into() ); } } @@ -353,10 +367,11 @@ impl STObject { let transaction_type_code = match get_transaction_type_code(value) { Some(code) => code, None => { - return Err!( + return Err( exceptions::XRPLSerializeMapException::UnknownTransactionType( - value + value.to_string(), ) + .into(), ) } }; @@ -365,16 +380,16 @@ impl STObject { Value::Number(transaction_type_code.to_owned().into()), ); } else if field == "TransactionResult" { - let transaction_result_code = match get_transaction_result_code(value) { - Some(code) => code, - None => { - return Err!( + let transaction_result_code = + match get_transaction_result_code(value) { + Some(code) => code, + None => return Err( exceptions::XRPLSerializeMapException::UnknownTransactionResult( - value + value.to_string(), ) - ) - } - }; + .into(), + ), + }; value_xaddress_handled.insert( field.to_owned(), Value::Number(transaction_result_code.to_owned().into()), @@ -383,10 +398,11 @@ impl STObject { let ledger_entry_type_code = match get_transaction_type_code(value) { Some(code) => code, None => { - return Err!( + return Err( exceptions::XRPLSerializeMapException::UnknownLedgerEntryType( - value + value.to_string(), ) + .into(), ) } }; @@ -421,13 +437,9 @@ impl STObject { let mut is_unl_modify = false; for field_instance in &sorted_keys { - let associated_value = match value_xaddress_handled.get(&field_instance.name) { - Some(value) => value, - None => Err(anyhow::anyhow!( - "Error prossessing field: {}", - field_instance.name - ))?, - }; + let associated_value = value_xaddress_handled.get(&field_instance.name).ok_or( + exceptions::XRPLTypeException::MissingField(field_instance.name.clone()), + )?; let associated_value = XRPLTypes::from_value( &field_instance.associated_type, associated_value.to_owned(), @@ -455,9 +467,9 @@ impl STObject { } impl XRPLType for STObject { - type Error = anyhow::Error; + type Error = XRPLTypeException; - fn new(buffer: Option<&[u8]>) -> Result { + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult { if let Some(data) = buffer { Ok(STObject(SerializedType(data.to_vec()))) } else { @@ -472,11 +484,8 @@ impl AsRef<[u8]> for STObject { } } -fn handle_xaddress(field: Cow, xaddress: Cow) -> Result> { - let (classic_address, tag, _is_test_net) = match xaddress_to_classic_address(&xaddress) { - Ok((classic_address, tag, is_test_net)) => (classic_address, tag, is_test_net), - Err(e) => return Err!(e), - }; +fn handle_xaddress(field: Cow, xaddress: Cow) -> XRPLCoreResult> { + let (classic_address, tag, _is_test_net) = xaddress_to_classic_address(&xaddress)?; if let Some(tag) = tag { if field == DESTINATION { let tag_name = DESTINATION_TAG; @@ -491,7 +500,10 @@ fn handle_xaddress(field: Cow, xaddress: Cow) -> Result, xaddress: Cow) -> Result); /// /// impl XRPLType for Example { /// type Error = XRPLBinaryCodecException; /// -/// fn new(buffer: Option<&[u8]>) -> Result { +/// fn new(buffer: Option<&[u8]>) -> XRPLCoreResult { /// if let Some(data) = buffer { /// Ok(Example(data.to_vec())) /// } else { @@ -530,7 +543,7 @@ pub trait XRPLType { type Error; /// Create a new instance of a type. - fn new(buffer: Option<&[u8]>) -> Result + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult where Self: Sized; } @@ -542,20 +555,20 @@ pub trait XRPLType { /// ## Basic usage /// /// ``` -/// use xrpl::core::types::TryFromParser; +/// use xrpl::core::binarycodec::types::TryFromParser; /// use xrpl::core::binarycodec::BinaryParser; /// use xrpl::core::Parser; -/// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; +/// use xrpl::core::exceptions::{XRPLCoreResult, XRPLCoreException}; /// /// pub struct Example(Vec); /// /// impl TryFromParser for Example { -/// type Error = XRPLBinaryCodecException; +/// type Error = XRPLCoreException; /// /// fn from_parser( /// parser: &mut BinaryParser, /// _length: Option, -/// ) -> Result { +/// ) -> XRPLCoreResult { /// Ok(Example(parser.read(42)?)) /// } /// } @@ -565,7 +578,10 @@ pub trait TryFromParser { type Error; /// Construct a type from a BinaryParser. - fn from_parser(parser: &mut BinaryParser, length: Option) -> Result + fn from_parser( + parser: &mut BinaryParser, + length: Option, + ) -> XRPLCoreResult where Self: Sized; } diff --git a/src/core/types/paths.rs b/src/core/binarycodec/types/paths.rs similarity index 86% rename from src/core/types/paths.rs rename to src/core/binarycodec/types/paths.rs index 6248b293..4e0558b7 100644 --- a/src/core/types/paths.rs +++ b/src/core/binarycodec/types/paths.rs @@ -5,11 +5,13 @@ use crate::constants::ACCOUNT_ID_LENGTH; use crate::core::binarycodec::exceptions::XRPLBinaryCodecException; -use crate::core::types::exceptions::XRPLHashException; -use crate::core::types::utils::CURRENCY_CODE_LENGTH; -use crate::core::types::*; +use crate::core::binarycodec::types::utils::CURRENCY_CODE_LENGTH; +use crate::core::binarycodec::types::*; +use crate::core::exceptions::XRPLCoreException; +use crate::core::exceptions::XRPLCoreResult; use crate::core::BinaryParser; use crate::core::Parser; +use crate::XRPLSerdeJsonError; use alloc::borrow::ToOwned; use alloc::string::String; use alloc::string::ToString; @@ -73,9 +75,9 @@ fn _is_path_set(value: &[Vec>]) -> bool { } impl XRPLType for PathStep { - type Error = XRPLBinaryCodecException; + type Error = XRPLCoreException; - fn new(buffer: Option<&[u8]>) -> Result { + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult { if let Some(data) = buffer { Ok(PathStep(data.to_vec())) } else { @@ -85,9 +87,9 @@ impl XRPLType for PathStep { } impl XRPLType for Path { - type Error = XRPLBinaryCodecException; + type Error = XRPLCoreException; - fn new(buffer: Option<&[u8]>) -> Result { + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult { if let Some(data) = buffer { Ok(Path(data.to_vec())) } else { @@ -97,9 +99,9 @@ impl XRPLType for Path { } impl XRPLType for PathSet { - type Error = XRPLBinaryCodecException; + type Error = XRPLCoreException; - fn new(buffer: Option<&[u8]>) -> Result { + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult { if let Some(data) = buffer { Ok(PathSet(data.to_vec())) } else { @@ -121,13 +123,13 @@ impl PathStepData { } impl TryFromParser for PathStep { - type Error = XRPLBinaryCodecException; + type Error = XRPLCoreException; /// Build PathStep from a BinaryParser. fn from_parser( parser: &mut BinaryParser, _length: Option, - ) -> Result { + ) -> XRPLCoreResult { let mut value_bytes: Vec = vec![]; let mut buffer: Vec = vec![]; let data_type = parser.read_uint8()?; @@ -152,13 +154,13 @@ impl TryFromParser for PathStep { } impl TryFromParser for PathStepData { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Build PathStepData from a BinaryParser. fn from_parser( parser: &mut BinaryParser, _length: Option, - ) -> Result { + ) -> XRPLCoreResult { let data_type = parser.read_uint8()?; let account: Option = if data_type & _TYPE_ACCOUNT != 0 { @@ -187,10 +189,13 @@ impl TryFromParser for PathStepData { } impl TryFromParser for Path { - type Error = XRPLBinaryCodecException; + type Error = XRPLCoreException; /// Build Path from a BinaryParser. - fn from_parser(parser: &mut BinaryParser, _length: Option) -> Result { + fn from_parser( + parser: &mut BinaryParser, + _length: Option, + ) -> XRPLCoreResult { let mut buffer: Vec = vec![]; while !parser.is_end(None) { @@ -209,13 +214,13 @@ impl TryFromParser for Path { } impl TryFromParser for PathSet { - type Error = XRPLBinaryCodecException; + type Error = XRPLCoreException; /// Build PathSet from a BinaryParser. fn from_parser( parser: &mut BinaryParser, _length: Option, - ) -> Result { + ) -> XRPLCoreResult { let mut buffer: Vec = vec![]; while !parser.is_end(None) { @@ -237,7 +242,7 @@ impl TryFromParser for PathSet { impl Serialize for PathStep { /// Returns the JSON representation of a PathStep. - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: S) -> XRPLCoreResult where S: Serializer, { @@ -265,7 +270,7 @@ impl Serialize for PathStep { impl Serialize for Path { /// Returns the JSON representation of a Path. - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: S) -> XRPLCoreResult where S: Serializer, { @@ -288,7 +293,7 @@ impl Serialize for Path { impl Serialize for PathSet { /// Returns the JSON representation of a Path. - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: S) -> XRPLCoreResult where S: Serializer, { @@ -314,10 +319,10 @@ impl Serialize for PathSet { } impl TryFrom> for PathStep { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Construct a PathStep object from a dictionary. - fn try_from(value: IndexMap) -> Result { + fn try_from(value: IndexMap) -> XRPLCoreResult { let mut value_bytes: Vec = vec![]; let mut data_type = 0x00; let mut buffer = vec![]; @@ -351,10 +356,10 @@ impl TryFrom> for PathStep { } impl TryFrom>> for Path { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Construct a Path object from a list. - fn try_from(value: Vec>) -> Result { + fn try_from(value: Vec>) -> XRPLCoreResult { let mut buffer: Vec = vec![]; for step in value { @@ -367,10 +372,10 @@ impl TryFrom>> for Path { } impl TryFrom>>> for PathSet { - type Error = XRPLBinaryCodecException; + type Error = XRPLCoreException; /// Construct a PathSet object from a list. - fn try_from(value: Vec>>) -> Result { + fn try_from(value: Vec>>) -> XRPLCoreResult { if _is_path_set(&value) { let mut buffer: Vec = vec![]; @@ -381,7 +386,7 @@ impl TryFrom>>> for PathSet { buffer.extend_from_slice(path.as_ref()); buffer.extend_from_slice(&[_PATH_SEPARATOR_BYTE; 1]); } else { - return Err(XRPLBinaryCodecException::InvalidPathSetFromValue); + return Err(XRPLBinaryCodecException::InvalidPathSetFromValue.into()); } } @@ -390,37 +395,40 @@ impl TryFrom>>> for PathSet { PathSet::new(Some(&buffer)) } else { - Err(XRPLBinaryCodecException::InvalidPathSetFromValue) + Err(XRPLBinaryCodecException::InvalidPathSetFromValue.into()) } } } impl TryFrom<&str> for Path { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Construct a Path object from a string. - fn try_from(value: &str) -> Result { - let json: Vec> = serde_json::from_str(value)?; + fn try_from(value: &str) -> XRPLCoreResult { + let json: Vec> = + serde_json::from_str(value).map_err(XRPLSerdeJsonError::from)?; Self::try_from(json) } } impl TryFrom<&str> for PathStep { - type Error = XRPLHashException; + type Error = XRPLCoreException; /// Construct a PathSet object from a string. - fn try_from(value: &str) -> Result { - let json: IndexMap = serde_json::from_str(value)?; + fn try_from(value: &str) -> XRPLCoreResult { + let json: IndexMap = + serde_json::from_str(value).map_err(XRPLSerdeJsonError::from)?; Self::try_from(json) } } impl TryFrom<&str> for PathSet { - type Error = XRPLBinaryCodecException; + type Error = XRPLCoreException; /// Construct a PathSet object from a string. - fn try_from(value: &str) -> Result { - let json: Vec>> = serde_json::from_str(value)?; + fn try_from(value: &str) -> XRPLCoreResult { + let json: Vec>> = + serde_json::from_str(value).map_err(XRPLSerdeJsonError::from)?; Self::try_from(json) } } @@ -471,9 +479,9 @@ impl Iterator for PathStepData { #[cfg(test)] mod test { use super::*; - use crate::core::types::test_cases::TEST_PATH_BUFFER; - use crate::core::types::test_cases::TEST_PATH_SET_BUFFER; - use crate::core::types::test_cases::TEST_PATH_STEP_BUFFER; + use crate::core::binarycodec::types::test_cases::TEST_PATH_BUFFER; + use crate::core::binarycodec::types::test_cases::TEST_PATH_SET_BUFFER; + use crate::core::binarycodec::types::test_cases::TEST_PATH_STEP_BUFFER; pub const PATH_SET_TEST: &str = include_str!("../test_data/path-set-test.json"); pub const PATH_TEST: &str = include_str!("../test_data/path-test.json"); diff --git a/src/core/types/test_cases.rs b/src/core/binarycodec/types/test_cases.rs similarity index 97% rename from src/core/types/test_cases.rs rename to src/core/binarycodec/types/test_cases.rs index c8dda4b5..690e3a34 100644 --- a/src/core/types/test_cases.rs +++ b/src/core/binarycodec/types/test_cases.rs @@ -1,4 +1,4 @@ -use crate::core::types::amount::IssuedCurrency; +use crate::core::binarycodec::types::amount::IssuedCurrency; use alloc::string::String; use serde::Deserialize; diff --git a/src/core/types/utils.rs b/src/core/binarycodec/types/utils.rs similarity index 100% rename from src/core/types/utils.rs rename to src/core/binarycodec/types/utils.rs diff --git a/src/core/types/vector256.rs b/src/core/binarycodec/types/vector256.rs similarity index 86% rename from src/core/types/vector256.rs rename to src/core/binarycodec/types/vector256.rs index d1d56128..06f9b3da 100644 --- a/src/core/types/vector256.rs +++ b/src/core/binarycodec/types/vector256.rs @@ -1,9 +1,11 @@ //! Codec for serializing and deserializing //! vectors of Hash256. -use crate::core::types::exceptions::XRPLVectorException; -use crate::core::types::hash::Hash256; -use crate::core::types::*; +use crate::core::binarycodec::types::exceptions::XRPLVectorException; +use crate::core::binarycodec::types::hash::Hash256; +use crate::core::binarycodec::types::*; +use crate::core::exceptions::XRPLCoreException; +use crate::core::exceptions::XRPLCoreResult; use crate::core::BinaryParser; use alloc::vec; use alloc::vec::Vec; @@ -24,7 +26,7 @@ pub struct Vector256(Vec); impl XRPLType for Vector256 { type Error = XRPLVectorException; - fn new(buffer: Option<&[u8]>) -> Result { + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult { if let Some(data) = buffer { Ok(Vector256(data.to_vec())) } else { @@ -34,13 +36,13 @@ impl XRPLType for Vector256 { } impl TryFromParser for Vector256 { - type Error = XRPLVectorException; + type Error = XRPLCoreException; /// Build Vector256 from a BinaryParser. fn from_parser( parser: &mut BinaryParser, length: Option, - ) -> Result { + ) -> XRPLCoreResult { let mut bytes = vec![]; let num_bytes: usize = if let Some(value) = length { @@ -60,7 +62,7 @@ impl TryFromParser for Vector256 { } impl Serialize for Vector256 { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: S) -> XRPLCoreResult where S: Serializer, { @@ -80,10 +82,10 @@ impl Serialize for Vector256 { } impl TryFrom> for Vector256 { - type Error = XRPLVectorException; + type Error = XRPLCoreException; /// Construct a Vector256 from a list of strings. - fn try_from(value: Vec<&str>) -> Result { + fn try_from(value: Vec<&str>) -> XRPLCoreResult { let mut bytes = vec![]; for string in value { diff --git a/src/core/types/xchain_bridge.rs b/src/core/binarycodec/types/xchain_bridge.rs similarity index 79% rename from src/core/types/xchain_bridge.rs rename to src/core/binarycodec/types/xchain_bridge.rs index 6d491d9b..1ddace2f 100644 --- a/src/core/types/xchain_bridge.rs +++ b/src/core/binarycodec/types/xchain_bridge.rs @@ -2,11 +2,14 @@ use alloc::vec::Vec; use serde::Deserialize; use serde_json::Value; -use crate::core::{BinaryParser, Parser}; +use crate::core::{ + exceptions::{XRPLCoreException, XRPLCoreResult}, + BinaryParser, Parser, +}; use super::{ - exceptions::{XRPLTypeException, XRPLXChainBridgeException}, - AccountId, Issue, SerializedType, TryFromParser, XRPLType, + exceptions::XRPLXChainBridgeException, AccountId, Issue, SerializedType, TryFromParser, + XRPLType, }; const TYPE_ORDER: [[&str; 2]; 4] = [ @@ -20,9 +23,9 @@ const TYPE_ORDER: [[&str; 2]; 4] = [ pub struct XChainBridge(SerializedType); impl XRPLType for XChainBridge { - type Error = XRPLTypeException; + type Error = XRPLCoreException; - fn new(buffer: Option<&[u8]>) -> anyhow::Result + fn new(buffer: Option<&[u8]>) -> XRPLCoreResult where Self: Sized, { @@ -35,9 +38,12 @@ impl XRPLType for XChainBridge { } impl TryFromParser for XChainBridge { - type Error = XRPLTypeException; + type Error = XRPLCoreException; - fn from_parser(parser: &mut BinaryParser, length: Option) -> Result { + fn from_parser( + parser: &mut BinaryParser, + length: Option, + ) -> XRPLCoreResult { let mut buf = Vec::new(); for [_, object_type] in TYPE_ORDER { if object_type == "AccountID" { @@ -62,9 +68,9 @@ impl TryFromParser for XChainBridge { } impl TryFrom for XChainBridge { - type Error = XRPLTypeException; + type Error = XRPLCoreException; - fn try_from(value: Value) -> Result { + fn try_from(value: Value) -> XRPLCoreResult { if !value.is_object() { return Err(XRPLXChainBridgeException::InvalidXChainBridgeType.into()); } @@ -92,9 +98,9 @@ impl TryFrom for XChainBridge { } impl TryFrom<&str> for XChainBridge { - type Error = XRPLTypeException; + type Error = XRPLCoreException; - fn try_from(value: &str) -> Result { + fn try_from(value: &str) -> XRPLCoreResult { Ok(XChainBridge(SerializedType::from(hex::decode(value)?))) } } diff --git a/src/core/binarycodec/utils.rs b/src/core/binarycodec/utils.rs index d2190cb4..1689449a 100644 --- a/src/core/binarycodec/utils.rs +++ b/src/core/binarycodec/utils.rs @@ -1,11 +1,12 @@ //! Utilities for binarycodec crate. +use super::definitions::load_definition_map; +use super::definitions::DefinitionHandler; +use super::definitions::FieldHeader; +use super::definitions::CODE_MAX_VALUE; +use super::definitions::CODE_MIN_VALUE; use crate::core::binarycodec::exceptions::XRPLBinaryCodecException; -use crate::core::definitions::load_definition_map; -use crate::core::definitions::DefinitionHandler; -use crate::core::definitions::FieldHeader; -use crate::core::definitions::CODE_MAX_VALUE; -use crate::core::definitions::CODE_MIN_VALUE; +use crate::core::exceptions::XRPLCoreResult; use alloc::vec; use alloc::vec::Vec; @@ -29,7 +30,7 @@ pub const MAX_LENGTH_VALUE: usize = 918744; pub const MAX_BYTE_VALUE: usize = 256; /// See: `` -fn _encode_field_id(field_header: &FieldHeader) -> Result, XRPLBinaryCodecException> { +fn _encode_field_id(field_header: &FieldHeader) -> XRPLCoreResult> { let type_code = field_header.type_code; let field_code = field_header.field_code; let range = CODE_MIN_VALUE..CODE_MAX_VALUE; @@ -38,12 +39,14 @@ fn _encode_field_id(field_header: &FieldHeader) -> Result, XRPLBinaryCod Err(XRPLBinaryCodecException::UnexpectedFieldCodeRange { min: CODE_MIN_VALUE as usize, max: CODE_MAX_VALUE as usize, - }) + } + .into()) } else if !range.contains(&type_code) { Err(XRPLBinaryCodecException::UnexpectedTypeCodeRange { min: CODE_MIN_VALUE as usize, max: CODE_MAX_VALUE as usize, - }) + } + .into()) } else if type_code < 16 && field_code < 16 { // high 4 bits is the type_code // low 4 bits is the field code @@ -91,7 +94,7 @@ fn _encode_field_id(field_header: &FieldHeader) -> Result, XRPLBinaryCod } /// See: `` -fn _decode_field_id(field_id: &str) -> Result { +fn _decode_field_id(field_id: &str) -> XRPLCoreResult { let bytes = hex::decode(field_id)?; match bytes.len() { @@ -141,7 +144,7 @@ fn _decode_field_id(field_id: &str) -> Result Err(XRPLBinaryCodecException::UnexpectedFieldIdByteRange { min: 1, max: 3 }), + _ => Err(XRPLBinaryCodecException::UnexpectedFieldIdByteRange { min: 1, max: 3 }.into()), } } @@ -160,6 +163,7 @@ fn _decode_field_id(field_id: &str) -> Result Result> = match encode_field_name(field_name) { /// Ok(bytes) => Some(bytes), /// Err(e) => match e { -/// XRPLBinaryCodecException::UnknownFieldName => None, +/// XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::UnknownFieldName) => None, /// _ => None, /// } /// }; /// /// assert_eq!(Some(bytes), encoding); /// ``` -pub fn encode_field_name(field_name: &str) -> Result, XRPLBinaryCodecException> { +pub fn encode_field_name(field_name: &str) -> XRPLCoreResult> { let definitions = load_definition_map(); let field_header = definitions.get_field_header_from_name(field_name); if let Some(header) = field_header { _encode_field_id(&header) } else { - Err(XRPLBinaryCodecException::UnknownFieldName) + Err(XRPLBinaryCodecException::UnknownFieldName.into()) } } @@ -198,6 +202,7 @@ pub fn encode_field_name(field_name: &str) -> Result, XRPLBinaryCodecExc /// ``` /// use xrpl::core::binarycodec::utils::decode_field_name; /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; +/// use xrpl::core::exceptions::XRPLCoreException; /// /// let field_id: &str = "26"; /// let field_name: &str = "LedgerSequence"; @@ -205,17 +210,17 @@ pub fn encode_field_name(field_name: &str) -> Result, XRPLBinaryCodecExc /// let decoding: Option<&str> = match decode_field_name(field_id) { /// Ok(field_name) => Some(field_name), /// Err(e) => match e { -/// XRPLBinaryCodecException::UnexpectedFieldIdByteRange { +/// XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::UnexpectedFieldIdByteRange { /// min: _, /// max: _ -/// } => None, +/// }) => None, /// _ => None, /// } /// }; /// /// assert_eq!(Some(field_name), decoding); /// ``` -pub fn decode_field_name(field_id: &str) -> Result<&str, XRPLBinaryCodecException> { +pub fn decode_field_name(field_id: &str) -> XRPLCoreResult<&str> { let definitions = load_definition_map(); let field_header = _decode_field_id(field_id)?; let field_name = definitions.get_field_name_from_header(&field_header); @@ -223,7 +228,7 @@ pub fn decode_field_name(field_id: &str) -> Result<&str, XRPLBinaryCodecExceptio if let Some(name) = field_name { Ok(name) } else { - Err(XRPLBinaryCodecException::UnknownFieldName) + Err(XRPLBinaryCodecException::UnknownFieldName.into()) } } diff --git a/src/core/exceptions.rs b/src/core/exceptions.rs new file mode 100644 index 00000000..a84f929f --- /dev/null +++ b/src/core/exceptions.rs @@ -0,0 +1,88 @@ +use alloc::string::String; +use thiserror_no_std::Error; + +use crate::{utils::exceptions::ISOCodeException, XRPLSerdeJsonError}; + +use super::{ + addresscodec::exceptions::XRPLAddressCodecException, + binarycodec::{ + exceptions::XRPLBinaryCodecException, + types::exceptions::{ + XRPLHashException, XRPLSerializeArrayException, XRPLSerializeMapException, + XRPLTypeException, XRPLVectorException, XRPLXChainBridgeException, + }, + }, + keypairs::exceptions::XRPLKeypairsException, +}; + +pub type XRPLCoreResult = core::result::Result; + +#[derive(Debug, PartialEq, Error)] +#[non_exhaustive] +pub enum XRPLCoreException { + #[error("XRPL Address Codec error: {0}")] + XRPLAddressCodecError(#[from] XRPLAddressCodecException), + #[error("XRPL Binary Codec error: {0}")] + XRPLBinaryCodecError(#[from] XRPLBinaryCodecException), + #[error("XRPL Keypairs error: {0}")] + XRPLKeypairsError(#[from] XRPLKeypairsException), + #[error("serde_json error: {0}")] + SerdeJsonError(#[from] XRPLSerdeJsonError), + #[error("XRPL utils error: {0}")] + XRPLUtilsError(String), // TODO: find a better way to avoid infinite recursion + #[error("From hex error: {0}")] + FromHexError(#[from] hex::FromHexError), + #[error("ISO code error: {0}")] + ISOCodeError(#[from] ISOCodeException), + #[error("Base58 error: {0}")] + Bs58Error(#[from] bs58::decode::Error), +} + +impl From for XRPLCoreException { + fn from(error: XRPLTypeException) -> Self { + XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::XRPLTypeError(error)) + } +} + +impl From for XRPLCoreException { + fn from(error: XRPLSerializeArrayException) -> Self { + XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::XRPLTypeError( + XRPLTypeException::XRPLSerializeArrayException(error), + )) + } +} + +impl From for XRPLCoreException { + fn from(error: XRPLSerializeMapException) -> Self { + XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::XRPLTypeError( + XRPLTypeException::XRPLSerializeMapException(error), + )) + } +} + +impl From for XRPLCoreException { + fn from(error: XRPLXChainBridgeException) -> Self { + XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::XRPLTypeError( + XRPLTypeException::XRPLXChainBridgeError(error), + )) + } +} + +impl From for XRPLCoreException { + fn from(error: XRPLHashException) -> Self { + XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::XRPLTypeError( + XRPLTypeException::XRPLHashError(error), + )) + } +} + +impl From for XRPLCoreException { + fn from(error: XRPLVectorException) -> Self { + XRPLCoreException::XRPLBinaryCodecError(XRPLBinaryCodecException::XRPLTypeError( + XRPLTypeException::XRPLVectorError(error), + )) + } +} + +#[cfg(feature = "std")] +impl alloc::error::Error for XRPLCoreException {} diff --git a/src/core/keypairs/algorithms.rs b/src/core/keypairs/algorithms.rs index 0dc47a03..471cf77c 100644 --- a/src/core/keypairs/algorithms.rs +++ b/src/core/keypairs/algorithms.rs @@ -8,6 +8,7 @@ //! `` use crate::constants::CryptoAlgorithm; +use crate::core::exceptions::XRPLCoreResult; use crate::core::keypairs::exceptions::XRPLKeypairsException; use crate::core::keypairs::utils::*; use crate::core::keypairs::CryptoImplementation; @@ -91,10 +92,11 @@ impl Secp256k1 { fn _derive_part( bytes: &[u8], phase: Secp256k1Phase, - ) -> Result<(secp256k1::PublicKey, secp256k1::SecretKey), XRPLKeypairsException> { + ) -> XRPLCoreResult<(secp256k1::PublicKey, secp256k1::SecretKey)> { let raw_private = Self::_get_secret(bytes, &phase)?; let secp = secp256k1::Secp256k1::new(); - let wrapped_private = secp256k1::SecretKey::from_slice(&raw_private)?; + let wrapped_private = secp256k1::SecretKey::from_slice(&raw_private) + .map_err(XRPLKeypairsException::SECP256K1Error)?; let wrapped_public = secp256k1::PublicKey::from_secret_key(&secp, &wrapped_private); Ok((wrapped_public, wrapped_private)) @@ -106,9 +108,13 @@ impl Secp256k1 { root_private: secp256k1::SecretKey, mid_public: secp256k1::PublicKey, mid_private: secp256k1::SecretKey, - ) -> Result<(secp256k1::PublicKey, secp256k1::SecretKey), XRPLKeypairsException> { - let wrapped_private = root_private.add_tweak(&Scalar::from(mid_private))?; - let wrapped_public = root_public.combine(&mid_public)?; + ) -> XRPLCoreResult<(secp256k1::PublicKey, secp256k1::SecretKey)> { + let wrapped_private = root_private + .add_tweak(&Scalar::from(mid_private)) + .map_err(XRPLKeypairsException::SECP256K1Error)?; + let wrapped_public = root_public + .combine(&mid_public) + .map_err(XRPLKeypairsException::SECP256K1Error)?; Ok((wrapped_public, wrapped_private)) } @@ -122,7 +128,7 @@ impl Secp256k1 { fn _get_secret( input: &[u8], phase: &Secp256k1Phase, - ) -> Result<[u8; SHA512_HASH_LENGTH], XRPLKeypairsException> { + ) -> XRPLCoreResult<[u8; SHA512_HASH_LENGTH]> { for raw_root in 0..SECP256K1_SEQUENCE_MAX { let root = (raw_root as u32).to_be_bytes(); let candidate = sha512_first_half(&Self::_candidate_merger(input, &root, phase)); @@ -134,7 +140,7 @@ impl Secp256k1 { } } - Err(XRPLKeypairsException::InvalidSecret) + Err(XRPLKeypairsException::InvalidSecret.into()) } } @@ -179,6 +185,7 @@ impl CryptoImplementation for Secp256k1 { /// use xrpl::core::keypairs::Secp256k1; /// use xrpl::core::keypairs::exceptions::XRPLKeypairsException; /// use xrpl::core::keypairs::CryptoImplementation; + /// use xrpl::core::exceptions::XRPLCoreException; /// /// let decoded_seed: &[u8] = &[ /// 207, 45, 227, 120, 251, 221, 126, 46, @@ -196,9 +203,9 @@ impl CryptoImplementation for Secp256k1 { /// ) { /// Ok((public, private)) => Some((public, private)), /// Err(e) => match e { - /// XRPLKeypairsException::InvalidSignature => None, - /// XRPLKeypairsException::InvalidSecret => None, - /// XRPLKeypairsException::SECP256K1Error => None, + /// XRPLCoreException::XRPLKeypairsError(XRPLKeypairsException::InvalidSignature) => None, + /// XRPLCoreException::XRPLKeypairsError(XRPLKeypairsException::InvalidSecret) => None, + /// XRPLCoreException::XRPLKeypairsError(XRPLKeypairsException::SECP256K1Error(_)) => None, /// _ => None, /// }, /// }; @@ -209,7 +216,7 @@ impl CryptoImplementation for Secp256k1 { &self, decoded_seed: &[u8], is_validator: bool, - ) -> Result<(String, String), XRPLKeypairsException> { + ) -> XRPLCoreResult<(String, String)> { let (root_public, root_secret) = Self::_derive_part(decoded_seed, Secp256k1Phase::Root)?; if is_validator { Ok(Secp256k1::_format_keys(root_public, root_secret)) @@ -235,6 +242,7 @@ impl CryptoImplementation for Secp256k1 { /// use xrpl::core::keypairs::Secp256k1; /// use xrpl::core::keypairs::exceptions::XRPLKeypairsException; /// use xrpl::core::keypairs::CryptoImplementation; + /// use xrpl::core::exceptions::XRPLCoreException; /// /// let message: &[u8] = "test message".as_bytes(); /// let private_key: &str = "00D78B9735C3F26501C7337B8A5727FD5\ @@ -254,22 +262,19 @@ impl CryptoImplementation for Secp256k1 { /// ) { /// Ok(signature) => Some(signature), /// Err(e) => match e { - /// XRPLKeypairsException::SECP256K1Error => None, + /// XRPLCoreException::XRPLKeypairsError(XRPLKeypairsException::SECP256K1Error(_)) => None, /// _ => None, /// }, /// }; /// /// assert_eq!(Some(signature), signing); /// ``` - fn sign( - &self, - message_bytes: &[u8], - private_key: &str, - ) -> Result, XRPLKeypairsException> { + fn sign(&self, message_bytes: &[u8], private_key: &str) -> XRPLCoreResult> { let secp = secp256k1::Secp256k1::::signing_only(); let message = Self::_get_message(message_bytes); let trimmed_key = private_key.trim_start_matches(SECP256K1_PREFIX); - let private = secp256k1::SecretKey::from_str(trimmed_key)?; + let private = secp256k1::SecretKey::from_str(trimmed_key) + .map_err(XRPLKeypairsException::SECP256K1Error)?; let signature = secp.sign_ecdsa(&message, &private); Ok(signature.serialize_der().to_vec()) @@ -332,6 +337,7 @@ impl CryptoImplementation for Ed25519 { /// use xrpl::core::keypairs::Ed25519; /// use xrpl::core::keypairs::exceptions::XRPLKeypairsException; /// use xrpl::core::keypairs::CryptoImplementation; + /// use xrpl::core::exceptions::XRPLCoreException; /// /// let decoded_seed: &[u8] = &[ /// 207, 45, 227, 120, 251, 221, 126, 46, @@ -349,9 +355,9 @@ impl CryptoImplementation for Ed25519 { /// ) { /// Ok((public, private)) => Some((public, private)), /// Err(e) => match e { - /// XRPLKeypairsException::InvalidSignature => None, - /// XRPLKeypairsException::ED25519Error => None, - /// XRPLKeypairsException::UnsupportedValidatorAlgorithm { expected: _ } => None, + /// XRPLCoreException::XRPLKeypairsError(XRPLKeypairsException::InvalidSignature) => None, + /// XRPLCoreException::XRPLKeypairsError(XRPLKeypairsException::ED25519Error) => None, + /// XRPLCoreException::XRPLKeypairsError(XRPLKeypairsException::UnsupportedValidatorAlgorithm { expected: _ }) => None, /// _ => None, /// }, /// }; @@ -362,11 +368,12 @@ impl CryptoImplementation for Ed25519 { &self, decoded_seed: &[u8], is_validator: bool, - ) -> Result<(String, String), XRPLKeypairsException> { + ) -> XRPLCoreResult<(String, String)> { if is_validator { Err(XRPLKeypairsException::UnsupportedValidatorAlgorithm { expected: CryptoAlgorithm::ED25519, - }) + } + .into()) } else { let raw_private = sha512_first_half(decoded_seed); let private: [u8; SECRET_KEY_LENGTH] = ed25519_dalek::SecretKey::from(raw_private); @@ -408,7 +415,7 @@ impl CryptoImplementation for Ed25519 { /// /// assert_eq!(Some(signature), signing); /// ``` - fn sign(&self, message: &[u8], private_key: &str) -> Result, XRPLKeypairsException> { + fn sign(&self, message: &[u8], private_key: &str) -> XRPLCoreResult> { let raw_private = hex::decode(&private_key[ED25519_PREFIX.len()..])?; let raw_private_slice: &[u8; SECRET_KEY_LENGTH] = raw_private .as_slice() diff --git a/src/core/keypairs/exceptions.rs b/src/core/keypairs/exceptions.rs index c72a10d9..2bda1112 100644 --- a/src/core/keypairs/exceptions.rs +++ b/src/core/keypairs/exceptions.rs @@ -1,24 +1,30 @@ //! XRPL keypair codec exceptions. +use thiserror_no_std::Error; + use crate::constants::CryptoAlgorithm; use crate::core::addresscodec::exceptions::XRPLAddressCodecException; -use strum_macros::Display; -#[derive(Debug, PartialEq, Display)] +#[derive(Debug, PartialEq, Error)] #[non_exhaustive] pub enum XRPLKeypairsException { + #[error("Invalid signature")] InvalidSignature, + #[error("Invalid secret")] InvalidSecret, + #[error("Unsupported validator algorithm: {expected:?}")] UnsupportedValidatorAlgorithm { expected: CryptoAlgorithm }, + #[error("ed25519 error")] ED25519Error, - SECP256K1Error, - FromHexError, - AddressCodecException(XRPLAddressCodecException), + #[error("secp256k1 error: {0:?}")] + SECP256K1Error(#[from] secp256k1::Error), + #[error("XRPL Address codec error: {0}")] + XRPLAddressCodecError(XRPLAddressCodecException), } impl From for XRPLKeypairsException { fn from(err: XRPLAddressCodecException) -> Self { - XRPLKeypairsException::AddressCodecException(err) + XRPLKeypairsException::XRPLAddressCodecError(err) } } @@ -28,17 +34,5 @@ impl From for XRPLKeypairsException { } } -impl From for XRPLKeypairsException { - fn from(_: secp256k1::Error) -> Self { - XRPLKeypairsException::SECP256K1Error - } -} - -impl From for XRPLKeypairsException { - fn from(_: hex::FromHexError) -> Self { - XRPLKeypairsException::FromHexError - } -} - #[cfg(feature = "std")] impl alloc::error::Error for XRPLKeypairsException {} diff --git a/src/core/keypairs/mod.rs b/src/core/keypairs/mod.rs index 06c69475..1a43104a 100644 --- a/src/core/keypairs/mod.rs +++ b/src/core/keypairs/mod.rs @@ -10,7 +10,6 @@ pub use self::algorithms::Ed25519; pub use self::algorithms::Secp256k1; use crate::constants::CryptoAlgorithm; -use crate::core::addresscodec::exceptions::XRPLAddressCodecException; use crate::core::addresscodec::utils::SEED_LENGTH; use crate::core::addresscodec::*; use crate::core::keypairs::exceptions::XRPLKeypairsException; @@ -21,6 +20,8 @@ use alloc::vec::Vec; use rand::Rng; use rand::SeedableRng; +use super::exceptions::XRPLCoreResult; + /// Return the signature length for an algorithm. const fn _get_algorithm_sig_length(algo: CryptoAlgorithm) -> usize { match algo { @@ -67,6 +68,7 @@ fn _get_algorithm_engine_from_key(key: &str) -> Box { /// use xrpl::core::addresscodec::exceptions::XRPLAddressCodecException; /// use xrpl::constants::CryptoAlgorithm; /// use xrpl::core::addresscodec::utils::SEED_LENGTH; +/// use xrpl::core::exceptions::XRPLCoreException; /// /// let entropy: Option<[u8; SEED_LENGTH]> = Some([ /// 207, 45, 227, 120, 251, 221, 126, 46, @@ -81,7 +83,7 @@ fn _get_algorithm_engine_from_key(key: &str) -> Box { /// ) { /// Ok(seed) => Some(seed), /// Err(e) => match e { -/// XRPLAddressCodecException::UnknownSeedEncoding => None, +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::UnknownSeedEncoding) => None, /// _ => None, /// } /// }; @@ -91,7 +93,7 @@ fn _get_algorithm_engine_from_key(key: &str) -> Box { pub fn generate_seed( entropy: Option<[u8; SEED_LENGTH]>, algorithm: Option, -) -> Result { +) -> XRPLCoreResult { let mut random_bytes: [u8; SEED_LENGTH] = [0u8; SEED_LENGTH]; let algo: CryptoAlgorithm = if let Some(value) = algorithm { @@ -119,6 +121,7 @@ pub fn generate_seed( /// ``` /// use xrpl::core::keypairs::derive_keypair; /// use xrpl::core::keypairs::exceptions::XRPLKeypairsException; +/// use xrpl::core::exceptions::XRPLCoreException; /// /// let seed: &str = "sEdSKaCy2JT7JaM7v95H9SxkhP9wS2r"; /// let validator: bool = false; @@ -133,20 +136,17 @@ pub fn generate_seed( /// ) { /// Ok(seed) => Some(seed), /// Err(e) => match e { -/// XRPLKeypairsException::InvalidSignature => None, -/// XRPLKeypairsException::ED25519Error => None, -/// XRPLKeypairsException::SECP256K1Error => None, -/// XRPLKeypairsException::UnsupportedValidatorAlgorithm { expected: _ } => None, +/// XRPLCoreException::XRPLKeypairsError(XRPLKeypairsException::InvalidSignature) => None, +/// XRPLCoreException::XRPLKeypairsError(XRPLKeypairsException::ED25519Error) => None, +/// XRPLCoreException::XRPLKeypairsError(XRPLKeypairsException::SECP256K1Error(_)) => None, +/// XRPLCoreException::XRPLKeypairsError(XRPLKeypairsException::UnsupportedValidatorAlgorithm { expected: _ }) => None, /// _ => None, /// } /// }; /// /// assert_eq!(Some(tuple), generator); /// ``` -pub fn derive_keypair( - seed: &str, - validator: bool, -) -> Result<(String, String), XRPLKeypairsException> { +pub fn derive_keypair(seed: &str, validator: bool) -> XRPLCoreResult<(String, String)> { let (decoded_seed, algorithm) = decode_seed(seed)?; let module = _get_algorithm_engine(algorithm); let (public, private) = module.derive_keypair(&decoded_seed, validator)?; @@ -155,7 +155,7 @@ pub fn derive_keypair( if module.is_valid_message(SIGNATURE_VERIFICATION_MESSAGE, &signature, &public) { Ok((public, private)) } else { - Err(XRPLKeypairsException::InvalidSignature) + Err(XRPLKeypairsException::InvalidSignature.into()) } } @@ -172,6 +172,7 @@ pub fn derive_keypair( /// ``` /// use xrpl::core::keypairs::derive_classic_address; /// use xrpl::core::addresscodec::exceptions::XRPLAddressCodecException; +/// use xrpl::core::exceptions::XRPLCoreException; /// /// let public_key: &str = "ED01FA53FA5A7E77798F882ECE20B1ABC00\ /// BB358A9E55A202D0D0676BD0CE37A63"; @@ -180,17 +181,17 @@ pub fn derive_keypair( /// let derivation: Option = match derive_classic_address(public_key) { /// Ok(address) => Some(address), /// Err(e) => match e { -/// XRPLAddressCodecException::UnexpectedPayloadLength { +/// XRPLCoreException::XRPLAddressCodecError(XRPLAddressCodecException::UnexpectedPayloadLength { /// expected: _, /// found: _, -/// } => None, +/// }) => None, /// _ => None, /// } /// }; /// /// assert_eq!(Some(address), derivation); /// ``` -pub fn derive_classic_address(public_key: &str) -> Result { +pub fn derive_classic_address(public_key: &str) -> XRPLCoreResult { let account_id = get_account_id(&hex::decode(public_key)?); encode_classic_address(&account_id) } @@ -204,6 +205,7 @@ pub fn derive_classic_address(public_key: &str) -> Result { - #[error("Expected `Value` to be an object.")] - ExpectedObject, - #[error("Field `{field}` is not allowed to have an associated tag.")] - DisallowedTag { field: &'a str }, - #[error("Cannot have mismatched Account X-Address and SourceTag")] - AccountMismatchingTags, - #[error("Cannot have mismatched Destination X-Address and DestinationTag")] - DestinationMismatchingTags, - #[error("Unknown transaction type: {0}")] - UnknownTransactionType(&'a str), - #[error("Unknown transaction result: {0}")] - UnknownTransactionResult(&'a str), - #[error("Unknown ledger entry type: {0}")] - UnknownLedgerEntryType(&'a str), -} - -#[derive(Debug, Clone, PartialEq, Error)] -pub enum XRPLXChainBridgeException { - #[error("Invalid XChainBridge type")] - InvalidXChainBridgeType, -} - -#[derive(Debug, Clone, PartialEq, Display)] -#[non_exhaustive] -pub enum XRPLHashException { - InvalidHashLength { expected: usize, found: usize }, - FromHexError, - ISOCodeError(ISOCodeException), - XRPLBinaryCodecError(XRPLBinaryCodecException), - XRPLAddressCodecError(XRPLAddressCodecException), - SerdeJsonError(serde_json::error::Category), -} - -#[derive(Debug, Clone, PartialEq, Display)] -#[non_exhaustive] -pub enum XRPLVectorException { - InvalidVector256Bytes, - XRPLBinaryCodecError(XRPLBinaryCodecException), - XRPLHashError(XRPLHashException), -} - -impl From for XRPLTypeException { - fn from(err: XRPLHashException) -> Self { - XRPLTypeException::XRPLHashError(err) - } -} - -impl From for XRPLTypeException { - fn from(err: XRPRangeException) -> Self { - XRPLTypeException::XRPLRangeError(err) - } -} - -impl From for XRPLTypeException { - fn from(err: XRPLBinaryCodecException) -> Self { - XRPLTypeException::XRPLBinaryCodecError(err) - } -} - -impl From for XRPLTypeException { - fn from(err: JSONParseException) -> Self { - XRPLTypeException::JSONParseError(err) - } -} - -impl From for XRPLTypeException { - fn from(err: rust_decimal::Error) -> Self { - XRPLTypeException::DecimalError(err) - } -} - -impl From for XRPLTypeException { - fn from(_: hex::FromHexError) -> Self { - XRPLTypeException::FromHexError - } -} - -impl From for XRPLTypeException { - fn from(err: bigdecimal::ParseBigDecimalError) -> Self { - XRPLTypeException::BigDecimalError(err) - } -} - -impl From for XRPLTypeException { - fn from(err: XRPLXChainBridgeException) -> Self { - XRPLTypeException::XRPLXChainBridgeError(err) - } -} - -impl From for XRPLHashException { - fn from(err: ISOCodeException) -> Self { - XRPLHashException::ISOCodeError(err) - } -} - -impl From for XRPLHashException { - fn from(err: XRPLBinaryCodecException) -> Self { - XRPLHashException::XRPLBinaryCodecError(err) - } -} - -impl From for XRPLHashException { - fn from(err: XRPLAddressCodecException) -> Self { - XRPLHashException::XRPLAddressCodecError(err) - } -} - -impl From for XRPLHashException { - fn from(err: serde_json::Error) -> Self { - XRPLHashException::SerdeJsonError(err.classify()) - } -} - -impl From for XRPLHashException { - fn from(_: hex::FromHexError) -> Self { - XRPLHashException::FromHexError - } -} - -impl From for XRPLVectorException { - fn from(err: XRPLHashException) -> Self { - XRPLVectorException::XRPLHashError(err) - } -} - -#[cfg(feature = "std")] -impl alloc::error::Error for XRPLTypeException {} - -#[cfg(feature = "std")] -impl alloc::error::Error for XRPLHashException {} - -#[cfg(feature = "std")] -impl alloc::error::Error for XRPLVectorException {} diff --git a/src/ledger/mod.rs b/src/ledger/mod.rs index ccbc04a0..74268bf4 100644 --- a/src/ledger/mod.rs +++ b/src/ledger/mod.rs @@ -1,9 +1,9 @@ -use anyhow::Result; use embassy_futures::block_on; use crate::{ asynch::{ clients::XRPLClient, + exceptions::XRPLHelperResult, ledger::{ get_fee as async_get_fee, get_latest_open_ledger_sequence as async_get_latest_open_ledger_sequence, @@ -15,14 +15,14 @@ use crate::{ pub use crate::asynch::ledger::FeeType; -pub fn get_latest_validated_ledger_sequence(client: &C) -> Result +pub fn get_latest_validated_ledger_sequence(client: &C) -> XRPLHelperResult where C: XRPLClient, { block_on(async_get_latest_validated_ledger_sequence(client)) } -pub fn get_latest_open_ledger_sequence(client: &C) -> Result +pub fn get_latest_open_ledger_sequence(client: &C) -> XRPLHelperResult where C: XRPLClient, { @@ -33,7 +33,7 @@ pub fn get_fee( client: &C, max_fee: Option, fee_type: Option, -) -> Result> +) -> XRPLHelperResult> where C: XRPLClient, { diff --git a/src/lib.rs b/src/lib.rs index 53e83cef..20de8126 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,38 +20,31 @@ #![no_std] #![allow(dead_code)] // Remove eventually +use ::core::fmt::Display; + +use alloc::string::{String, ToString}; +use thiserror_no_std::Error; + #[cfg(not(feature = "std"))] extern crate alloc; #[cfg(feature = "std")] extern crate std as alloc; -#[cfg(feature = "account-helpers")] +#[cfg(feature = "helpers")] pub mod account; -#[cfg(any( - feature = "json-rpc", - feature = "websocket", - feature = "account-helpers", - feature = "ledger-helpers", - feature = "transaction-helpers", - feature = "wallet-helpers" -))] +#[cfg(any(feature = "json-rpc", feature = "websocket", feature = "helpers"))] pub mod asynch; #[cfg(any(feature = "json-rpc", feature = "websocket"))] pub mod clients; pub mod constants; #[cfg(feature = "core")] pub mod core; -#[cfg(feature = "ledger-helpers")] +#[cfg(feature = "helpers")] pub mod ledger; pub mod macros; -#[cfg(any( - feature = "ledger-models", - feature = "request-models", - feature = "result-models", - feature = "transaction-models" -))] +#[cfg(any(feature = "models"))] pub mod models; -#[cfg(feature = "transaction-helpers")] +#[cfg(feature = "helpers")] pub mod transaction; #[cfg(feature = "utils")] pub mod utils; @@ -60,17 +53,11 @@ pub mod wallet; pub extern crate serde_json; -mod _anyhow; -#[cfg(any( - feature = "ledger-models", - feature = "request-models", - feature = "result-models", - feature = "transaction-models" -))] +#[cfg(any(feature = "models"))] mod _serde; #[cfg(all( - any(feature = "transaction-helpers", feature = "wallet-helpers"), + feature = "helpers", not(any( feature = "tokio-rt", feature = "embassy-rt", @@ -80,4 +67,49 @@ mod _serde; feature = "smol-rt" )) ))] -compile_error!("Cannot enable `transaction-helpers` or `wallet-helpers` without enabling a runtime feature (\"*-rt\"). This is required for sleeping between retries internally."); +compile_error!("Cannot enable `helpers` without enabling a runtime feature (\"*-rt\"). This is required for sleeping between retries internally."); +#[cfg(all( + feature = "helpers", + not(any(feature = "json-rpc", feature = "websocket",)) +))] +compile_error!("Cannot enable `helpers` without enabling a client feature (\"json-rpc\", \"websocket\"). This is required for interacting with the XRP Ledger."); + +#[derive(Debug, Error)] +pub enum XRPLSerdeJsonError { + SerdeJsonError(serde_json::Error), + InvalidNoneError(String), + UnexpectedValueType { + expected: String, + found: serde_json::Value, + }, +} + +impl Display for XRPLSerdeJsonError { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + XRPLSerdeJsonError::SerdeJsonError(err) => write!(f, "{}", err), + XRPLSerdeJsonError::InvalidNoneError(err) => { + write!(f, "Invalid None value on field: {}", err) + } + XRPLSerdeJsonError::UnexpectedValueType { expected, found } => { + write!( + f, + "Unexpected value type (expected: {}, found: {})", + expected, found + ) + } + } + } +} + +impl From for XRPLSerdeJsonError { + fn from(err: serde_json::Error) -> Self { + XRPLSerdeJsonError::SerdeJsonError(err) + } +} + +impl PartialEq for XRPLSerdeJsonError { + fn eq(&self, other: &Self) -> bool { + self.to_string() == other.to_string() + } +} diff --git a/src/models/amount/exceptions.rs b/src/models/amount/exceptions.rs deleted file mode 100644 index bbef37f1..00000000 --- a/src/models/amount/exceptions.rs +++ /dev/null @@ -1,18 +0,0 @@ -use core::num::ParseFloatError; - -use thiserror_no_std::Error; - -#[derive(Debug, Error)] -pub enum XRPLAmountException { - #[error("Unable to convert amount `value` into `Decimal`.")] - ToDecimalError(#[from] rust_decimal::Error), - #[error("Unable to convert amount float.")] - ToFloatError(#[from] ParseFloatError), - #[error("Unable to convert amount integer.")] - ToIntError(#[from] core::num::ParseIntError), - #[error("{0:?}")] - FromSerdeError(#[from] serde_json::Error), -} - -#[cfg(feature = "std")] -impl alloc::error::Error for XRPLAmountException {} diff --git a/src/models/amount/issued_currency_amount.rs b/src/models/amount/issued_currency_amount.rs index 90343415..12671f0d 100644 --- a/src/models/amount/issued_currency_amount.rs +++ b/src/models/amount/issued_currency_amount.rs @@ -1,9 +1,8 @@ -use crate::models::Model; -use crate::{models::amount::exceptions::XRPLAmountException, Err}; +use crate::models::{Model, XRPLModelException, XRPLModelResult}; use alloc::borrow::Cow; +use bigdecimal::BigDecimal; use core::convert::TryInto; use core::str::FromStr; -use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)] @@ -25,14 +24,11 @@ impl<'a> IssuedCurrencyAmount<'a> { } } -impl<'a> TryInto for IssuedCurrencyAmount<'a> { - type Error = anyhow::Error; +impl<'a> TryInto for IssuedCurrencyAmount<'a> { + type Error = XRPLModelException; - fn try_into(self) -> Result { - match Decimal::from_str(&self.value) { - Ok(decimal) => Ok(decimal), - Err(decimal_error) => Err!(XRPLAmountException::ToDecimalError(decimal_error)), - } + fn try_into(self) -> XRPLModelResult { + Ok(BigDecimal::from_str(&self.value)?) } } diff --git a/src/models/amount/mod.rs b/src/models/amount/mod.rs index 25893015..a27bb6b0 100644 --- a/src/models/amount/mod.rs +++ b/src/models/amount/mod.rs @@ -1,17 +1,17 @@ -mod exceptions; mod issued_currency_amount; mod xrp_amount; -pub use exceptions::*; +use bigdecimal::BigDecimal; pub use issued_currency_amount::*; pub use xrp_amount::*; use crate::models::Model; use core::convert::TryInto; -use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use strum_macros::Display; +use super::{XRPLModelException, XRPLModelResult}; + #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Display)] #[serde(untagged)] pub enum Amount<'a> { @@ -19,10 +19,10 @@ pub enum Amount<'a> { XRPAmount(XRPAmount<'a>), } -impl<'a> TryInto for Amount<'a> { - type Error = anyhow::Error; +impl<'a> TryInto for Amount<'a> { + type Error = XRPLModelException; - fn try_into(self) -> Result { + fn try_into(self) -> XRPLModelResult { match self { Amount::IssuedCurrencyAmount(amount) => amount.try_into(), Amount::XRPAmount(amount) => amount.try_into(), diff --git a/src/models/amount/xrp_amount.rs b/src/models/amount/xrp_amount.rs index dea52afe..a42dbeb2 100644 --- a/src/models/amount/xrp_amount.rs +++ b/src/models/amount/xrp_amount.rs @@ -1,13 +1,14 @@ -use crate::models::Model; -use crate::{models::amount::exceptions::XRPLAmountException, Err}; +use crate::models::{Model, XRPLModelException, XRPLModelResult}; use alloc::{ borrow::Cow, string::{String, ToString}, }; -use anyhow::Result; -use core::convert::{TryFrom, TryInto}; +use bigdecimal::BigDecimal; use core::str::FromStr; -use rust_decimal::Decimal; +use core::{ + convert::{TryFrom, TryInto}, + fmt::Display, +}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -23,9 +24,15 @@ impl Default for XRPAmount<'_> { } } +impl Display for XRPAmount<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", self.0) + } +} + // implement Deserializing from Cow, &str, String, Decimal, f64, u32, and Value impl<'de, 'a> Deserialize<'de> for XRPAmount<'a> { - fn deserialize(deserializer: D) -> Result, D::Error> + fn deserialize(deserializer: D) -> XRPLModelResult, D::Error> where D: serde::Deserializer<'de>, { @@ -52,8 +59,8 @@ impl<'a> From for XRPAmount<'a> { } } -impl<'a> From for XRPAmount<'a> { - fn from(value: Decimal) -> Self { +impl<'a> From for XRPAmount<'a> { + fn from(value: BigDecimal) -> Self { Self(value.to_string().into()) } } @@ -71,56 +78,47 @@ impl<'a> From for XRPAmount<'a> { } impl<'a> TryFrom for XRPAmount<'a> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_from(value: Value) -> Result { + fn try_from(value: Value) -> XRPLModelResult { match serde_json::to_string(&value) { Ok(amount_string) => { let amount_string = amount_string.clone().replace("\"", ""); Ok(Self(amount_string.into())) } - Err(serde_error) => Err!(XRPLAmountException::FromSerdeError(serde_error)), + Err(serde_error) => Err(serde_error.into()), } } } impl<'a> TryInto for XRPAmount<'a> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_into(self) -> Result { - match self.0.parse::() { - Ok(f64_value) => Ok(f64_value), - Err(parse_error) => Err!(XRPLAmountException::ToFloatError(parse_error)), - } + fn try_into(self) -> XRPLModelResult { + Ok(self.0.parse::()?) } } impl<'a> TryInto for XRPAmount<'a> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_into(self) -> Result { - match self.0.parse::() { - Ok(u32_value) => Ok(u32_value), - Err(parse_error) => Err!(XRPLAmountException::ToIntError(parse_error)), - } + fn try_into(self) -> XRPLModelResult { + Ok(self.0.parse::()?) } } -impl<'a> TryInto for XRPAmount<'a> { - type Error = anyhow::Error; +impl<'a> TryInto for XRPAmount<'a> { + type Error = XRPLModelException; - fn try_into(self) -> Result { - match Decimal::from_str(&self.0) { - Ok(decimal) => Ok(decimal), - Err(decimal_error) => Err!(XRPLAmountException::ToDecimalError(decimal_error)), - } + fn try_into(self) -> XRPLModelResult { + Ok(BigDecimal::from_str(&self.0)?) } } impl<'a> TryInto> for XRPAmount<'a> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_into(self) -> Result, Self::Error> { + fn try_into(self) -> XRPLModelResult, Self::Error> { Ok(self.0) } } @@ -133,8 +131,8 @@ impl<'a> PartialOrd for XRPAmount<'a> { impl<'a> Ord for XRPAmount<'a> { fn cmp(&self, other: &Self) -> core::cmp::Ordering { - let self_decimal: Decimal = self.clone().try_into().unwrap(); - let other_decimal: Decimal = other.clone().try_into().unwrap(); + let self_decimal: BigDecimal = self.clone().try_into().unwrap(); + let other_decimal: BigDecimal = other.clone().try_into().unwrap(); self_decimal.cmp(&other_decimal) } } diff --git a/src/models/currency/mod.rs b/src/models/currency/mod.rs index ff4f8a5d..3b0aeb54 100644 --- a/src/models/currency/mod.rs +++ b/src/models/currency/mod.rs @@ -55,11 +55,7 @@ impl<'a> From> for Currency<'a> { impl<'a> From<&IssuedCurrencyAmount<'a>> for Currency<'a> { fn from(value: &IssuedCurrencyAmount<'a>) -> Self { - IssuedCurrency::new( - value.currency.clone(), - value.issuer.clone(), - ) - .into() + IssuedCurrency::new(value.currency.clone(), value.issuer.clone()).into() } } diff --git a/src/models/exceptions.rs b/src/models/exceptions.rs index c1f3089a..624a152d 100644 --- a/src/models/exceptions.rs +++ b/src/models/exceptions.rs @@ -1,29 +1,148 @@ //! General XRPL Model Exception. +use core::num::{ParseFloatError, ParseIntError}; + use alloc::string::String; -use serde::{Deserialize, Serialize}; use thiserror_no_std::Error; -#[derive(Debug, Clone, PartialEq, Error)] -pub enum XRPLModelException<'a> { - #[error("Missing Field: {0}")] - MissingField(&'a str), -} +use crate::XRPLSerdeJsonError; + +use super::{ + results::exceptions::XRPLResultException, + transactions::exceptions::{ + XRPLAccountSetException, XRPLNFTokenCancelOfferException, XRPLNFTokenCreateOfferException, + XRPLPaymentException, XRPLSignerListSetException, XRPLTransactionException, + XRPLXChainClaimException, XRPLXChainCreateBridgeException, + XRPLXChainCreateClaimIDException, XRPLXChainModifyBridgeException, + }, +}; + +pub type XRPLModelResult = core::result::Result; + +#[derive(Debug, PartialEq, Error)] +pub enum XRPLModelException { + // Model validation errors + #[error("Expected one of: {}", .0.join(", "))] + ExpectedOneOf(&'static [&'static str]), + #[error("Invalid field combination: {field} with {other_fields:?}")] + InvalidFieldCombination { + field: &'static str, + other_fields: &'static [&'static str], + }, + #[error("The value of the field `{field:?}` is defined above its maximum (max {max:?}, found {found:?})")] + ValueTooHigh { field: String, max: u32, found: u32 }, + #[error("The value of the field `{field:?}` is defined below its minimum (min {min:?}, found {found:?})")] + ValueTooLow { field: String, min: u32, found: u32 }, + #[error("The value of the field `{field:?}` does not have the correct format (expected {format:?}, found {found:?})")] + InvalidValueFormat { + field: String, + format: String, + found: String, + }, + #[error("The value of the field `{field:?}` exceeds its maximum length of characters (max {max:?}, found {found:?})")] + ValueTooLong { + field: String, + max: usize, + found: usize, + }, + #[error("The value of the field `{field:?}` is below its minimum length of characters (min {min:?}, found {found:?})")] + ValueTooShort { + field: String, + min: usize, + found: usize, + }, + #[error("The value of the field `{field1:?}` is not allowed to be below the value of the field `{field2:?}` (max {field2_val:?}, found {field1_val:?})")] + ValueBelowValue { + field1: String, + field2: String, + field1_val: u32, + field2_val: u32, + }, + #[error("The value of the field `{field1:?}` is not allowed to be the same as the value of the field `{field2:?}`")] + ValueEqualsValue { field1: String, field2: String }, + #[error("The value of the field `{0:?}` is not allowed to be zero")] + ValueZero(String), + #[error("If the field `{field1:?}` is defined, the field `{field2:?}` must also be defined")] + FieldRequiresField { field1: String, field2: String }, + + #[error("Expected field `{0}` is missing")] + MissingField(String), -#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] -pub struct JSONRPCException { - code: i32, - message: String, + #[error("From hex error: {0}")] + FromHexError(#[from] hex::FromHexError), + #[error("Parse int error: {0}")] + ParseIntError(#[from] ParseIntError), + #[error("Parse float error: {0}")] + ParseFloatError(#[from] ParseFloatError), + #[error("serde_json error: {0}")] + SerdeJsonError(#[from] XRPLSerdeJsonError), + #[error("BigDecimal error: {0}")] + BigDecimalError(#[from] bigdecimal::ParseBigDecimalError), + #[error("{0}")] + XRPLResultError(#[from] XRPLResultException), + #[error("{0}")] + XRPLTransactionError(#[from] XRPLTransactionException), } #[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLModelException<'a> {} +impl alloc::error::Error for XRPLModelException {} -#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Error)] -pub enum XRPLFlagsException { - #[error("Cannot convert flag to u32")] - CannotConvertFlagToU32, +impl From for XRPLModelException { + fn from(error: serde_json::Error) -> Self { + XRPLModelException::SerdeJsonError(error.into()) + } } -#[cfg(feature = "std")] -impl alloc::error::Error for XRPLFlagsException {} +impl From for XRPLModelException { + fn from(error: XRPLAccountSetException) -> Self { + XRPLModelException::XRPLTransactionError(error.into()) + } +} + +impl From for XRPLModelException { + fn from(error: XRPLNFTokenCancelOfferException) -> Self { + XRPLModelException::XRPLTransactionError(error.into()) + } +} + +impl From for XRPLModelException { + fn from(error: XRPLNFTokenCreateOfferException) -> Self { + XRPLModelException::XRPLTransactionError(error.into()) + } +} + +impl From for XRPLModelException { + fn from(error: XRPLPaymentException) -> Self { + XRPLModelException::XRPLTransactionError(error.into()) + } +} + +impl From for XRPLModelException { + fn from(error: XRPLSignerListSetException) -> Self { + XRPLModelException::XRPLTransactionError(error.into()) + } +} + +impl From for XRPLModelException { + fn from(error: XRPLXChainClaimException) -> Self { + XRPLModelException::XRPLTransactionError(error.into()) + } +} + +impl From for XRPLModelException { + fn from(error: XRPLXChainCreateBridgeException) -> Self { + XRPLModelException::XRPLTransactionError(error.into()) + } +} + +impl From for XRPLModelException { + fn from(error: XRPLXChainCreateClaimIDException) -> Self { + XRPLModelException::XRPLTransactionError(error.into()) + } +} + +impl From for XRPLModelException { + fn from(error: XRPLXChainModifyBridgeException) -> Self { + XRPLModelException::XRPLTransactionError(error.into()) + } +} diff --git a/src/models/flag_collection.rs b/src/models/flag_collection.rs index 17331e26..c4f00bdf 100644 --- a/src/models/flag_collection.rs +++ b/src/models/flag_collection.rs @@ -1,13 +1,12 @@ use core::convert::TryFrom; use alloc::vec::Vec; -use anyhow::Result; use derive_new::new; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use strum_macros::{AsRefStr, Display, EnumIter}; -use crate::{models::XRPLFlagsException, Err}; +use super::{XRPLModelException, XRPLModelResult}; /// Represents the type of flags when the XRPL model has no flags. #[derive( @@ -53,9 +52,9 @@ impl TryFrom for FlagCollection where T: IntoEnumIterator + Serialize, { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_from(flags: u32) -> Result { + fn try_from(flags: u32) -> XRPLModelResult { let mut flag_collection = Vec::new(); for flag in T::iter() { let flag_as_u32 = flag_to_u32(&flag)?; @@ -71,9 +70,9 @@ impl TryFrom> for u32 where T: IntoEnumIterator + Serialize, { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_from(flag_collection: FlagCollection) -> Result { + fn try_from(flag_collection: FlagCollection) -> XRPLModelResult { let mut flags = 0; for flag in flag_collection { let flag_as_u32 = flag_to_u32(&flag)?; @@ -97,15 +96,9 @@ where } } -fn flag_to_u32(flag: &T) -> Result +fn flag_to_u32(flag: &T) -> XRPLModelResult where T: Serialize, { - match serde_json::to_string(flag) { - Ok(flag_as_string) => match flag_as_string.parse::() { - Ok(flag_as_u32) => Ok(flag_as_u32), - Err(_error) => Err!(XRPLFlagsException::CannotConvertFlagToU32), - }, - Err(_error) => Err!(XRPLFlagsException::CannotConvertFlagToU32), - } + Ok(serde_json::to_string(flag)?.parse::()?) } diff --git a/src/models/mod.rs b/src/models/mod.rs index 3badeb21..3d3ad7f3 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -7,16 +7,16 @@ //! 4. Required specific fields in alphabetical order //! 5. Optional specific fields in alphabetical order -#[cfg(feature = "ledger-models")] +#[cfg(feature = "models")] #[allow(clippy::too_many_arguments)] pub mod ledger; -#[cfg(feature = "request-models")] +#[cfg(feature = "models")] #[allow(clippy::too_many_arguments)] pub mod requests; -#[cfg(feature = "result-models")] +#[cfg(feature = "models")] #[allow(clippy::too_many_arguments)] pub mod results; -#[cfg(feature = "transaction-models")] +#[cfg(feature = "models")] #[allow(clippy::too_many_arguments)] pub mod transactions; diff --git a/src/models/model.rs b/src/models/model.rs index 1fbc2278..53fdbcae 100644 --- a/src/models/model.rs +++ b/src/models/model.rs @@ -1,16 +1,16 @@ //! Base model -use anyhow::Result; +use super::XRPLModelResult; /// A trait that implements basic functions to every model. pub trait Model { /// Collects a models errors and returns the first error that occurs. - fn get_errors(&self) -> Result<()> { + fn get_errors(&self) -> XRPLModelResult<()> { Ok(()) } /// Simply forwards the error from `get_errors` if there was one. - fn validate(&self) -> Result<()> { + fn validate(&self) -> XRPLModelResult<()> { self.get_errors() } } diff --git a/src/models/requests/channel_authorize.rs b/src/models/requests/channel_authorize.rs index 4b84c5c5..7d12c286 100644 --- a/src/models/requests/channel_authorize.rs +++ b/src/models/requests/channel_authorize.rs @@ -1,14 +1,12 @@ use alloc::borrow::Cow; use alloc::vec::Vec; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::requests::exceptions::XRPLChannelAuthorizeException; +use crate::models::{XRPLModelException, XRPLModelResult}; use crate::{ constants::CryptoAlgorithm, models::{requests::RequestMethod, Model}, - Err, }; use super::{CommonFields, Request}; @@ -72,11 +70,8 @@ pub struct ChannelAuthorize<'a> { } impl<'a> Model for ChannelAuthorize<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_field_error() { - Err(error) => Err!(error), - Ok(_no_error) => Ok(()), - } + fn get_errors(&self) -> XRPLModelResult<()> { + self._get_field_error() } } @@ -91,7 +86,7 @@ impl<'a> Request<'a> for ChannelAuthorize<'a> { } impl<'a> ChannelAuthorizeError for ChannelAuthorize<'a> { - fn _get_field_error(&self) -> Result<(), XRPLChannelAuthorizeException> { + fn _get_field_error(&self) -> XRPLModelResult<()> { let mut signing_methods = Vec::new(); for method in [ self.secret.clone(), @@ -104,13 +99,12 @@ impl<'a> ChannelAuthorizeError for ChannelAuthorize<'a> { } } if signing_methods.len() != 1 { - Err(XRPLChannelAuthorizeException::DefineExactlyOneOf { - field1: "secret".into(), - field2: "seed".into(), - field3: "seed_hex".into(), - field4: "passphrase".into(), - resource: "".into(), - }) + Err(XRPLModelException::ExpectedOneOf(&[ + "secret", + "seed", + "seed_hex", + "passphrase", + ])) } else { Ok(()) } @@ -145,7 +139,7 @@ impl<'a> ChannelAuthorize<'a> { } pub trait ChannelAuthorizeError { - fn _get_field_error(&self) -> Result<(), XRPLChannelAuthorizeException>; + fn _get_field_error(&self) -> XRPLModelResult<()>; } #[cfg(test)] @@ -170,8 +164,12 @@ mod test_channel_authorize_errors { ); assert_eq!( - channel_authorize.validate().unwrap_err().to_string().as_str(), - "The field `secret` can not be defined with `seed`, `seed_hex`, `passphrase`. Define exactly one of them. For more information see: " + channel_authorize + .validate() + .unwrap_err() + .to_string() + .as_str(), + "Expected one of: secret, seed, seed_hex, passphrase" ); } diff --git a/src/models/requests/exceptions.rs b/src/models/requests/exceptions.rs deleted file mode 100644 index 1114cd4f..00000000 --- a/src/models/requests/exceptions.rs +++ /dev/null @@ -1,77 +0,0 @@ -use alloc::borrow::Cow; -use strum_macros::Display; -use thiserror_no_std::Error; - -#[derive(Debug, Clone, PartialEq, Eq, Display)] -pub enum XRPLRequestException<'a> { - XRPLChannelAuthorizeError(XRPLChannelAuthorizeException<'a>), - XRPLLedgerEntryError(XRPLLedgerEntryException<'a>), - /*SignAndSubmitError(SignAndSubmitException), - SignForError(SignForException), - SignError(SignException),*/ -} - -#[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLRequestException<'a> {} - -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLChannelAuthorizeException<'a> { - /// A field cannot be defined with other fields. - #[error("The field `{field1:?}` can not be defined with `{field2:?}`, `{field3:?}`, `{field4:?}`. Define exactly one of them. For more information see: {resource:?}")] - DefineExactlyOneOf { - field1: Cow<'a, str>, - field2: Cow<'a, str>, - field3: Cow<'a, str>, - field4: Cow<'a, str>, - resource: Cow<'a, str>, - }, -} - -/*impl<'a> From> for anyhow::Error { - fn from(value: XRPLChannelAuthorizeException<'a>) -> Self { - anyhow::anyhow!("{:?}", value) - } -}*/ - -#[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLChannelAuthorizeException<'a> {} - -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLLedgerEntryException<'a> { - /// A field cannot be defined with other fields. - #[error("Define one of: `{field1:?}`, `{field2:?}`, `{field3:?}`, `{field4:?}`, `{field5:?}`, `{field6:?}`, `{field7:?}`, `{field8:?}`, `{field9:?}`, `{field10:?}`. Define exactly one of them. For more information see: {resource:?}")] - DefineExactlyOneOf { - field1: Cow<'a, str>, - field2: Cow<'a, str>, - field3: Cow<'a, str>, - field4: Cow<'a, str>, - field5: Cow<'a, str>, - field6: Cow<'a, str>, - field7: Cow<'a, str>, - field8: Cow<'a, str>, - field9: Cow<'a, str>, - field10: Cow<'a, str>, - resource: Cow<'a, str>, - }, -} - -#[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLLedgerEntryException<'a> {} - -/*#[derive(Debug, Clone, PartialEq, Display)] -pub enum SignAndSubmitException { - InvalidMustSetExactlyOneOf { fields: String }, - InvalidMustOmitKeyTypeIfSecretProvided, -} - -#[derive(Debug, Clone, PartialEq, Display)] -pub enum SignForException { - InvalidMustSetExactlyOneOf { fields: String }, - InvalidMustOmitKeyTypeIfSecretProvided, -} - -#[derive(Debug, Clone, PartialEq, Display)] -pub enum SignException { - InvalidMustSetExactlyOneOf { fields: String }, - InvalidMustOmitKeyTypeIfSecretProvided, -}*/ diff --git a/src/models/requests/ledger_entry.rs b/src/models/requests/ledger_entry.rs index 836def71..a2a5ff35 100644 --- a/src/models/requests/ledger_entry.rs +++ b/src/models/requests/ledger_entry.rs @@ -1,12 +1,9 @@ -use crate::Err; use alloc::borrow::Cow; -use anyhow::Result; use derive_new::new; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::requests::exceptions::XRPLLedgerEntryException; -use crate::models::{requests::RequestMethod, Model}; +use crate::models::{requests::RequestMethod, Model, XRPLModelException, XRPLModelResult}; use super::{CommonFields, Request}; @@ -98,16 +95,13 @@ pub struct LedgerEntry<'a> { } impl<'a: 'static> Model for LedgerEntry<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_field_error() { - Err(error) => Err!(error), - Ok(_no_error) => Ok(()), - } + fn get_errors(&self) -> XRPLModelResult<()> { + Ok(self._get_field_error()?) } } impl<'a> LedgerEntryError for LedgerEntry<'a> { - fn _get_field_error(&self) -> Result<(), XRPLLedgerEntryException> { + fn _get_field_error(&self) -> XRPLModelResult<()> { let mut signing_methods: u32 = 0; for method in [ self.index.clone(), @@ -140,19 +134,18 @@ impl<'a> LedgerEntryError for LedgerEntry<'a> { signing_methods += 1 } if signing_methods != 1 { - Err(XRPLLedgerEntryException::DefineExactlyOneOf { - field1: "index".into(), - field2: "account_root".into(), - field3: "check".into(), - field4: "directory".into(), - field5: "offer".into(), - field6: "ripple_state".into(), - field7: "escrow".into(), - field8: "payment_channel".into(), - field9: "deposit_preauth".into(), - field10: "ticket".into(), - resource: "".into(), - }) + Err(XRPLModelException::ExpectedOneOf(&[ + "index", + "account_root", + "check", + "directory", + "offer", + "ripple_state", + "escrow", + "payment_channel", + "deposit_preauth", + "ticket", + ])) } else { Ok(()) } @@ -210,13 +203,12 @@ impl<'a> LedgerEntry<'a> { pub trait LedgerEntryError { #[allow(clippy::result_large_err)] - fn _get_field_error(&self) -> Result<(), XRPLLedgerEntryException>; + fn _get_field_error(&self) -> XRPLModelResult<()>; } #[cfg(test)] mod test_ledger_entry_errors { use super::Offer; - use crate::models::requests::exceptions::XRPLLedgerEntryException; use crate::models::Model; use alloc::string::ToString; @@ -243,22 +235,21 @@ mod test_ledger_entry_errors { None, None, ); - let _expected = XRPLLedgerEntryException::DefineExactlyOneOf { - field1: "index".into(), - field2: "account_root".into(), - field3: "check".into(), - field4: "directory".into(), - field5: "offer".into(), - field6: "ripple_state".into(), - field7: "escrow".into(), - field8: "payment_channel".into(), - field9: "deposit_preauth".into(), - field10: "ticket".into(), - resource: "".into(), - }; + let _expected = XRPLModelException::ExpectedOneOf(&[ + "index", + "account_root", + "check", + "directory", + "offer", + "ripple_state", + "escrow", + "payment_channel", + "deposit_preauth", + "ticket", + ]); assert_eq!( ledger_entry.validate().unwrap_err().to_string().as_str(), - "Define one of: `index`, `account_root`, `check`, `directory`, `offer`, `ripple_state`, `escrow`, `payment_channel`, `deposit_preauth`, `ticket`. Define exactly one of them. For more information see: " + "Expected one of: index, account_root, check, directory, offer, ripple_state, escrow, payment_channel, deposit_preauth, ticket" ); } diff --git a/src/models/requests/mod.rs b/src/models/requests/mod.rs index 8f79abce..71d02cfe 100644 --- a/src/models/requests/mod.rs +++ b/src/models/requests/mod.rs @@ -10,7 +10,6 @@ pub mod book_offers; pub mod channel_authorize; pub mod channel_verify; pub mod deposit_authorize; -pub mod exceptions; pub mod fee; pub mod gateway_balances; pub mod ledger; diff --git a/src/models/results/account_info.rs b/src/models/results/account_info.rs index 4372e084..ed2f2c5e 100644 --- a/src/models/results/account_info.rs +++ b/src/models/results/account_info.rs @@ -1,14 +1,11 @@ use core::convert::TryFrom; -use anyhow::Result; +use alloc::string::ToString; use serde::{Deserialize, Serialize}; -use crate::{ - models::{ledger::objects::AccountRoot, results::exceptions::XRPLResultException}, - Err, -}; +use crate::models::{ledger::objects::AccountRoot, XRPLModelException, XRPLModelResult}; -use super::XRPLResult; +use super::{exceptions::XRPLResultException, XRPLResult}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct AccountInfo<'a> { @@ -16,15 +13,16 @@ pub struct AccountInfo<'a> { } impl<'a> TryFrom> for AccountInfo<'a> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_from(result: XRPLResult<'a>) -> Result { + fn try_from(result: XRPLResult<'a>) -> XRPLModelResult { match result { XRPLResult::AccountInfo(account_info) => Ok(account_info), - res => Err!(XRPLResultException::UnexpectedResultType( + res => Err(XRPLResultException::UnexpectedResultType( "AccountInfo".to_string(), - res.get_name() - )), + res.get_name(), + ) + .into()), } } } diff --git a/src/models/results/account_tx.rs b/src/models/results/account_tx.rs index 35b49834..5fb65763 100644 --- a/src/models/results/account_tx.rs +++ b/src/models/results/account_tx.rs @@ -1,11 +1,12 @@ use core::convert::TryFrom; -use alloc::{borrow::Cow, vec::Vec}; -use anyhow::Result; +use alloc::{borrow::Cow, string::ToString, vec::Vec}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use crate::{models::results::exceptions::XRPLResultException, Err}; +use crate::models::{ + results::exceptions::XRPLResultException, XRPLModelException, XRPLModelResult, +}; use super::XRPLResult; @@ -21,15 +22,16 @@ pub struct AccountTx<'a> { } impl<'a> TryFrom> for AccountTx<'a> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_from(result: XRPLResult<'a>) -> Result { + fn try_from(result: XRPLResult<'a>) -> XRPLModelResult { match result { XRPLResult::AccountTx(account_tx) => Ok(account_tx), - res => Err!(XRPLResultException::UnexpectedResultType( + res => Err(XRPLResultException::UnexpectedResultType( "AccountTx".to_string(), - res.get_name() - )), + res.get_name(), + ) + .into()), } } } diff --git a/src/models/results/exceptions.rs b/src/models/results/exceptions.rs index 1900e349..aac08cea 100644 --- a/src/models/results/exceptions.rs +++ b/src/models/results/exceptions.rs @@ -3,7 +3,8 @@ use thiserror_no_std::Error; use super::XRPLOtherResult; -#[derive(Debug, Error)] +#[derive(Debug, PartialEq, Error)] +#[non_exhaustive] pub enum XRPLResultException { #[error("Response error: {0}")] ResponseError(String), diff --git a/src/models/results/fee.rs b/src/models/results/fee.rs index 2e70d6e1..b4dd69be 100644 --- a/src/models/results/fee.rs +++ b/src/models/results/fee.rs @@ -1,11 +1,11 @@ use core::convert::TryFrom; -use anyhow::Result; +use alloc::string::ToString; use serde::{Deserialize, Serialize}; -use crate::{ - models::{amount::XRPAmount, results::exceptions::XRPLResultException}, - Err, +use crate::models::{ + amount::XRPAmount, results::exceptions::XRPLResultException, XRPLModelException, + XRPLModelResult, }; use super::XRPLResult; @@ -24,15 +24,16 @@ pub struct Drops<'a> { } impl<'a> TryFrom> for Fee<'a> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_from(result: XRPLResult<'a>) -> Result { + fn try_from(result: XRPLResult<'a>) -> XRPLModelResult { match result { XRPLResult::Fee(fee) => Ok(fee), - res => Err!(XRPLResultException::UnexpectedResultType( + res => Err(XRPLResultException::UnexpectedResultType( "Fee".to_string(), - res.get_name() - )), + res.get_name(), + ) + .into()), } } } diff --git a/src/models/results/ledger.rs b/src/models/results/ledger.rs index b81456db..3cad2f62 100644 --- a/src/models/results/ledger.rs +++ b/src/models/results/ledger.rs @@ -1,10 +1,11 @@ use core::convert::TryFrom; -use alloc::{borrow::Cow, vec::Vec}; -use anyhow::Result; +use alloc::{borrow::Cow, string::ToString, vec::Vec}; use serde::{Deserialize, Serialize}; -use crate::{models::results::exceptions::XRPLResultException, Err}; +use crate::models::{ + results::exceptions::XRPLResultException, XRPLModelException, XRPLModelResult, +}; use super::XRPLResult; @@ -35,15 +36,16 @@ pub struct LedgerInner<'a> { } impl<'a> TryFrom> for Ledger<'a> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_from(result: XRPLResult<'a>) -> Result { + fn try_from(result: XRPLResult<'a>) -> XRPLModelResult { match result { XRPLResult::Ledger(ledger) => Ok(ledger), - res => Err!(XRPLResultException::UnexpectedResultType( - "ledger-models".to_string(), - res.get_name() - )), + res => Err(XRPLResultException::UnexpectedResultType( + "Ledger".to_string(), + res.get_name(), + ) + .into()), } } } diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index fd2e19ca..021232e4 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -7,16 +7,19 @@ pub mod server_state; pub mod submit; pub mod tx; -use super::requests::XRPLRequest; -use crate::Err; +use crate::XRPLSerdeJsonError; + +use super::{requests::XRPLRequest, XRPLModelException, XRPLModelResult}; use alloc::{ - borrow::{Cow, ToOwned}, + borrow::Cow, format, string::{String, ToString}, vec::Vec, }; -use anyhow::Result; -use core::convert::{TryFrom, TryInto}; +use core::{ + convert::{TryFrom, TryInto}, + fmt::Display, +}; use exceptions::XRPLResultException; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::{value::Index, Map, Value}; @@ -38,31 +41,30 @@ impl XRPLOptionalResult { } /// Try to convert the result into an expected XRPL result. - pub fn try_into_result(self) -> Result { + pub fn try_into_result(self) -> XRPLModelResult { match self { XRPLOptionalResult::Result(result) => Ok(result), - XRPLOptionalResult::Other(other) => Err!(XRPLResultException::ExpectedResult(other)), + XRPLOptionalResult::Other(other) => { + Err(XRPLResultException::ExpectedResult(other).into()) + } } } /// Get a value from the result by index. - pub fn try_get_typed(&self, index: I) -> Result + pub fn try_get_typed(&self, index: I) -> XRPLModelResult where T: Serialize, - I: Index, + I: Index + Display, U: DeserializeOwned, { match self { - XRPLOptionalResult::Result(result) => match serde_json::to_value(result) { - Ok(value) => match value.get(index) { - Some(value) => match serde_json::from_value(value.to_owned()) { - Ok(value) => Ok(value), - Err(e) => Err!(e), - }, - None => Err!(XRPLResultException::IndexNotFound), - }, - Err(e) => Err!(e), - }, + XRPLOptionalResult::Result(result) => { + let result_value = serde_json::to_value(result)?; + let value = result_value + .get(&index) + .ok_or(XRPLSerdeJsonError::InvalidNoneError(index.to_string()))?; + Ok(serde_json::from_value(value.clone())?) + } XRPLOptionalResult::Other(other) => other.try_get_typed(index), } } @@ -72,15 +74,16 @@ impl XRPLOptionalResult { pub struct XRPLOtherResult(Value); impl TryFrom> for XRPLOtherResult { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_from(result: XRPLResult) -> Result { + fn try_from(result: XRPLResult) -> XRPLModelResult { match result { XRPLResult::Other(value) => Ok(value), - res => Err!(XRPLResultException::UnexpectedResultType( + res => Err(XRPLResultException::UnexpectedResultType( "Other".to_string(), - res.get_name() - )), + res.get_name(), + ) + .into()), } } } @@ -102,18 +105,17 @@ impl XRPLOtherResult { self.0.get(index) } - pub fn try_get_typed(&self, index: I) -> Result + pub fn try_get_typed(&self, index: I) -> XRPLModelResult where I: Index, T: DeserializeOwned, { - match self.0.get(index) { - Some(value) => match serde_json::from_value(value.clone()) { - Ok(value) => Ok(value), - Err(e) => Err!(e), - }, - None => Err!(XRPLResultException::IndexNotFound), - } + let value = self + .0 + .get(index) + .ok_or(XRPLResultException::IndexNotFound)?; + + Ok(serde_json::from_value(value.clone())?) } } @@ -185,15 +187,12 @@ impl<'a> From for XRPLResult<'a> { } impl<'a> TryInto for XRPLResult<'a> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_into(self) -> Result { + fn try_into(self) -> XRPLModelResult { match self { XRPLResult::Other(XRPLOtherResult(value)) => Ok(value), - res => match serde_json::to_value(res) { - Ok(value) => Ok(value), - Err(e) => Err!(e), - }, + res => Ok(serde_json::to_value(res)?), } } } @@ -204,7 +203,7 @@ impl XRPLResult<'_> { XRPLResult::AccountInfo(_) => "AccountInfo".to_string(), XRPLResult::AccountTx(_) => "AccountTx".to_string(), XRPLResult::Fee(_) => "Fee".to_string(), - XRPLResult::Ledger(_) => "ledger-models".to_string(), + XRPLResult::Ledger(_) => "Ledger".to_string(), XRPLResult::ServerState(_) => "ServerState".to_string(), XRPLResult::Submit(_) => "Submit".to_string(), XRPLResult::Tx(_) => "Tx".to_string(), @@ -248,7 +247,7 @@ fn is_subscription_stream_item(item: &Map) -> bool { } impl<'a, 'de> Deserialize<'de> for XRPLResponse<'a> { - fn deserialize(deserializer: D) -> Result, D::Error> + fn deserialize(deserializer: D) -> XRPLModelResult, D::Error> where D: serde::Deserializer<'de>, { @@ -316,13 +315,10 @@ impl<'a, 'de> Deserialize<'de> for XRPLResponse<'a> { } impl TryInto for XRPLResponse<'_> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_into(self) -> Result { - match serde_json::to_value(self) { - Ok(value) => Ok(value), - Err(e) => Err!(e), - } + fn try_into(self) -> XRPLModelResult { + Ok(serde_json::to_value(self)?) } } @@ -343,9 +339,9 @@ impl<'a> XRPLResponse<'a> { } } - pub fn try_into_opt_result(self) -> Result> + pub fn try_into_opt_result(self) -> XRPLModelResult> where - T: TryFrom, Error = anyhow::Error>, + T: TryFrom, Error = XRPLModelException>, { match self.result { Some(result) => match result.clone().try_into() { @@ -354,25 +350,28 @@ impl<'a> XRPLResponse<'a> { }, None => { if let Some(error) = self.error { - Err!(XRPLResultException::ResponseError(format!( + Err(XRPLResultException::ResponseError(format!( "{}: {}", error, self.error_message.unwrap_or_default() - ))) + )) + .into()) } else { - Err!(XRPLResultException::ExpectedResultOrError) + Err(XRPLResultException::ExpectedResultOrError.into()) } } } } - pub fn try_into_result(self) -> Result + pub fn try_into_result(self) -> XRPLModelResult where - T: TryFrom, Error = anyhow::Error>, + T: TryFrom, Error = XRPLModelException>, { match self.try_into_opt_result()? { XRPLOptionalResult::Result(result) => Ok(result), - XRPLOptionalResult::Other(other) => Err!(XRPLResultException::ExpectedResult(other)), + XRPLOptionalResult::Other(other) => { + Err(XRPLResultException::ExpectedResult(other).into()) + } } } } diff --git a/src/models/results/server_state.rs b/src/models/results/server_state.rs index f1dbf250..7fa77d22 100644 --- a/src/models/results/server_state.rs +++ b/src/models/results/server_state.rs @@ -1,12 +1,11 @@ use core::convert::TryFrom; -use alloc::borrow::Cow; -use anyhow::Result; +use alloc::{borrow::Cow, string::ToString}; use serde::{Deserialize, Serialize}; -use crate::{ - models::{amount::XRPAmount, results::exceptions::XRPLResultException}, - Err, +use crate::models::{ + amount::XRPAmount, results::exceptions::XRPLResultException, XRPLModelException, + XRPLModelResult, }; use super::XRPLResult; @@ -34,15 +33,16 @@ pub struct ValidatedLedger<'a> { } impl<'a> TryFrom> for ServerState<'a> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_from(result: XRPLResult<'a>) -> Result { + fn try_from(result: XRPLResult<'a>) -> XRPLModelResult { match result { XRPLResult::ServerState(server_state) => Ok(server_state), - res => Err!(XRPLResultException::UnexpectedResultType( + res => Err(XRPLResultException::UnexpectedResultType( "ServerState".to_string(), - res.get_name() - )), + res.get_name(), + ) + .into()), } } } diff --git a/src/models/results/submit.rs b/src/models/results/submit.rs index 1b55706b..6ef749e2 100644 --- a/src/models/results/submit.rs +++ b/src/models/results/submit.rs @@ -1,11 +1,12 @@ use core::convert::TryFrom; -use alloc::borrow::Cow; -use anyhow::Result; +use alloc::{borrow::Cow, string::ToString}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use crate::{models::results::exceptions::XRPLResultException, Err}; +use crate::models::{ + results::exceptions::XRPLResultException, XRPLModelException, XRPLModelResult, +}; use super::XRPLResult; @@ -28,15 +29,16 @@ pub struct Submit<'a> { } impl<'a> TryFrom> for Submit<'a> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_from(result: XRPLResult<'a>) -> Result { + fn try_from(result: XRPLResult<'a>) -> XRPLModelResult { match result { XRPLResult::Submit(server_state) => Ok(server_state), - res => Err!(XRPLResultException::UnexpectedResultType( + res => Err(XRPLResultException::UnexpectedResultType( "Submit".to_string(), - res.get_name() - )), + res.get_name(), + ) + .into()), } } } diff --git a/src/models/results/tx.rs b/src/models/results/tx.rs index cecdf63b..16c3883e 100644 --- a/src/models/results/tx.rs +++ b/src/models/results/tx.rs @@ -1,11 +1,12 @@ use core::convert::TryFrom; -use alloc::borrow::Cow; -use anyhow::Result; +use alloc::{borrow::Cow, string::ToString}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use crate::{models::results::exceptions::XRPLResultException, Err}; +use crate::models::{ + results::exceptions::XRPLResultException, XRPLModelException, XRPLModelResult, +}; use super::XRPLResult; @@ -26,15 +27,14 @@ pub struct Tx<'a> { } impl<'a> TryFrom> for Tx<'a> { - type Error = anyhow::Error; + type Error = XRPLModelException; - fn try_from(result: XRPLResult<'a>) -> Result { + fn try_from(result: XRPLResult<'a>) -> XRPLModelResult { match result { XRPLResult::Tx(tx) => Ok(tx), - res => Err!(XRPLResultException::UnexpectedResultType( - "Tx".to_string(), - res.get_name() - )), + res => Err( + XRPLResultException::UnexpectedResultType("Tx".to_string(), res.get_name()).into(), + ), } } } diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index 66082045..3fbf28eb 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -1,6 +1,5 @@ use alloc::borrow::Cow; use alloc::vec::Vec; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -8,6 +7,7 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::amount::XRPAmount; use crate::models::transactions::{exceptions::XRPLAccountSetException, CommonFields}; +use crate::models::{XRPLModelException, XRPLModelResult}; use crate::{ constants::{ DISABLE_TICK_SIZE, MAX_DOMAIN_LENGTH, MAX_TICK_SIZE, MAX_TRANSFER_RATE, MIN_TICK_SIZE, @@ -17,7 +17,6 @@ use crate::{ transactions::{Memo, Signer, Transaction, TransactionType}, Model, }, - Err, }; use super::FlagCollection; @@ -121,23 +120,14 @@ pub struct AccountSet<'a> { } impl<'a> Model for AccountSet<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_tick_size_error() { - Err(error) => Err!(error), - Ok(_no_error) => match self._get_transfer_rate_error() { - Err(error) => Err!(error), - Ok(_no_error) => match self._get_domain_error() { - Err(error) => Err!(error), - Ok(_no_error) => match self._get_clear_flag_error() { - Err(error) => Err!(error), - Ok(_no_error) => match self._get_nftoken_minter_error() { - Err(error) => Err!(error), - Ok(_no_error) => Ok(()), - }, - }, - }, - }, - } + fn get_errors(&self) -> XRPLModelResult<()> { + self._get_tick_size_error()?; + self._get_transfer_rate_error()?; + self._get_domain_error()?; + self._get_clear_flag_error()?; + self._get_nftoken_minter_error()?; + + Ok(()) } } @@ -160,21 +150,19 @@ impl<'a> Transaction<'a, AccountSetFlag> for AccountSet<'a> { } impl<'a> AccountSetError for AccountSet<'a> { - fn _get_tick_size_error(&self) -> Result<(), XRPLAccountSetException> { + fn _get_tick_size_error(&self) -> Result<(), XRPLModelException> { if let Some(tick_size) = self.tick_size { if tick_size > MAX_TICK_SIZE { - Err(XRPLAccountSetException::ValueTooHigh { + Err(XRPLModelException::ValueTooHigh { field: "tick_size".into(), max: MAX_TICK_SIZE, found: tick_size, - resource: "".into(), }) } else if tick_size < MIN_TICK_SIZE && tick_size != DISABLE_TICK_SIZE { - Err(XRPLAccountSetException::ValueTooLow { + Err(XRPLModelException::ValueTooLow { field: "tick_size".into(), min: MIN_TICK_SIZE, found: tick_size, - resource: "".into(), }) } else { Ok(()) @@ -184,23 +172,21 @@ impl<'a> AccountSetError for AccountSet<'a> { } } - fn _get_transfer_rate_error(&self) -> Result<(), XRPLAccountSetException> { + fn _get_transfer_rate_error(&self) -> Result<(), XRPLModelException> { if let Some(transfer_rate) = self.transfer_rate { if transfer_rate > MAX_TRANSFER_RATE { - Err(XRPLAccountSetException::ValueTooHigh { + Err(XRPLModelException::ValueTooHigh { field: "transfer_rate".into(), max: MAX_TRANSFER_RATE, found: transfer_rate, - resource: "".into(), }) } else if transfer_rate < MIN_TRANSFER_RATE && transfer_rate != SPECIAL_CASE_TRANFER_RATE { - Err(XRPLAccountSetException::ValueTooLow { + Err(XRPLModelException::ValueTooLow { field: "transfer_rate".into(), min: MIN_TRANSFER_RATE, found: transfer_rate, - resource: "".into(), }) } else { Ok(()) @@ -210,21 +196,19 @@ impl<'a> AccountSetError for AccountSet<'a> { } } - fn _get_domain_error(&self) -> Result<(), XRPLAccountSetException> { + fn _get_domain_error(&self) -> Result<(), XRPLModelException> { if let Some(domain) = self.domain.clone() { if domain.to_lowercase().as_str() != domain { - Err(XRPLAccountSetException::InvalidValueFormat { + Err(XRPLModelException::InvalidValueFormat { field: "domain".into(), - found: domain, + found: domain.into(), format: "lowercase".into(), - resource: "".into(), }) } else if domain.len() > MAX_DOMAIN_LENGTH { - Err(XRPLAccountSetException::ValueTooLong { + Err(XRPLModelException::ValueTooLong { field: "domain".into(), max: MAX_DOMAIN_LENGTH, found: domain.len(), - resource: "".into(), }) } else { Ok(()) @@ -234,19 +218,19 @@ impl<'a> AccountSetError for AccountSet<'a> { } } - fn _get_clear_flag_error(&self) -> Result<(), XRPLAccountSetException> { + fn _get_clear_flag_error(&self) -> Result<(), XRPLModelException> { if self.clear_flag.is_some() && self.set_flag.is_some() && self.clear_flag == self.set_flag { Err(XRPLAccountSetException::SetAndUnsetSameFlag { found: self.clear_flag.unwrap(), - resource: "".into(), - }) + } + .into()) } else { Ok(()) } } - fn _get_nftoken_minter_error(&self) -> Result<(), XRPLAccountSetException> { + fn _get_nftoken_minter_error(&self) -> Result<(), XRPLModelException> { if let Some(_nftoken_minter) = self.nftoken_minter.clone() { if self.set_flag.is_none() { if let Some(clear_flag) = &self.clear_flag { @@ -255,8 +239,8 @@ impl<'a> AccountSetError for AccountSet<'a> { Err(XRPLAccountSetException::SetFieldWhenUnsetRequiredFlag { field: "nftoken_minter".into(), flag: AccountSetFlag::AsfAuthorizedNFTokenMinter, - resource: "".into(), - }) + } + .into()) } _ => Ok(()), } @@ -264,8 +248,8 @@ impl<'a> AccountSetError for AccountSet<'a> { Err(XRPLAccountSetException::FieldRequiresFlag { field: "set_flag".into(), flag: AccountSetFlag::AsfAuthorizedNFTokenMinter, - resource: "".into(), - }) + } + .into()) } } else { Ok(()) @@ -276,8 +260,8 @@ impl<'a> AccountSetError for AccountSet<'a> { Err(XRPLAccountSetException::FlagRequiresField { flag: AccountSetFlag::AsfAuthorizedNFTokenMinter, field: "nftoken_minter".into(), - resource: "".into(), - }) + } + .into()) } _ => Ok(()), } @@ -338,11 +322,11 @@ impl<'a> AccountSet<'a> { } pub trait AccountSetError { - fn _get_tick_size_error(&self) -> Result<(), XRPLAccountSetException>; - fn _get_transfer_rate_error(&self) -> Result<(), XRPLAccountSetException>; - fn _get_domain_error(&self) -> Result<(), XRPLAccountSetException>; - fn _get_clear_flag_error(&self) -> Result<(), XRPLAccountSetException>; - fn _get_nftoken_minter_error(&self) -> Result<(), XRPLAccountSetException>; + fn _get_tick_size_error(&self) -> Result<(), XRPLModelException>; + fn _get_transfer_rate_error(&self) -> Result<(), XRPLModelException>; + fn _get_domain_error(&self) -> Result<(), XRPLModelException>; + fn _get_clear_flag_error(&self) -> Result<(), XRPLModelException>; + fn _get_nftoken_minter_error(&self) -> Result<(), XRPLModelException>; } #[cfg(test)] @@ -380,7 +364,7 @@ mod test_account_set_errors { assert_eq!( account_set.validate().unwrap_err().to_string().as_str(), - "The value of the field `tick_size` is defined below its minimum (min 3, found 2). For more information see: " + "The value of the field `\"tick_size\"` is defined below its minimum (min 3, found 2)" ); let tick_size_too_high = Some(16); @@ -388,7 +372,7 @@ mod test_account_set_errors { assert_eq!( account_set.validate().unwrap_err().to_string().as_str(), - "The value of the field `tick_size` is defined above its maximum (max 15, found 16). For more information see: " + "The value of the field `\"tick_size\"` is defined above its maximum (max 15, found 16)" ); } @@ -419,7 +403,7 @@ mod test_account_set_errors { assert_eq!( account_set.validate().unwrap_err().to_string().as_str(), - "The value of the field `transfer_rate` is defined below its minimum (min 1000000000, found 999999999). For more information see: " + "The value of the field `\"transfer_rate\"` is defined below its minimum (min 1000000000, found 999999999)" ); let tick_size_too_high = Some(2000000001); @@ -427,7 +411,7 @@ mod test_account_set_errors { assert_eq!( account_set.validate().unwrap_err().to_string().as_str(), - "The value of the field `transfer_rate` is defined above its maximum (max 2000000000, found 2000000001). For more information see: " + "The value of the field `\"transfer_rate\"` is defined above its maximum (max 2000000000, found 2000000001)" ); } @@ -458,7 +442,7 @@ mod test_account_set_errors { assert_eq!( account_set.validate().unwrap_err().to_string().as_str(), - "The value of the field `domain` does not have the correct format (expected lowercase, found https://Example.com/). For more information see: " + "The value of the field `\"domain\"` does not have the correct format (expected \"lowercase\", found \"https://Example.com/\")" ); let domain_too_long = Some("https://example.com/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into()); @@ -466,7 +450,7 @@ mod test_account_set_errors { assert_eq!( account_set.validate().unwrap_err().to_string().as_str(), - "The value of the field `domain` exceeds its maximum length of characters (max 256, found 270). For more information see: " + "The value of the field `\"domain\"` exceeds its maximum length of characters (max 256, found 270)" ); } @@ -495,7 +479,7 @@ mod test_account_set_errors { assert_eq!( account_set.validate().unwrap_err().to_string().as_str(), - "A flag cannot be set and unset at the same time (found AsfDisallowXRP). For more information see: " + "A flag cannot be set and unset at the same time (found AsfDisallowXRP)" ); } @@ -525,7 +509,7 @@ mod test_account_set_errors { assert_eq!( account_set.validate().unwrap_err().to_string().as_str(), - "For the field `set_flag` to be defined it is required to set the flag `AsfAuthorizedNFTokenMinter`. For more information see: " + "For the field `\"set_flag\"` to be defined it is required to set the flag `AsfAuthorizedNFTokenMinter`" ); account_set.nftoken_minter = None; @@ -533,7 +517,7 @@ mod test_account_set_errors { assert_eq!( account_set.validate().unwrap_err().to_string().as_str(), - "For the flag `AsfAuthorizedNFTokenMinter` to be set it is required to define the field `nftoken_minter`. For more information see: " + "For the flag `AsfAuthorizedNFTokenMinter` to be set it is required to define the field `\"nftoken_minter\"`" ); account_set.set_flag = None; @@ -542,7 +526,7 @@ mod test_account_set_errors { assert_eq!( account_set.validate().unwrap_err().to_string().as_str(), - "The field `nftoken_minter` cannot be defined if its required flag `AsfAuthorizedNFTokenMinter` is being unset. For more information see: " + "The field `\"nftoken_minter\"` cannot be defined if its required flag `AsfAuthorizedNFTokenMinter` is being unset" ); } } diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index 3f4fdab6..ab5cfd73 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -1,18 +1,16 @@ -use crate::Err; use alloc::borrow::Cow; use alloc::vec::Vec; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::transactions::{exceptions::XRPLCheckCashException, CommonFields}; +use crate::models::transactions::CommonFields; use crate::models::{ amount::Amount, transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; -use crate::models::{FlagCollection, NoFlags}; +use crate::models::{FlagCollection, NoFlags, XRPLModelException, XRPLModelResult}; /// Cancels an unredeemed Check, removing it from the ledger without /// sending any money. The source or the destination of the check can @@ -48,11 +46,10 @@ pub struct CheckCash<'a> { } impl<'a: 'static> Model for CheckCash<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_amount_and_deliver_min_error() { - Err(error) => Err!(error), - Ok(_no_error) => Ok(()), - } + fn get_errors(&self) -> XRPLModelResult<()> { + self._get_amount_and_deliver_min_error()?; + + Ok(()) } } @@ -71,14 +68,13 @@ impl<'a> Transaction<'a, NoFlags> for CheckCash<'a> { } impl<'a> CheckCashError for CheckCash<'a> { - fn _get_amount_and_deliver_min_error(&self) -> Result<(), XRPLCheckCashException> { + fn _get_amount_and_deliver_min_error(&self) -> XRPLModelResult<()> { if (self.amount.is_none() && self.deliver_min.is_none()) || (self.amount.is_some() && self.deliver_min.is_some()) { - Err(XRPLCheckCashException::DefineExactlyOneOf { - field1: "amount".into(), - field2: "deliver_min".into(), - resource: "".into(), + Err(XRPLModelException::InvalidFieldCombination { + field: "amount", + other_fields: &["deliver_min"], }) } else { Ok(()) @@ -126,7 +122,7 @@ impl<'a> CheckCash<'a> { } pub trait CheckCashError { - fn _get_amount_and_deliver_min_error(&self) -> Result<(), XRPLCheckCashException>; + fn _get_amount_and_deliver_min_error(&self) -> XRPLModelResult<()>; } #[cfg(test)] @@ -155,7 +151,7 @@ mod test_check_cash_error { assert_eq!( check_cash.validate().unwrap_err().to_string().as_str(), - "The field `amount` can not be defined with `deliver_min`. Define exactly one of them. For more information see: " + "Invalid field combination: amount with [\"deliver_min\"]" ); } } diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index d61dd999..98502701 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -1,17 +1,15 @@ -use crate::Err; use alloc::borrow::Cow; use alloc::vec::Vec; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::transactions::{exceptions::XRPLDepositPreauthException, CommonFields}; +use crate::models::transactions::CommonFields; use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; -use crate::models::{FlagCollection, NoFlags}; +use crate::models::{FlagCollection, NoFlags, XRPLModelException, XRPLModelResult}; /// A DepositPreauth transaction gives another account pre-approval /// to deliver payments to the sender of this transaction. @@ -39,11 +37,10 @@ pub struct DepositPreauth<'a> { } impl<'a: 'static> Model for DepositPreauth<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_authorize_and_unauthorize_error() { - Ok(_no_error) => Ok(()), - Err(error) => Err!(error), - } + fn get_errors(&self) -> XRPLModelResult<()> { + self._get_authorize_and_unauthorize_error()?; + + Ok(()) } } @@ -62,14 +59,13 @@ impl<'a> Transaction<'a, NoFlags> for DepositPreauth<'a> { } impl<'a> DepositPreauthError for DepositPreauth<'a> { - fn _get_authorize_and_unauthorize_error(&self) -> Result<(), XRPLDepositPreauthException> { + fn _get_authorize_and_unauthorize_error(&self) -> XRPLModelResult<()> { if (self.authorize.is_none() && self.unauthorize.is_none()) || (self.authorize.is_some() && self.unauthorize.is_some()) { - Err(XRPLDepositPreauthException::DefineExactlyOneOf { - field1: "authorize".into(), - field2: "unauthorize".into(), - resource: "".into(), + Err(XRPLModelException::InvalidFieldCombination { + field: "authorize", + other_fields: &["unauthorize"], }) } else { Ok(()) @@ -115,7 +111,7 @@ impl<'a> DepositPreauth<'a> { } pub trait DepositPreauthError { - fn _get_authorize_and_unauthorize_error(&self) -> Result<(), XRPLDepositPreauthException>; + fn _get_authorize_and_unauthorize_error(&self) -> XRPLModelResult<()>; } #[cfg(test)] @@ -144,7 +140,7 @@ mod test_deposit_preauth_exception { assert_eq!( deposit_preauth.validate().unwrap_err().to_string().as_str(), - "The field `authorize` can not be defined with `unauthorize`. Define exactly one of them. For more information see: " + "Invalid field combination: authorize with [\"unauthorize\"]" ); } } diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index 006ddafe..dff5028c 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -1,17 +1,15 @@ -use crate::Err; use alloc::borrow::Cow; use alloc::vec::Vec; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::transactions::{exceptions::XRPLEscrowCreateException, CommonFields}; +use crate::models::transactions::CommonFields; use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; -use crate::models::{FlagCollection, NoFlags}; +use crate::models::{FlagCollection, NoFlags, XRPLModelException, XRPLModelResult}; /// Creates an Escrow, which requests XRP until the escrow process either finishes or is canceled. /// @@ -57,11 +55,10 @@ pub struct EscrowCreate<'a> { } impl<'a: 'static> Model for EscrowCreate<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_finish_after_error() { - Ok(_) => Ok(()), - Err(error) => Err!(error), - } + fn get_errors(&self) -> XRPLModelResult<()> { + self._get_finish_after_error()?; + + Ok(()) } } @@ -80,15 +77,14 @@ impl<'a> Transaction<'a, NoFlags> for EscrowCreate<'a> { } impl<'a> EscrowCreateError for EscrowCreate<'a> { - fn _get_finish_after_error(&self) -> Result<(), XRPLEscrowCreateException> { + fn _get_finish_after_error(&self) -> XRPLModelResult<()> { if let (Some(finish_after), Some(cancel_after)) = (self.finish_after, self.cancel_after) { if finish_after >= cancel_after { - Err(XRPLEscrowCreateException::ValueBelowValue { + Err(XRPLModelException::ValueBelowValue { field1: "cancel_after".into(), field2: "finish_after".into(), field1_val: cancel_after, field2_val: finish_after, - resource: "".into(), }) } else { Ok(()) @@ -145,7 +141,7 @@ impl<'a> EscrowCreate<'a> { } pub trait EscrowCreateError { - fn _get_finish_after_error(&self) -> Result<(), XRPLEscrowCreateException>; + fn _get_finish_after_error(&self) -> XRPLModelResult<()>; } #[cfg(test)] @@ -180,7 +176,7 @@ mod test_escrow_create_errors { assert_eq!( escrow_create.validate().unwrap_err().to_string().as_str(), - "The value of the field `cancel_after` is not allowed to be below the value of the field `finish_after` (max 14359039, found 13298498). For more information see: " + "The value of the field `\"cancel_after\"` is not allowed to be below the value of the field `\"finish_after\"` (max 14359039, found 13298498)" ); } } diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index 268a973a..afd77f3a 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -1,15 +1,12 @@ -use crate::Err; use alloc::borrow::Cow; use alloc::vec::Vec; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::transactions::exceptions::XRPLEscrowFinishException; use crate::models::{ amount::XRPAmount, transactions::{Memo, Signer, Transaction, TransactionType}, - Model, + Model, XRPLModelException, XRPLModelResult, }; use crate::models::{FlagCollection, NoFlags}; @@ -48,11 +45,10 @@ pub struct EscrowFinish<'a> { } impl<'a: 'static> Model for EscrowFinish<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_condition_and_fulfillment_error() { - Ok(_) => Ok(()), - Err(error) => Err!(error), - } + fn get_errors(&self) -> XRPLModelResult<()> { + self._get_condition_and_fulfillment_error()?; + + Ok(()) } } @@ -71,14 +67,13 @@ impl<'a> Transaction<'a, NoFlags> for EscrowFinish<'a> { } impl<'a> EscrowFinishError for EscrowFinish<'a> { - fn _get_condition_and_fulfillment_error(&self) -> Result<(), XRPLEscrowFinishException> { + fn _get_condition_and_fulfillment_error(&self) -> XRPLModelResult<()> { if (self.condition.is_some() && self.fulfillment.is_none()) || (self.condition.is_none() && self.condition.is_some()) { - Err(XRPLEscrowFinishException::FieldRequiresField { + Err(XRPLModelException::FieldRequiresField { field1: "condition".into(), field2: "fulfillment".into(), - resource: "".into(), }) } else { Ok(()) @@ -128,7 +123,7 @@ impl<'a> EscrowFinish<'a> { } pub trait EscrowFinishError { - fn _get_condition_and_fulfillment_error(&self) -> Result<(), XRPLEscrowFinishException>; + fn _get_condition_and_fulfillment_error(&self) -> XRPLModelResult<()>; } #[cfg(test)] @@ -162,7 +157,7 @@ mod test_escrow_finish_errors { assert_eq!( escrow_finish.validate().unwrap_err().to_string().as_str(), - "For the field `condition` to be defined it is required to also define the field `fulfillment`. For more information see: " + "If the field `\"condition\"` is defined, the field `\"fulfillment\"` must also be defined" ); } } diff --git a/src/models/transactions/exceptions.rs b/src/models/transactions/exceptions.rs index 432f6fae..5352621f 100644 --- a/src/models/transactions/exceptions.rs +++ b/src/models/transactions/exceptions.rs @@ -1,352 +1,158 @@ -use crate::models::transactions::{account_set::AccountSetFlag, payment::PaymentFlag}; -use alloc::borrow::Cow; -use core::fmt::Debug; -use strum_macros::Display; +use crate::{ + core::exceptions::XRPLCoreException, + models::transactions::{account_set::AccountSetFlag, payment::PaymentFlag}, +}; +use alloc::string::String; use thiserror_no_std::Error; -#[derive(Debug, Clone, PartialEq, Eq, Display)] -pub enum XRPLTransactionException<'a> { - XRPLAccountSetError(XRPLAccountSetException<'a>), - XRPLCheckCashError(XRPLCheckCashException<'a>), - XRPLDepositPreauthError(XRPLDepositPreauthException<'a>), - XRPLEscrowCreateError(XRPLEscrowCreateException<'a>), - XRPLEscrowFinishError(XRPLEscrowFinishException<'a>), - XRPLNFTokenAcceptOfferError(XRPLNFTokenAcceptOfferException<'a>), - XRPLNFTokenCancelOfferError(XRPLNFTokenCancelOfferException<'a>), - XRPLNFTokenCreateOfferError(XRPLNFTokenCreateOfferException<'a>), - XRPLNFTokenMintError(XRPLNFTokenMintException<'a>), - XRPLPaymentError(XRPLPaymentException<'a>), - XRPLSignerListSetError(XRPLSignerListSetException<'a>), +#[derive(Debug, PartialEq, Error)] +pub enum XRPLTransactionException { + #[error("{0}")] + XRPLAccountSetError(#[from] XRPLAccountSetException), + #[error("{0}")] + XRPLNFTokenCancelOfferError(#[from] XRPLNFTokenCancelOfferException), + #[error("{0}")] + XRPLNFTokenCreateOfferError(#[from] XRPLNFTokenCreateOfferException), + #[error("{0}")] + XRPLPaymentError(#[from] XRPLPaymentException), + #[error("{0}")] + XRPLSignerListSetError(#[from] XRPLSignerListSetException), + #[error("{0}")] + XRPLXChainClaimError(#[from] XRPLXChainClaimException), + #[error("{0}")] + XRPLXChainCreateBridgeError(#[from] XRPLXChainCreateBridgeException), + #[error("{0}")] + XRPLXChainCreateClaimIDError(#[from] XRPLXChainCreateClaimIDException), + #[error("{0}")] + XRPLXChainModifyBridgeError(#[from] XRPLXChainModifyBridgeException), + #[error("{0}")] + XRPLCoreError(#[from] XRPLCoreException), + #[error("The transaction must be signed")] TxMustBeSigned, } #[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLTransactionException<'a> {} +impl alloc::error::Error for XRPLTransactionException {} -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLTransactionFieldException<'a> { - #[error("Transaction is missing common field `{0:?}`")] - FieldMissing(&'a str), +#[derive(Debug, PartialEq, Error)] +pub enum XRPLTransactionFieldException { #[error("There is no transaction common field `{0:?}`")] - InvalidCommonField(&'a str), + InvalidCommonField(String), #[error("There is no account field named `{0:?}`")] - UnknownAccountField(&'a str), + UnknownAccountField(String), } -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLAccountSetException<'a> { - /// A fields value exceeds its maximum value. - #[error("The value of the field `{field:?}` is defined above its maximum (max {max:?}, found {found:?}). For more information see: {resource:?}")] - ValueTooHigh { - field: Cow<'a, str>, - max: u32, - found: u32, - resource: Cow<'a, str>, - }, - /// A fields value exceeds its minimum value. - #[error("The value of the field `{field:?}` is defined below its minimum (min {min:?}, found {found:?}). For more information see: {resource:?}")] - ValueTooLow { - field: Cow<'a, str>, - min: u32, - found: u32, - resource: Cow<'a, str>, - }, - /// A fields value exceeds its maximum character length. - #[error("The value of the field `{field:?}` exceeds its maximum length of characters (max {max:?}, found {found:?}). For more information see: {resource:?}")] - ValueTooLong { - field: Cow<'a, str>, - max: usize, - found: usize, - resource: Cow<'a, str>, - }, - /// A fields value doesn't match its required format. - #[error("The value of the field `{field:?}` does not have the correct format (expected {format:?}, found {found:?}). For more information see: {resource:?}")] - InvalidValueFormat { - field: Cow<'a, str>, - format: Cow<'a, str>, - found: Cow<'a, str>, - resource: Cow<'a, str>, - }, +#[derive(Debug, PartialEq, Error)] +pub enum XRPLAccountSetException { /// A field can only be defined if a transaction flag is set. - #[error("For the field `{field:?}` to be defined it is required to set the flag `{flag:?}`. For more information see: {resource:?}")] - FieldRequiresFlag { - field: Cow<'a, str>, - flag: AccountSetFlag, - resource: Cow<'a, str>, - }, + #[error("For the field `{field:?}` to be defined it is required to set the flag `{flag:?}`")] + FieldRequiresFlag { field: String, flag: AccountSetFlag }, /// An account set flag can only be set if a field is defined. - #[error("For the flag `{flag:?}` to be set it is required to define the field `{field:?}`. For more information see: {resource:?}")] - FlagRequiresField { - flag: AccountSetFlag, - field: Cow<'a, str>, - resource: Cow<'a, str>, - }, + #[error("For the flag `{flag:?}` to be set it is required to define the field `{field:?}`")] + FlagRequiresField { flag: AccountSetFlag, field: String }, /// Am account set flag can not be set and unset at the same time. - #[error("A flag cannot be set and unset at the same time (found {found:?}). For more information see: {resource:?}")] - SetAndUnsetSameFlag { - found: AccountSetFlag, - resource: Cow<'a, str>, - }, + #[error("A flag cannot be set and unset at the same time (found {found:?})")] + SetAndUnsetSameFlag { found: AccountSetFlag }, /// A field was defined and an account set flag that is required for that field was unset. - #[error("The field `{field:?}` cannot be defined if its required flag `{flag:?}` is being unset. For more information see: {resource:?}")] - SetFieldWhenUnsetRequiredFlag { - field: Cow<'a, str>, - flag: AccountSetFlag, - resource: Cow<'a, str>, - }, -} - -#[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLAccountSetException<'a> {} - -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLCheckCashException<'a> { - /// A field cannot be defined with other fields. - #[error("The field `{field1:?}` can not be defined with `{field2:?}`. Define exactly one of them. For more information see: {resource:?}")] - DefineExactlyOneOf { - field1: Cow<'a, str>, - field2: Cow<'a, str>, - resource: Cow<'a, str>, - }, -} - -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLDepositPreauthException<'a> { - /// A field cannot be defined with other fields. - #[error("The field `{field1:?}` can not be defined with `{field2:?}`. Define exactly one of them. For more information see: {resource:?}")] - DefineExactlyOneOf { - field1: Cow<'a, str>, - field2: Cow<'a, str>, - resource: Cow<'a, str>, - }, -} - -#[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLCheckCashException<'a> {} - -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLEscrowCreateException<'a> { - /// A fields value cannot be below another fields value. - #[error("The value of the field `{field1:?}` is not allowed to be below the value of the field `{field2:?}` (max {field2_val:?}, found {field1_val:?}). For more information see: {resource:?}")] - ValueBelowValue { - field1: Cow<'a, str>, - field2: Cow<'a, str>, - field1_val: u32, - field2_val: u32, - resource: Cow<'a, str>, - }, -} - -#[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLEscrowCreateException<'a> {} - -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLEscrowFinishException<'a> { - /// For a field to be defined it also needs another field to be defined. - #[error("For the field `{field1:?}` to be defined it is required to also define the field `{field2:?}`. For more information see: {resource:?}")] - FieldRequiresField { - field1: Cow<'a, str>, - field2: Cow<'a, str>, - resource: Cow<'a, str>, - }, -} - -#[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLEscrowFinishException<'a> {} - -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLNFTokenAcceptOfferException<'a> { - /// Define at least one of the fields. - #[error("Define at least one of the fields `{field1:?}` and `{field2:?}`. For more information see: {resource:?}")] - DefineOneOf { - field1: Cow<'a, str>, - field2: Cow<'a, str>, - resource: Cow<'a, str>, - }, - /// The value can not be zero. - #[error("The value of the field `{field:?}` is not allowed to be zero. For more information see: {resource:?}")] - ValueZero { - field: Cow<'a, str>, - resource: Cow<'a, str>, - }, + #[error( + "The field `{field:?}` cannot be defined if its required flag `{flag:?}` is being unset" + )] + SetFieldWhenUnsetRequiredFlag { field: String, flag: AccountSetFlag }, } #[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLNFTokenAcceptOfferException<'a> {} +impl alloc::error::Error for XRPLAccountSetException {} #[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLNFTokenCancelOfferException<'a> { +pub enum XRPLNFTokenCancelOfferException { /// A collection was defined to be empty. - #[error("The value of the field `{field:?}` is not allowed to be empty (type `{r#type:?}`). If the field is optional, define it to be `None`. For more information see: {resource:?}")] - CollectionEmpty { - field: Cow<'a, str>, - r#type: Cow<'a, str>, - resource: Cow<'a, str>, - }, + #[error("The value of the field `{field:?}` is not allowed to be empty (type `{r#type:?}`). If the field is optional, define it to be `None`")] + CollectionEmpty { field: String, r#type: String }, } #[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLNFTokenCancelOfferException<'a> {} +impl alloc::error::Error for XRPLNFTokenCancelOfferException {} #[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLNFTokenCreateOfferException<'a> { - /// The value can not be zero. - #[error("The value of the field `{field:?}` is not allowed to be zero. For more information see: {resource:?}")] - ValueZero { - field: Cow<'a, str>, - resource: Cow<'a, str>, - }, - /// A fields value is not allowed to be the same as another fields value. - #[error("The value of the field `{field1:?}` is not allowed to be the same as the value of the field `{field2:?}`. For more information see: {resource:?}")] - ValueEqualsValue { - field1: Cow<'a, str>, - field2: Cow<'a, str>, - resource: Cow<'a, str>, - }, +pub enum XRPLNFTokenCreateOfferException { /// An optional value must be defined in a certain context. - #[error("The optional field `{field:?}` is required to be defined for {context:?}. For more information see: {resource:?}")] - OptionRequired { - field: Cow<'a, str>, - context: Cow<'a, str>, - resource: Cow<'a, str>, - }, + #[error("The optional field `{field:?}` is required to be defined for {context:?}")] + OptionRequired { field: String, context: String }, /// An optional value is not allowed to be defined in a certain context. - #[error("The optional field `{field:?}` is not allowed to be defined for {context:?}. For more information see: {resource:?}")] - IllegalOption { - field: Cow<'a, str>, - context: Cow<'a, str>, - resource: Cow<'a, str>, - }, + #[error("The optional field `{field:?}` is not allowed to be defined for {context:?}")] + IllegalOption { field: String, context: String }, } #[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLNFTokenCreateOfferException<'a> {} +impl alloc::error::Error for XRPLNFTokenCreateOfferException {} #[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLNFTokenMintException<'a> { - /// A fields value is not allowed to be the same as another fields value. - #[error("The value of the field `{field1:?}` is not allowed to be the same as the value of the field `{field2:?}`. For more information see: {resource:?}")] - ValueEqualsValue { - field1: Cow<'a, str>, - field2: Cow<'a, str>, - resource: Cow<'a, str>, - }, - /// A fields value exceeds its maximum value. - #[error("The field `{field:?}` exceeds its maximum value (max {max:?}, found {found:?}). For more information see: {resource:?}")] - ValueTooHigh { - field: Cow<'a, str>, - max: u32, - found: u32, - resource: Cow<'a, str>, - }, - /// A fields value exceeds its maximum character length. - #[error("The value of the field `{field:?}` exceeds its maximum length of characters (max {max:?}, found {found:?}). For more information see: {resource:?}")] - ValueTooLong { - field: Cow<'a, str>, - max: usize, - found: usize, - resource: Cow<'a, str>, - }, -} - -#[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLNFTokenMintException<'a> {} - -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLPaymentException<'a> { +pub enum XRPLPaymentException { /// An optional value must be defined in a certain context. - #[error("The optional field `{field:?}` is required to be defined for {context:?}. For more information see: {resource:?}")] - OptionRequired { - field: Cow<'a, str>, - context: Cow<'a, str>, - resource: Cow<'a, str>, - }, + #[error("The optional field `{field:?}` is required to be defined for {context:?}")] + OptionRequired { field: String, context: String }, /// An optional value is not allowed to be defined in a certain context. - #[error("The optional field `{field:?}` is not allowed to be defined for {context:?}.For more information see: {resource:?}")] - IllegalOption { - field: Cow<'a, str>, - context: Cow<'a, str>, - resource: Cow<'a, str>, - }, + #[error("The optional field `{field:?}` is not allowed to be defined for {context:?}")] + IllegalOption { field: String, context: String }, /// A fields value is not allowed to be the same as another fields value, in a certain context. - #[error("The value of the field `{field1:?}` is not allowed to be the same as the value of the field `{field2:?}`, for {context:?}. For more information see: {resource:?}")] + #[error("The value of the field `{field1:?}` is not allowed to be the same as the value of the field `{field2:?}`, for {context:?}")] ValueEqualsValueInContext { - field1: Cow<'a, str>, - field2: Cow<'a, str>, - context: Cow<'a, str>, - resource: Cow<'a, str>, + field1: String, + field2: String, + context: String, }, /// An account set flag can only be set if a field is defined. - #[error("For the flag `{flag:?}` to be set it is required to define the field `{field:?}`. For more information see: {resource:?}")] - FlagRequiresField { - flag: PaymentFlag, - field: Cow<'a, str>, - resource: Cow<'a, str>, - }, + #[error("For the flag `{flag:?}` to be set it is required to define the field `{field:?}`")] + FlagRequiresField { flag: PaymentFlag, field: String }, } #[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLPaymentException<'a> {} +impl alloc::error::Error for XRPLPaymentException {} #[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum XRPLSignerListSetException<'a> { +#[non_exhaustive] +pub enum XRPLSignerListSetException { /// A field was defined that another field definition would delete. - #[error("The value of the field `{field1:?}` can not be defined with the field `{field2:?}` because it would cause the deletion of `{field1:?}`. For more information see: {resource:?}")] - ValueCausesValueDeletion { - field1: Cow<'a, str>, - field2: Cow<'a, str>, - resource: Cow<'a, str>, - }, + #[error("The value of the field `{field1:?}` can not be defined with the field `{field2:?}` because it would cause the deletion of `{field1:?}`")] + ValueCausesValueDeletion { field1: String, field2: String }, /// A field is expected to have a certain value to be deleted. - #[error("The field `{field:?}` has the wrong value to be deleted (expected {expected:?}, found {found:?}). For more information see: {resource:?}")] + #[error("The field `{field:?}` has the wrong value to be deleted (expected {expected:?}, found {found:?})")] InvalidValueForValueDeletion { - field: Cow<'a, str>, + field: String, expected: u32, found: u32, - resource: Cow<'a, str>, }, /// A collection has too few items in it. - #[error("The value of the field `{field:?}` has too few items in it (min {min:?}, found {found:?}). For more information see: {resource:?}")] + #[error( + "The value of the field `{field:?}` has too few items in it (min {min:?}, found {found:?})" + )] CollectionTooFewItems { - field: Cow<'a, str>, + field: String, min: usize, found: usize, - resource: Cow<'a, str>, }, /// A collection has too many items in it. - #[error("The value of the field `{field:?}` has too many items in it (max {max:?}, found {found:?}). For more information see: {resource:?}")] + #[error("The value of the field `{field:?}` has too many items in it (max {max:?}, found {found:?})")] CollectionTooManyItems { - field: Cow<'a, str>, + field: String, max: usize, found: usize, - resource: Cow<'a, str>, }, /// A collection is not allowed to have duplicates in it. - #[error("The value of the field `{field:?}` has a duplicate in it (found {found:?}). For more information see: {resource:?}")] - CollectionItemDuplicate { - field: Cow<'a, str>, - found: Cow<'a, str>, - resource: Cow<'a, str>, - }, + #[error("The value of the field `{field:?}` has a duplicate in it (found {found:?})")] + CollectionItemDuplicate { field: String, found: String }, /// A collection contains an invalid value. - #[error("The field `{field:?}` contains an invalid value (found {found:?}). For more information see: {resource:?}")] - CollectionInvalidItem { - field: Cow<'a, str>, - found: Cow<'a, str>, - resource: Cow<'a, str>, - }, - #[error("The field `signer_quorum` must be below or equal to the sum of `signer_weight` in `signer_entries`. For more information see: {resource:?}")] - SignerQuorumExceedsSignerWeight { - max: u32, - found: u32, - resource: Cow<'a, str>, - }, + #[error("The field `{field:?}` contains an invalid value (found {found:?})")] + CollectionInvalidItem { field: String, found: String }, + #[error("The field `signer_quorum` must be below or equal to the sum of `signer_weight` in `signer_entries`")] + SignerQuorumExceedsSignerWeight { max: u32, found: u32 }, } #[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLSignerListSetException<'a> {} +impl alloc::error::Error for XRPLSignerListSetException {} #[derive(Debug, Clone, PartialEq, Eq, Error)] +#[non_exhaustive] pub enum XRPLXChainClaimException { #[error("`amount` must match either `locking_chain_issue` or `issuing_chain_issue`")] AmountMismatch, @@ -356,6 +162,7 @@ pub enum XRPLXChainClaimException { impl alloc::error::Error for XRPLXChainClaimException {} #[derive(Debug, Clone, PartialEq, Eq, Error)] +#[non_exhaustive] pub enum XRPLXChainCreateBridgeException { #[error("Cannot have the same door accounts on the locking and issuing chain")] SameDoorAccounts, @@ -377,6 +184,7 @@ pub enum XRPLXChainCreateBridgeException { impl alloc::error::Error for XRPLXChainCreateBridgeException {} #[derive(Debug, Clone, PartialEq, Eq, Error)] +#[non_exhaustive] pub enum XRPLXChainCreateClaimIDException { #[error("`other_chain_source` must be a valid XRPL address")] OtherChainSourceIsInvalid, @@ -386,6 +194,7 @@ pub enum XRPLXChainCreateClaimIDException { impl alloc::error::Error for XRPLXChainCreateClaimIDException {} #[derive(Debug, Clone, PartialEq, Eq, Error)] +#[non_exhaustive] pub enum XRPLXChainModifyBridgeException { #[error("Must either change `signature_reward`, change `min_account_create_amount`, or clear `min_account_create_amount`")] MustChangeOrClear, diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 406a2e7e..65ea2256 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -33,16 +33,14 @@ pub mod xchain_create_bridge; pub mod xchain_create_claim_id; pub mod xchain_modify_bridge; -use super::FlagCollection; +use super::{FlagCollection, XRPLModelResult}; use crate::core::binarycodec::encode; use crate::models::amount::XRPAmount; -use crate::Err; use crate::{_serde::txn_flags, serde_with_tag}; use alloc::borrow::Cow; use alloc::format; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use anyhow::Result; use core::fmt::Debug; use derive_new::new; use exceptions::XRPLTransactionException; @@ -321,11 +319,10 @@ where fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, T>; - fn get_field_value(&self, field: &str) -> Result> { - match serde_json::to_value(self) { - Ok(value) => Ok(value.get(field).map(|v| v.to_string())), - Err(e) => Err!(e), - } + fn get_field_value(&self, field: &str) -> XRPLModelResult> { + let value = serde_json::to_value(self)?; + + Ok(value.get(field).map(|v| v.to_string())) } fn is_signed(&self) -> bool { @@ -335,22 +332,19 @@ where /// Hashes the Transaction object as the ledger does. Only valid for signed /// Transaction objects. - fn get_hash(&self) -> Result> + fn get_hash(&self) -> XRPLModelResult> where Self: Serialize + DeserializeOwned + Debug + Clone, { if self.get_common_fields().txn_signature.is_none() && self.get_common_fields().signers.is_none() { - return Err!(XRPLTransactionException::TxMustBeSigned); + return Err(XRPLTransactionException::TxMustBeSigned.into()); } let prefix = format!("{:X}", TRANSACTION_HASH_PREFIX); - let tx_hex = encode(self)?; + let tx_hex = encode(self).map_err(XRPLTransactionException::XRPLCoreError)?; let tx_hex = prefix + &tx_hex; - let tx_bytes = match hex::decode(&tx_hex) { - Ok(bytes) => bytes, - Err(e) => return Err!(e), - }; + let tx_bytes = hex::decode(&tx_hex)?; let mut hasher = Sha512::new(); hasher.update(&tx_bytes); let hash = hasher.finalize(); @@ -376,15 +370,15 @@ pub enum Flag { #[cfg(all( feature = "std", feature = "websocket", - feature = "transaction-models", - feature = "transaction-helpers", + feature = "models", + feature = "helpers", feature = "wallet" ))] #[cfg(test)] mod test_tx_common_fields { use super::*; use account_set::AccountSet; - + use offer_create::OfferCreate; #[tokio::test] diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index c82219ae..e52b25c0 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -1,20 +1,17 @@ -use crate::Err; use alloc::borrow::Cow; use alloc::vec::Vec; -use anyhow::Result; +use bigdecimal::{BigDecimal, Zero}; use core::convert::TryInto; -use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::transactions::exceptions::XRPLNFTokenAcceptOfferException; use crate::models::{ amount::Amount, transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; -use crate::models::{FlagCollection, NoFlags}; +use crate::models::{FlagCollection, NoFlags, XRPLModelException, XRPLModelResult}; use super::CommonFields; @@ -58,14 +55,11 @@ pub struct NFTokenAcceptOffer<'a> { } impl<'a: 'static> Model for NFTokenAcceptOffer<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_brokered_mode_error() { - Err(error) => Err!(error), - Ok(_no_error) => match self._get_nftoken_broker_fee_error() { - Err(error) => Err!(error), - Ok(_no_error) => Ok(()), - }, - } + fn get_errors(&self) -> XRPLModelResult<()> { + self._get_brokered_mode_error()?; + self._get_nftoken_broker_fee_error()?; + + Ok(()) } } @@ -84,28 +78,24 @@ impl<'a> Transaction<'a, NoFlags> for NFTokenAcceptOffer<'a> { } impl<'a> NFTokenAcceptOfferError for NFTokenAcceptOffer<'a> { - fn _get_brokered_mode_error(&self) -> Result<(), XRPLNFTokenAcceptOfferException> { + fn _get_brokered_mode_error(&self) -> XRPLModelResult<()> { if self.nftoken_broker_fee.is_some() && self.nftoken_sell_offer.is_none() && self.nftoken_buy_offer.is_none() { - Err(XRPLNFTokenAcceptOfferException::DefineOneOf { - field1: "nftoken_sell_offer".into(), - field2: "nftoken_buy_offer".into(), - resource: "".into(), - }) + Err(XRPLModelException::ExpectedOneOf(&[ + "nftoken_sell_offer", + "nftoken_buy_offer", + ])) } else { Ok(()) } } - fn _get_nftoken_broker_fee_error(&self) -> Result<()> { + fn _get_nftoken_broker_fee_error(&self) -> XRPLModelResult<()> { if let Some(nftoken_broker_fee) = &self.nftoken_broker_fee { - let nftoken_broker_fee_decimal: Decimal = nftoken_broker_fee.clone().try_into()?; + let nftoken_broker_fee_decimal: BigDecimal = nftoken_broker_fee.clone().try_into()?; if nftoken_broker_fee_decimal.is_zero() { - Err!(XRPLNFTokenAcceptOfferException::ValueZero { - field: "nftoken_broker_fee".into(), - resource: "".into(), - }) + Err(XRPLModelException::ValueZero("nftoken_broker_fee".into())) } else { Ok(()) } @@ -155,8 +145,8 @@ impl<'a> NFTokenAcceptOffer<'a> { } pub trait NFTokenAcceptOfferError { - fn _get_brokered_mode_error(&self) -> Result<(), XRPLNFTokenAcceptOfferException>; - fn _get_nftoken_broker_fee_error(&self) -> Result<()>; + fn _get_brokered_mode_error(&self) -> XRPLModelResult<()>; + fn _get_nftoken_broker_fee_error(&self) -> XRPLModelResult<()>; } #[cfg(test)] @@ -189,8 +179,12 @@ mod test_nftoken_accept_offer_error { ); assert_eq!( - nftoken_accept_offer.validate().unwrap_err().to_string().as_str(), - "Define at least one of the fields `nftoken_sell_offer` and `nftoken_buy_offer`. For more information see: " + nftoken_accept_offer + .validate() + .unwrap_err() + .to_string() + .as_str(), + "Expected one of: nftoken_sell_offer, nftoken_buy_offer" ); } @@ -212,8 +206,12 @@ mod test_nftoken_accept_offer_error { ); assert_eq!( - nftoken_accept_offer.validate().unwrap_err().to_string().as_str(), - "The value of the field `nftoken_broker_fee` is not allowed to be zero. For more information see: " + nftoken_accept_offer + .validate() + .unwrap_err() + .to_string() + .as_str(), + "The value of the field `\"nftoken_broker_fee\"` is not allowed to be zero" ); } } diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index 6fb637e6..ca2ea623 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -1,7 +1,5 @@ -use crate::Err; use alloc::borrow::Cow; use alloc::vec::Vec; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -11,7 +9,7 @@ use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; -use crate::models::{FlagCollection, NoFlags}; +use crate::models::{FlagCollection, NoFlags, XRPLModelResult}; use super::CommonFields; @@ -48,11 +46,10 @@ pub struct NFTokenCancelOffer<'a> { } impl<'a: 'static> Model for NFTokenCancelOffer<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_nftoken_offers_error() { - Ok(_) => Ok(()), - Err(error) => Err!(error), - } + fn get_errors(&self) -> XRPLModelResult<()> { + self._get_nftoken_offers_error()?; + + Ok(()) } } @@ -71,13 +68,13 @@ impl<'a> Transaction<'a, NoFlags> for NFTokenCancelOffer<'a> { } impl<'a> NFTokenCancelOfferError for NFTokenCancelOffer<'a> { - fn _get_nftoken_offers_error(&self) -> Result<(), XRPLNFTokenCancelOfferException> { + fn _get_nftoken_offers_error(&self) -> XRPLModelResult<()> { if self.nftoken_offers.is_empty() { Err(XRPLNFTokenCancelOfferException::CollectionEmpty { field: "nftoken_offers".into(), r#type: stringify!(Vec).into(), - resource: "".into(), - }) + } + .into()) } else { Ok(()) } @@ -120,7 +117,7 @@ impl<'a> NFTokenCancelOffer<'a> { } pub trait NFTokenCancelOfferError { - fn _get_nftoken_offers_error(&self) -> Result<(), XRPLNFTokenCancelOfferException>; + fn _get_nftoken_offers_error(&self) -> XRPLModelResult<()>; } #[cfg(test)] @@ -149,7 +146,7 @@ mod test_nftoken_cancel_offer_error { assert_eq!( nftoken_cancel_offer.validate().unwrap_err().to_string().as_str(), - "The value of the field `nftoken_offers` is not allowed to be empty (type `Vec`). If the field is optional, define it to be `None`. For more information see: " + "The value of the field `\"nftoken_offers\"` is not allowed to be empty (type `\"Vec\"`). If the field is optional, define it to be `None`" ); } } diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index a9315866..9f397259 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -1,8 +1,7 @@ use alloc::borrow::Cow; use alloc::vec::Vec; -use anyhow::Result; +use bigdecimal::{BigDecimal, Zero}; use core::convert::TryInto; -use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -10,12 +9,11 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, - Model, + Model, XRPLModelException, XRPLModelResult, }; use crate::models::amount::{Amount, XRPAmount}; use crate::models::transactions::exceptions::XRPLNFTokenCreateOfferException; -use crate::Err; use super::{CommonFields, FlagCollection}; @@ -81,17 +79,12 @@ pub struct NFTokenCreateOffer<'a> { } impl<'a: 'static> Model for NFTokenCreateOffer<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_amount_error() { - Err(error) => Err!(error), - Ok(_no_error) => match self._get_destination_error() { - Err(error) => Err!(error), - Ok(_no_error) => match self._get_owner_error() { - Err(error) => Err!(error), - Ok(_no_error) => Ok(()), - }, - }, - } + fn get_errors(&self) -> XRPLModelResult<()> { + self._get_amount_error()?; + self._get_destination_error()?; + self._get_owner_error()?; + + Ok(()) } } @@ -114,25 +107,21 @@ impl<'a> Transaction<'a, NFTokenCreateOfferFlag> for NFTokenCreateOffer<'a> { } impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { - fn _get_amount_error(&self) -> Result<()> { - let amount_into_decimal: Decimal = self.amount.clone().try_into()?; + fn _get_amount_error(&self) -> XRPLModelResult<()> { + let amount_into_decimal: BigDecimal = self.amount.clone().try_into()?; if !self.has_flag(&NFTokenCreateOfferFlag::TfSellOffer) && amount_into_decimal.is_zero() { - Err!(XRPLNFTokenCreateOfferException::ValueZero { - field: "amount".into(), - resource: "".into(), - }) + Err(XRPLModelException::ValueZero("amount".into())) } else { Ok(()) } } - fn _get_destination_error(&self) -> Result<(), XRPLNFTokenCreateOfferException> { + fn _get_destination_error(&self) -> XRPLModelResult<()> { if let Some(destination) = self.destination.clone() { if destination == self.common_fields.account { - Err(XRPLNFTokenCreateOfferException::ValueEqualsValue { + Err(XRPLModelException::ValueEqualsValue { field1: "destination".into(), field2: "account".into(), - resource: "".into(), }) } else { Ok(()) @@ -142,19 +131,18 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { } } - fn _get_owner_error(&self) -> Result<(), XRPLNFTokenCreateOfferException> { + fn _get_owner_error(&self) -> XRPLModelResult<()> { if let Some(owner) = self.owner.clone() { if self.has_flag(&NFTokenCreateOfferFlag::TfSellOffer) { Err(XRPLNFTokenCreateOfferException::IllegalOption { field: "owner".into(), context: "NFToken sell offers".into(), - resource: "".into(), - }) + } + .into()) } else if owner == self.common_fields.account { - Err(XRPLNFTokenCreateOfferException::ValueEqualsValue { + Err(XRPLModelException::ValueEqualsValue { field1: "owner".into(), field2: "account".into(), - resource: "".into(), }) } else { Ok(()) @@ -163,8 +151,8 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { Err(XRPLNFTokenCreateOfferException::OptionRequired { field: "owner".into(), context: "NFToken buy offers".into(), - resource: "".into(), - }) + } + .into()) } else { Ok(()) } @@ -216,9 +204,9 @@ impl<'a> NFTokenCreateOffer<'a> { } pub trait NFTokenCreateOfferError { - fn _get_amount_error(&self) -> Result<()>; - fn _get_destination_error(&self) -> Result<(), XRPLNFTokenCreateOfferException>; - fn _get_owner_error(&self) -> Result<(), XRPLNFTokenCreateOfferException>; + fn _get_amount_error(&self) -> XRPLModelResult<()>; + fn _get_destination_error(&self) -> XRPLModelResult<()>; + fn _get_owner_error(&self) -> XRPLModelResult<()>; } #[cfg(test)] @@ -259,7 +247,7 @@ mod test_nftoken_create_offer_error { .unwrap_err() .to_string() .as_str(), - "The value of the field `amount` is not allowed to be zero. For more information see: " + "The value of the field `\"amount\"` is not allowed to be zero" ); } @@ -285,7 +273,7 @@ mod test_nftoken_create_offer_error { assert_eq!( nftoken_create_offer.validate().unwrap_err().to_string().as_str(), - "The value of the field `destination` is not allowed to be the same as the value of the field `account`. For more information see: " + "The value of the field `\"destination\"` is not allowed to be the same as the value of the field `\"account\"`" ); } @@ -313,22 +301,26 @@ mod test_nftoken_create_offer_error { assert_eq!( nftoken_create_offer.validate().unwrap_err().to_string().as_str(), - "The optional field `owner` is not allowed to be defined for NFToken sell offers. For more information see: " + "The optional field `\"owner\"` is not allowed to be defined for \"NFToken sell offers\"" ); nftoken_create_offer.common_fields.flags = FlagCollection::default(); nftoken_create_offer.owner = None; assert_eq!( - nftoken_create_offer.validate().unwrap_err().to_string().as_str(), - "The optional field `owner` is required to be defined for NFToken buy offers. For more information see: " + nftoken_create_offer + .validate() + .unwrap_err() + .to_string() + .as_str(), + "The optional field `\"owner\"` is required to be defined for \"NFToken buy offers\"" ); nftoken_create_offer.owner = Some("rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into()); assert_eq!( nftoken_create_offer.validate().unwrap_err().to_string().as_str(), - "The value of the field `owner` is not allowed to be the same as the value of the field `account`. For more information see: " + "The value of the field `\"owner\"` is not allowed to be the same as the value of the field `\"account\"`" ); } } diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index 1c491105..3bb480ba 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -1,6 +1,5 @@ use alloc::borrow::Cow; use alloc::vec::Vec; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -10,13 +9,11 @@ use crate::{ constants::{MAX_TRANSFER_FEE, MAX_URI_LENGTH}, models::{ transactions::{Memo, Signer, Transaction, TransactionType}, - Model, + Model, XRPLModelException, XRPLModelResult, }, - Err, }; use crate::models::amount::XRPAmount; -use crate::models::transactions::exceptions::XRPLNFTokenMintException; use super::{CommonFields, FlagCollection}; @@ -90,18 +87,13 @@ pub struct NFTokenMint<'a> { pub uri: Option>, } -impl<'a: 'static> Model for NFTokenMint<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_issuer_error() { - Err(error) => Err!(error), - Ok(_no_error) => match self._get_transfer_fee_error() { - Err(error) => Err!(error), - Ok(_no_error) => match self._get_uri_error() { - Err(error) => Err!(error), - Ok(_no_error) => Ok(()), - }, - }, - } +impl<'a> Model for NFTokenMint<'a> { + fn get_errors(&self) -> XRPLModelResult<()> { + self._get_issuer_error()?; + self._get_transfer_fee_error()?; + self._get_uri_error()?; + + Ok(()) } } @@ -124,14 +116,14 @@ impl<'a> Transaction<'a, NFTokenMintFlag> for NFTokenMint<'a> { } impl<'a> NFTokenMintError for NFTokenMint<'a> { - fn _get_issuer_error(&self) -> Result<(), XRPLNFTokenMintException> { + fn _get_issuer_error(&self) -> XRPLModelResult<()> { if let Some(issuer) = self.issuer.clone() { if issuer == self.common_fields.account { - Err(XRPLNFTokenMintException::ValueEqualsValue { + Err(XRPLModelException::ValueEqualsValue { field1: "issuer".into(), field2: "account".into(), - resource: "".into(), - }) + } + .into()) } else { Ok(()) } @@ -140,15 +132,15 @@ impl<'a> NFTokenMintError for NFTokenMint<'a> { } } - fn _get_transfer_fee_error(&self) -> Result<(), XRPLNFTokenMintException> { + fn _get_transfer_fee_error(&self) -> XRPLModelResult<()> { if let Some(transfer_fee) = self.transfer_fee { if transfer_fee > MAX_TRANSFER_FEE { - Err(XRPLNFTokenMintException::ValueTooHigh { + Err(XRPLModelException::ValueTooHigh { field: "transfer_fee".into(), max: MAX_TRANSFER_FEE, found: transfer_fee, - resource: "".into(), - }) + } + .into()) } else { Ok(()) } @@ -157,15 +149,15 @@ impl<'a> NFTokenMintError for NFTokenMint<'a> { } } - fn _get_uri_error(&self) -> Result<(), XRPLNFTokenMintException> { + fn _get_uri_error(&self) -> XRPLModelResult<()> { if let Some(uri) = self.uri.clone() { if uri.len() > MAX_URI_LENGTH { - Err(XRPLNFTokenMintException::ValueTooLong { + Err(XRPLModelException::ValueTooLong { field: "uri".into(), max: MAX_URI_LENGTH, found: uri.len(), - resource: "".into(), - }) + } + .into()) } else { Ok(()) } @@ -218,9 +210,9 @@ impl<'a> NFTokenMint<'a> { } pub trait NFTokenMintError { - fn _get_issuer_error(&self) -> Result<(), XRPLNFTokenMintException>; - fn _get_transfer_fee_error(&self) -> Result<(), XRPLNFTokenMintException>; - fn _get_uri_error(&self) -> Result<(), XRPLNFTokenMintException>; + fn _get_issuer_error(&self) -> XRPLModelResult<()>; + fn _get_transfer_fee_error(&self) -> XRPLModelResult<()>; + fn _get_uri_error(&self) -> XRPLModelResult<()>; } #[cfg(test)] @@ -252,7 +244,7 @@ mod test_nftoken_mint_error { assert_eq!( nftoken_mint.validate().unwrap_err().to_string().as_str(), - "The value of the field `issuer` is not allowed to be the same as the value of the field `account`. For more information see: " + "The value of the field `\"issuer\"` is not allowed to be the same as the value of the field `\"account\"`" ); } @@ -277,7 +269,7 @@ mod test_nftoken_mint_error { assert_eq!( nftoken_mint.validate().unwrap_err().to_string().as_str(), - "The field `transfer_fee` exceeds its maximum value (max 50000, found 50001). For more information see: " + "The value of the field `\"transfer_fee\"` is defined above its maximum (max 50000, found 50001)" ); } @@ -302,7 +294,7 @@ mod test_nftoken_mint_error { assert_eq!( nftoken_mint.validate().unwrap_err().to_string().as_str(), - "The value of the field `uri` exceeds its maximum length of characters (max 512, found 513). For more information see: " + "The value of the field `\"uri\"` exceeds its maximum length of characters (max 512, found 513)" ); } } diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index ee13ce4b..c4be6bff 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -1,6 +1,5 @@ use alloc::borrow::Cow; use alloc::vec::Vec; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -9,12 +8,11 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ amount::Amount, transactions::{Memo, Signer, Transaction, TransactionType}, - Model, PathStep, + Model, PathStep, XRPLModelResult, }; use crate::models::amount::XRPAmount; use crate::models::transactions::exceptions::XRPLPaymentException; -use crate::Err; use super::{CommonFields, FlagCollection}; @@ -90,17 +88,12 @@ pub struct Payment<'a> { } impl<'a: 'static> Model for Payment<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_xrp_transaction_error() { - Err(error) => Err!(error), - Ok(_no_error) => match self._get_partial_payment_error() { - Err(error) => Err!(error), - Ok(_no_error) => match self._get_exchange_error() { - Err(error) => Err!(error), - Ok(_no_error) => Ok(()), - }, - }, - } + fn get_errors(&self) -> XRPLModelResult<()> { + self._get_xrp_transaction_error()?; + self._get_partial_payment_error()?; + self._get_exchange_error()?; + + Ok(()) } } @@ -123,21 +116,21 @@ impl<'a> Transaction<'a, PaymentFlag> for Payment<'a> { } impl<'a> PaymentError for Payment<'a> { - fn _get_xrp_transaction_error(&self) -> Result<(), XRPLPaymentException> { + fn _get_xrp_transaction_error(&self) -> XRPLModelResult<()> { if self.amount.is_xrp() && self.send_max.is_none() { if self.paths.is_some() { Err(XRPLPaymentException::IllegalOption { field: "paths".into(), context: "XRP to XRP payments".into(), - resource: "".into(), - }) + } + .into()) } else if self.common_fields.account == self.destination { Err(XRPLPaymentException::ValueEqualsValueInContext { field1: "account".into(), field2: "destination".into(), context: "XRP to XRP Payments".into(), - resource: "".into(), - }) + } + .into()) } else { Ok(()) } @@ -146,7 +139,7 @@ impl<'a> PaymentError for Payment<'a> { } } - fn _get_partial_payment_error(&self) -> Result<(), XRPLPaymentException> { + fn _get_partial_payment_error(&self) -> XRPLModelResult<()> { if let Some(send_max) = &self.send_max { if !self.has_flag(&PaymentFlag::TfPartialPayment) && send_max.is_xrp() @@ -155,8 +148,8 @@ impl<'a> PaymentError for Payment<'a> { Err(XRPLPaymentException::IllegalOption { field: "send_max".into(), context: "XRP to XRP non-partial payments".into(), - resource: "".into(), - }) + } + .into()) } else { Ok(()) } @@ -164,15 +157,15 @@ impl<'a> PaymentError for Payment<'a> { Err(XRPLPaymentException::FlagRequiresField { flag: PaymentFlag::TfPartialPayment, field: "send_max".into(), - resource: "".into(), - }) + } + .into()) } else if !self.has_flag(&PaymentFlag::TfPartialPayment) { if let Some(_deliver_min) = &self.deliver_min { Err(XRPLPaymentException::IllegalOption { field: "deliver_min".into(), context: "XRP to XRP non-partial payments".into(), - resource: "".into(), - }) + } + .into()) } else { Ok(()) } @@ -181,13 +174,13 @@ impl<'a> PaymentError for Payment<'a> { } } - fn _get_exchange_error(&self) -> Result<(), XRPLPaymentException> { + fn _get_exchange_error(&self) -> XRPLModelResult<()> { if self.common_fields.account == self.destination && self.send_max.is_none() { return Err(XRPLPaymentException::OptionRequired { field: "send_max".into(), context: "exchanges".into(), - resource: "".into(), - }); + } + .into()); } Ok(()) @@ -243,9 +236,9 @@ impl<'a> Payment<'a> { } pub trait PaymentError { - fn _get_xrp_transaction_error(&self) -> Result<(), XRPLPaymentException>; - fn _get_partial_payment_error(&self) -> Result<(), XRPLPaymentException>; - fn _get_exchange_error(&self) -> Result<(), XRPLPaymentException>; + fn _get_xrp_transaction_error(&self) -> XRPLModelResult<()>; + fn _get_partial_payment_error(&self) -> XRPLModelResult<()>; + fn _get_exchange_error(&self) -> XRPLModelResult<()>; } #[cfg(test)] @@ -290,7 +283,7 @@ mod test_payment_error { assert_eq!( payment.validate().unwrap_err().to_string().as_str(), - "The optional field `paths` is not allowed to be defined for XRP to XRP payments.For more information see: " + "The optional field `\"paths\"` is not allowed to be defined for \"XRP to XRP payments\"" ); payment.paths = None; @@ -298,7 +291,7 @@ mod test_payment_error { assert_eq!( payment.validate().unwrap_err().to_string().as_str(), - "The optional field `send_max` is not allowed to be defined for XRP to XRP non-partial payments.For more information see: " + "The optional field `\"send_max\"` is not allowed to be defined for \"XRP to XRP non-partial payments\"" ); payment.send_max = None; @@ -306,7 +299,7 @@ mod test_payment_error { assert_eq!( payment.validate().unwrap_err().to_string().as_str(), - "The value of the field `account` is not allowed to be the same as the value of the field `destination`, for XRP to XRP Payments. For more information see: " + "The value of the field `\"account\"` is not allowed to be the same as the value of the field `\"destination\"`, for \"XRP to XRP Payments\"" ); } @@ -335,7 +328,7 @@ mod test_payment_error { assert_eq!( payment.validate().unwrap_err().to_string().as_str(), - "For the flag `TfPartialPayment` to be set it is required to define the field `send_max`. For more information see: " + "For the flag `TfPartialPayment` to be set it is required to define the field `\"send_max\"`" ); payment.common_fields.flags = FlagCollection::default(); @@ -343,7 +336,7 @@ mod test_payment_error { assert_eq!( payment.validate().unwrap_err().to_string().as_str(), - "The optional field `deliver_min` is not allowed to be defined for XRP to XRP non-partial payments.For more information see: " + "The optional field `\"deliver_min\"` is not allowed to be defined for \"XRP to XRP non-partial payments\"" ); } @@ -375,7 +368,7 @@ mod test_payment_error { assert_eq!( payment.validate().unwrap_err().to_string().as_str(), - "The optional field `send_max` is required to be defined for exchanges. For more information see: " + "The optional field `\"send_max\"` is required to be defined for \"exchanges\"" ); } } diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index dadcb6b1..b85b359a 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -2,7 +2,6 @@ use alloc::borrow::Cow; use alloc::string::String; use alloc::string::ToString; use alloc::vec::Vec; -use anyhow::Result; use derive_new::new; use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -10,12 +9,13 @@ use serde_with::skip_serializing_none; use crate::models::transactions::exceptions::XRPLSignerListSetException; use crate::models::FlagCollection; use crate::models::NoFlags; +use crate::models::XRPLModelResult; use crate::models::{ amount::XRPAmount, transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; -use crate::{serde_with_tag, Err}; +use crate::serde_with_tag; use super::CommonFields; @@ -64,14 +64,11 @@ pub struct SignerListSet<'a> { } impl<'a> Model for SignerListSet<'a> { - fn get_errors(&self) -> Result<()> { - match self._get_signer_entries_error() { - Err(error) => Err!(error), - Ok(_no_error) => match self._get_signer_quorum_error() { - Err(error) => Err!(error), - Ok(_no_error) => Ok(()), - }, - } + fn get_errors(&self) -> XRPLModelResult<()> { + self._get_signer_entries_error()?; + self._get_signer_quorum_error()?; + + Ok(()) } } @@ -90,28 +87,28 @@ impl<'a> Transaction<'a, NoFlags> for SignerListSet<'a> { } impl<'a> SignerListSetError for SignerListSet<'a> { - fn _get_signer_entries_error(&self) -> Result<(), XRPLSignerListSetException> { + fn _get_signer_entries_error(&self) -> XRPLModelResult<()> { if let Some(signer_entries) = &self.signer_entries { if self.signer_quorum == 0 { Err(XRPLSignerListSetException::ValueCausesValueDeletion { field1: "signer_entries".into(), field2: "signer_quorum".into(), - resource: "".into(), - }) + } + .into()) } else if signer_entries.is_empty() { Err(XRPLSignerListSetException::CollectionTooFewItems { field: "signer_entries".into(), min: 1_usize, found: signer_entries.len(), - resource: "".into(), - }) + } + .into()) } else if signer_entries.len() > 8 { Err(XRPLSignerListSetException::CollectionTooManyItems { field: "signer_entries".into(), max: 8_usize, found: signer_entries.len(), - resource: "".into(), - }) + } + .into()) } else { Ok(()) } @@ -120,7 +117,7 @@ impl<'a> SignerListSetError for SignerListSet<'a> { } } - fn _get_signer_quorum_error(&self) -> Result<(), XRPLSignerListSetException> { + fn _get_signer_quorum_error(&self) -> XRPLModelResult<()> { let mut accounts = Vec::new(); let mut signer_weight_sum: u32 = 0; if self.signer_entries.is_some() { @@ -137,8 +134,8 @@ impl<'a> SignerListSetError for SignerListSet<'a> { return Err(XRPLSignerListSetException::CollectionItemDuplicate { field: "signer_entries".into(), found: account.into(), - resource: "".into(), - }); + } + .into()); } else { check_account.push(account); } @@ -147,16 +144,16 @@ impl<'a> SignerListSetError for SignerListSet<'a> { if accounts.contains(&self.common_fields.account.to_string()) { Err(XRPLSignerListSetException::CollectionInvalidItem { field: "signer_entries".into(), - found: self.common_fields.account.clone(), - resource: "".into(), - }) + found: self.common_fields.account.clone().into(), + } + .into()) } else if self.signer_quorum > signer_weight_sum { Err( XRPLSignerListSetException::SignerQuorumExceedsSignerWeight { max: signer_weight_sum, found: self.signer_quorum, - resource: "".into(), - }, + } + .into(), ) } else { Ok(()) @@ -166,8 +163,8 @@ impl<'a> SignerListSetError for SignerListSet<'a> { field: "signer_quorum".into(), expected: 0, found: self.signer_quorum, - resource: "".into(), - }) + } + .into()) } else { Ok(()) } @@ -212,8 +209,8 @@ impl<'a> SignerListSet<'a> { } pub trait SignerListSetError { - fn _get_signer_entries_error(&self) -> Result<(), XRPLSignerListSetException>; - fn _get_signer_quorum_error(&self) -> Result<(), XRPLSignerListSetException>; + fn _get_signer_entries_error(&self) -> XRPLModelResult<()>; + fn _get_signer_quorum_error(&self) -> XRPLModelResult<()>; } #[cfg(test)] @@ -246,7 +243,7 @@ mod test_signer_list_set_error { assert_eq!( signer_list_set.validate().unwrap_err().to_string().as_str(), - "The value of the field `signer_entries` can not be defined with the field `signer_quorum` because it would cause the deletion of `signer_entries`. For more information see: " + "The value of the field `\"signer_entries\"` can not be defined with the field `\"signer_quorum\"` because it would cause the deletion of `\"signer_entries\"`" ); signer_list_set.signer_quorum = 3; @@ -254,7 +251,7 @@ mod test_signer_list_set_error { assert_eq!( signer_list_set.validate().unwrap_err().to_string().as_str(), - "The field `signer_quorum` has the wrong value to be deleted (expected 0, found 3). For more information see: " + "The field `\"signer_quorum\"` has the wrong value to be deleted (expected 0, found 3)" ); } @@ -276,7 +273,7 @@ mod test_signer_list_set_error { assert_eq!( signer_list_set.validate().unwrap_err().to_string().as_str(), - "The value of the field `signer_entries` has too few items in it (min 1, found 0). For more information see: " + "The value of the field `\"signer_entries\"` has too few items in it (min 1, found 0)" ); signer_list_set.signer_entries = Some(vec![ @@ -320,7 +317,7 @@ mod test_signer_list_set_error { assert_eq!( signer_list_set.validate().unwrap_err().to_string().as_str(), - "The value of the field `signer_entries` has too many items in it (max 8, found 9). For more information see: " + "The value of the field `\"signer_entries\"` has too many items in it (max 8, found 9)" ); signer_list_set.signer_entries = Some(vec![ @@ -340,7 +337,7 @@ mod test_signer_list_set_error { assert_eq!( signer_list_set.validate().unwrap_err().to_string().as_str(), - "The field `signer_entries` contains an invalid value (found rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb). For more information see: " + "The field `\"signer_entries\"` contains an invalid value (found \"rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb\")" ); signer_list_set.signer_entries = Some(vec![SignerEntry { @@ -351,7 +348,7 @@ mod test_signer_list_set_error { assert_eq!( signer_list_set.validate().unwrap_err().to_string().as_str(), - "The field `signer_quorum` must be below or equal to the sum of `signer_weight` in `signer_entries`. For more information see: " + "The field `signer_quorum` must be below or equal to the sum of `signer_weight` in `signer_entries`" ); signer_list_set.signer_entries = Some(vec![ @@ -368,7 +365,7 @@ mod test_signer_list_set_error { assert_eq!( signer_list_set.validate().unwrap_err().to_string().as_str(), - "The value of the field `signer_entries` has a duplicate in it (found rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW). For more information see: " + "The value of the field `\"signer_entries\"` has a duplicate in it (found \"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW\")" ); } } diff --git a/src/models/transactions/xchain_claim.rs b/src/models/transactions/xchain_claim.rs index 14cb695d..c91726d2 100644 --- a/src/models/transactions/xchain_claim.rs +++ b/src/models/transactions/xchain_claim.rs @@ -1,14 +1,10 @@ use alloc::{borrow::Cow, vec::Vec}; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::{ - models::{ - transactions::exceptions::XRPLXChainClaimException, Amount, Currency, FlagCollection, - Model, NoFlags, XChainBridge, - }, - Err, +use crate::models::{ + transactions::exceptions::XRPLXChainClaimException, Amount, Currency, FlagCollection, Model, + NoFlags, XChainBridge, XRPLModelResult, }; use super::{CommonFields, Memo, Signer, Transaction, TransactionType}; @@ -30,7 +26,7 @@ pub struct XChainClaim<'a> { } impl Model for XChainClaim<'_> { - fn get_errors(&self) -> Result<()> { + fn get_errors(&self) -> XRPLModelResult<()> { self.get_amount_mismatch_error() } } @@ -91,14 +87,14 @@ impl<'a> XChainClaim<'a> { } } - fn get_amount_mismatch_error(&self) -> Result<()> { + fn get_amount_mismatch_error(&self) -> XRPLModelResult<()> { let bridge = &self.xchain_bridge; match &self.amount { Amount::XRPAmount(amount) => { if Currency::from(amount) != bridge.locking_chain_issue && Currency::from(amount) != bridge.issuing_chain_issue { - Err!(XRPLXChainClaimException::AmountMismatch) + Err(XRPLXChainClaimException::AmountMismatch.into()) } else { Ok(()) } @@ -107,7 +103,7 @@ impl<'a> XChainClaim<'a> { if Currency::from(amount) != bridge.locking_chain_issue && Currency::from(amount) != bridge.issuing_chain_issue { - Err!(XRPLXChainClaimException::AmountMismatch) + Err(XRPLXChainClaimException::AmountMismatch.into()) } else { Ok(()) } diff --git a/src/models/transactions/xchain_create_bridge.rs b/src/models/transactions/xchain_create_bridge.rs index ae604b76..023d192a 100644 --- a/src/models/transactions/xchain_create_bridge.rs +++ b/src/models/transactions/xchain_create_bridge.rs @@ -1,14 +1,10 @@ use alloc::{borrow::Cow, vec::Vec}; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::{ - models::{ - transactions::exceptions::XRPLXChainCreateBridgeException, Amount, FlagCollection, Model, - NoFlags, XChainBridge, XRPAmount, XRP, - }, - Err, +use crate::models::{ + transactions::exceptions::XRPLXChainCreateBridgeException, Amount, FlagCollection, Model, + NoFlags, XChainBridge, XRPAmount, XRPLModelResult, XRP, }; use super::{CommonFields, Memo, Signer, Transaction, TransactionType}; @@ -26,7 +22,7 @@ pub struct XChainCreateBridge<'a> { } impl Model for XChainCreateBridge<'_> { - fn get_errors(&self) -> Result<()> { + fn get_errors(&self) -> XRPLModelResult<()> { self.get_same_door_error()?; self.get_account_door_mismatch_error()?; self.get_cross_currency_bridge_not_allowed_error()?; @@ -86,43 +82,43 @@ impl<'a> XChainCreateBridge<'a> { } } - fn get_same_door_error(&self) -> Result<()> { + fn get_same_door_error(&self) -> XRPLModelResult<()> { let bridge = &self.xchain_bridge; if bridge.issuing_chain_door == bridge.locking_chain_door { - Err!(XRPLXChainCreateBridgeException::SameDoorAccounts) + Err(XRPLXChainCreateBridgeException::SameDoorAccounts.into()) } else { Ok(()) } } - fn get_account_door_mismatch_error(&self) -> Result<()> { + fn get_account_door_mismatch_error(&self) -> XRPLModelResult<()> { let bridge = &self.xchain_bridge; if [&bridge.issuing_chain_door, &bridge.locking_chain_door] .contains(&&self.common_fields.account) { - Err!(XRPLXChainCreateBridgeException::AccountDoorMismatch) + Err(XRPLXChainCreateBridgeException::AccountDoorMismatch.into()) } else { Ok(()) } } - fn get_cross_currency_bridge_not_allowed_error(&self) -> Result<()> { + fn get_cross_currency_bridge_not_allowed_error(&self) -> XRPLModelResult<()> { let bridge = &self.xchain_bridge; if (bridge.locking_chain_issue == XRP::new().into()) != (bridge.issuing_chain_issue == XRP::new().into()) { - Err!(XRPLXChainCreateBridgeException::CrossCurrencyBridgeNotAllowed) + Err(XRPLXChainCreateBridgeException::CrossCurrencyBridgeNotAllowed.into()) } else { Ok(()) } } - fn get_min_account_create_amount_for_iou_error(&self) -> Result<()> { + fn get_min_account_create_amount_for_iou_error(&self) -> XRPLModelResult<()> { let bridge = &self.xchain_bridge; if self.min_account_create_amount.is_some() && bridge.locking_chain_issue != XRP::new().into() { - Err!(XRPLXChainCreateBridgeException::MinAccountCreateAmountForIOU) + Err(XRPLXChainCreateBridgeException::MinAccountCreateAmountForIOU.into()) } else { Ok(()) } diff --git a/src/models/transactions/xchain_create_claim_id.rs b/src/models/transactions/xchain_create_claim_id.rs index c82ba082..b75412ed 100644 --- a/src/models/transactions/xchain_create_claim_id.rs +++ b/src/models/transactions/xchain_create_claim_id.rs @@ -1,5 +1,4 @@ use alloc::{borrow::Cow, vec::Vec}; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -7,9 +6,8 @@ use crate::{ core::addresscodec::is_valid_classic_address, models::{ transactions::exceptions::XRPLXChainCreateClaimIDException, FlagCollection, Model, NoFlags, - XChainBridge, XRPAmount, + XChainBridge, XRPAmount, XRPLModelResult, }, - Err, }; use super::{CommonFields, Memo, Signer, Transaction, TransactionType}; @@ -27,7 +25,7 @@ pub struct XChainCreateClaimID<'a> { } impl Model for XChainCreateClaimID<'_> { - fn get_errors(&self) -> Result<()> { + fn get_errors(&self) -> XRPLModelResult<()> { self.get_other_chain_source_is_invalid_error() } } @@ -84,9 +82,9 @@ impl<'a> XChainCreateClaimID<'a> { } } - fn get_other_chain_source_is_invalid_error(&self) -> Result<()> { + fn get_other_chain_source_is_invalid_error(&self) -> XRPLModelResult<()> { if !is_valid_classic_address(self.other_chain_source.as_ref()) { - Err!(XRPLXChainCreateClaimIDException::OtherChainSourceIsInvalid) + Err(XRPLXChainCreateClaimIDException::OtherChainSourceIsInvalid.into()) } else { Ok(()) } diff --git a/src/models/transactions/xchain_modify_bridge.rs b/src/models/transactions/xchain_modify_bridge.rs index 022f2df3..007d2328 100644 --- a/src/models/transactions/xchain_modify_bridge.rs +++ b/src/models/transactions/xchain_modify_bridge.rs @@ -1,16 +1,12 @@ use alloc::{borrow::Cow, vec::Vec}; -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; -use crate::{ - models::{ - transactions::exceptions::XRPLXChainModifyBridgeException, Amount, FlagCollection, Model, - XChainBridge, XRPAmount, XRP, - }, - Err, +use crate::models::{ + transactions::exceptions::XRPLXChainModifyBridgeException, Amount, FlagCollection, Model, + XChainBridge, XRPAmount, XRPLModelResult, XRP, }; use super::{CommonFields, Memo, Signer, Transaction, TransactionType}; @@ -37,7 +33,7 @@ pub struct XChainModifyBridge<'a> { } impl Model for XChainModifyBridge<'_> { - fn get_errors(&self) -> Result<()> { + fn get_errors(&self) -> XRPLModelResult<()> { self.get_must_change_or_clear_error()?; self.get_account_door_mismatch_error()?; self.get_cannot_have_min_account_create_amount()?; @@ -98,34 +94,34 @@ impl<'a> XChainModifyBridge<'a> { } } - fn get_must_change_or_clear_error(&self) -> Result<()> { + fn get_must_change_or_clear_error(&self) -> XRPLModelResult<()> { if self.signature_reward.is_none() && self.min_account_create_amount.is_none() && !self.has_flag(&XChainModifyBridgeFlags::TfClearAccountCreateAmount) { - Err!(XRPLXChainModifyBridgeException::MustChangeOrClear) + Err(XRPLXChainModifyBridgeException::MustChangeOrClear.into()) } else { Ok(()) } } - fn get_account_door_mismatch_error(&self) -> Result<()> { + fn get_account_door_mismatch_error(&self) -> XRPLModelResult<()> { let bridge = &self.xchain_bridge; if [&bridge.locking_chain_door, &bridge.issuing_chain_door] .contains(&&self.get_common_fields().account) { - Err!(XRPLXChainModifyBridgeException::AccountDoorMismatch) + Err(XRPLXChainModifyBridgeException::AccountDoorMismatch.into()) } else { Ok(()) } } - fn get_cannot_have_min_account_create_amount(&self) -> Result<()> { + fn get_cannot_have_min_account_create_amount(&self) -> XRPLModelResult<()> { let bridge = &self.xchain_bridge; if self.min_account_create_amount.is_some() && bridge.locking_chain_issue != XRP::new().into() { - Err!(XRPLXChainModifyBridgeException::CannotHaveMinAccountCreateAmount) + Err(XRPLXChainModifyBridgeException::CannotHaveMinAccountCreateAmount.into()) } else { Ok(()) } diff --git a/src/transaction/exceptions.rs b/src/transaction/exceptions.rs index d0c0298a..ac3f4432 100644 --- a/src/transaction/exceptions.rs +++ b/src/transaction/exceptions.rs @@ -1,6 +1,7 @@ use thiserror_no_std::Error; #[derive(Debug, PartialEq, Error)] +#[non_exhaustive] pub enum XRPLMultisignException { #[error("No signers set in the transaction. Use `sign` function with `multisign = true`.")] NoSigners, diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs index c5afd560..c761d710 100644 --- a/src/transaction/mod.rs +++ b/src/transaction/mod.rs @@ -6,6 +6,7 @@ use core::fmt::Debug; use crate::{ asynch::{ clients::XRPLAsyncClient, + exceptions::XRPLHelperResult, transaction::{ autofill as async_autofill, autofill_and_sign as async_autofill_and_sign, calculate_fee_per_transaction_type as async_calculate_fee_per_transaction_type, @@ -20,7 +21,6 @@ use crate::{ }, wallet::Wallet, }; -use anyhow::Result; use embassy_futures::block_on; use serde::{de::DeserializeOwned, Serialize}; use strum::IntoEnumIterator; @@ -34,7 +34,7 @@ pub fn sign_and_submit<'a, 'b, T, F, C>( wallet: &Wallet, autofill: bool, check_fee: bool, -) -> Result> +) -> XRPLHelperResult> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, @@ -53,7 +53,7 @@ pub fn autofill<'a, 'b, F, T, C>( transaction: &mut T, client: &'b C, signers_count: Option, -) -> Result<()> +) -> XRPLHelperResult<()> where T: Transaction<'a, F> + Model + Clone, F: IntoEnumIterator + Serialize + Debug + PartialEq, @@ -67,7 +67,7 @@ pub fn autofill_and_sign<'a, 'b, T, F, C>( client: &'b C, wallet: &Wallet, check_fee: bool, -) -> Result<()> +) -> XRPLHelperResult<()> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, @@ -81,7 +81,7 @@ where )) } -pub fn submit<'a, T, F, C>(transaction: &T, client: &C) -> Result> +pub fn submit<'a, T, F, C>(transaction: &T, client: &C) -> XRPLHelperResult> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, @@ -96,7 +96,7 @@ pub fn submit_and_wait<'a: 'b, 'b, T, F, C>( wallet: Option<&Wallet>, check_fee: Option, autofill: Option, -) -> Result> +) -> XRPLHelperResult> where T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone + 'a, @@ -115,7 +115,7 @@ pub fn calculate_fee_per_transaction_type<'a, 'b, 'c, T, F, C>( transaction: &T, client: Option<&'b C>, signers_count: Option, -) -> Result> +) -> XRPLHelperResult> where T: Transaction<'a, F>, F: IntoEnumIterator + Serialize + Debug + PartialEq, diff --git a/src/transaction/multisign.rs b/src/transaction/multisign.rs index 437629a6..cfc4bc3d 100644 --- a/src/transaction/multisign.rs +++ b/src/transaction/multisign.rs @@ -1,16 +1,15 @@ use core::fmt::Debug; use alloc::vec::Vec; -use anyhow::Result; use serde::Serialize; use strum::IntoEnumIterator; use crate::{ - core::addresscodec::decode_classic_address, models::transactions::Transaction, - transaction::exceptions::XRPLMultisignException, Err, + asynch::exceptions::XRPLHelperResult, core::addresscodec::decode_classic_address, + models::transactions::Transaction, transaction::exceptions::XRPLMultisignException, }; -pub fn multisign<'a, T, F>(transaction: &mut T, tx_list: &'a Vec) -> Result<()> +pub fn multisign<'a, T, F>(transaction: &mut T, tx_list: &'a Vec) -> XRPLHelperResult<()> where F: IntoEnumIterator + Serialize + Debug + PartialEq + 'a, T: Transaction<'a, F>, @@ -19,11 +18,11 @@ where for tx in tx_list { let tx_signers = match tx.get_common_fields().signers.as_ref() { Some(signers) => signers, - None => return Err!(XRPLMultisignException::NoSigners), + None => return Err(XRPLMultisignException::NoSigners.into()), }; let tx_signer = match tx_signers.first() { Some(signer) => signer, - None => return Err!(XRPLMultisignException::NoSigners), + None => return Err(XRPLMultisignException::NoSigners.into()), }; decoded_tx_signers.push(tx_signer.clone()); } diff --git a/src/utils/exceptions.rs b/src/utils/exceptions.rs index cf0bb82d..ae3230f3 100644 --- a/src/utils/exceptions.rs +++ b/src/utils/exceptions.rs @@ -1,83 +1,80 @@ //! Exception for invalid XRP Ledger amount data. use alloc::string::String; -use strum_macros::Display; +use thiserror_no_std::Error; -#[derive(Debug, Clone, PartialEq, Display)] +use crate::XRPLSerdeJsonError; + +pub type XRPLUtilsResult = core::result::Result; + +#[derive(Debug, PartialEq, Error)] +#[non_exhaustive] +pub enum XRPLUtilsException { + #[error("XRPL Time Range error: {0}")] + XRPLTimeRangeError(#[from] XRPLTimeRangeException), + #[error("XRP Range error: {0}")] + XRPRangeError(#[from] XRPRangeException), + #[error("ISO Code error: {0}")] + ISOCodeError(#[from] ISOCodeException), + #[error("Decimal error: {0}")] + DecimalError(#[from] rust_decimal::Error), + #[error("BigDecimal error: {0}")] + BigDecimalError(#[from] bigdecimal::ParseBigDecimalError), + #[error("serde_json error: {0}")] + SerdeJsonError(#[from] XRPLSerdeJsonError), + #[error("Invalid Hex error: {0}")] + FromHexError(#[from] hex::FromHexError), +} + +#[derive(Debug, Clone, PartialEq, Error)] +#[non_exhaustive] pub enum XRPLTimeRangeException { + #[error("Invalid time before epoch (min: {min} found: {found})")] InvalidTimeBeforeEpoch { min: i64, found: i64 }, + #[error("Invalid time after epoch (max: {max} found: {found})")] UnexpectedTimeOverflow { max: i64, found: i64 }, + #[error("Invalid local time")] InvalidLocalTime, } -#[derive(Debug, Clone, PartialEq, Display)] +#[derive(Debug, Clone, PartialEq, Error)] #[non_exhaustive] pub enum XRPRangeException { + #[error("Invalid XRP amount")] InvalidXRPAmount, + #[error("Invalid Issued Currency amount")] InvalidICAmount, + #[error("Invalid value contains decimal")] InvalidValueContainsDecimal, + #[error("Invalid XRP amount too small (min: {min} found: {found})")] InvalidXRPAmountTooSmall { min: String, found: String }, + #[error("Invalid XRP amount too large (max: {max} found: {found})")] InvalidXRPAmountTooLarge { max: u64, found: String }, + #[error("Invalid Issued Currency precision too small (min: {min} found: {found})")] InvalidICPrecisionTooSmall { min: i32, found: i32 }, + #[error("Invalid Issued Currency precision too large (max: {max} found: {found})")] InvalidICPrecisionTooLarge { max: i32, found: i32 }, + #[error("Invalid Drops amount too large (max: {max} found: {found})")] InvalidDropsAmountTooLarge { max: String, found: String }, + #[error("Invalid Issued Currency serialization length (expected: {expected} found: {found})")] InvalidICSerializationLength { expected: usize, found: usize }, + #[error("Invalid Issued Currency amount overflow (max: {max} found: {found})")] UnexpectedICAmountOverflow { max: usize, found: usize }, - FromHexError, - DecimalError(rust_decimal::Error), - BigDecimalError(bigdecimal::ParseBigDecimalError), } -#[derive(Debug, Clone, PartialEq, Display)] +#[derive(Debug, Clone, PartialEq, Error)] #[non_exhaustive] pub enum ISOCodeException { + #[error("Invalid ISO code")] InvalidISOCode, + #[error("Invalid ISO length")] InvalidISOLength, + #[error("Invalid XRP bytes")] InvalidXRPBytes, - InvalidSerdeValue { - expected: String, - found: serde_json::Value, - }, + #[error("Invalid Currency representation")] UnsupportedCurrencyRepresentation, - FromHexError, + #[error("Invalid UTF-8")] Utf8Error, - DecimalError(rust_decimal::Error), -} - -#[derive(Debug, Clone, PartialEq, Display)] -#[non_exhaustive] -pub enum JSONParseException { - ISOCodeError(ISOCodeException), - DecimalError(rust_decimal::Error), - XRPRangeError(XRPRangeException), - InvalidSerdeValue { - expected: String, - found: serde_json::Value, - }, -} - -impl From for XRPRangeException { - fn from(err: rust_decimal::Error) -> Self { - XRPRangeException::DecimalError(err) - } -} - -impl From for XRPRangeException { - fn from(err: bigdecimal::ParseBigDecimalError) -> Self { - XRPRangeException::BigDecimalError(err) - } -} - -impl From for XRPRangeException { - fn from(_: hex::FromHexError) -> Self { - XRPRangeException::FromHexError - } -} - -impl From for ISOCodeException { - fn from(err: rust_decimal::Error) -> Self { - ISOCodeException::DecimalError(err) - } } impl From for ISOCodeException { @@ -86,27 +83,9 @@ impl From for ISOCodeException { } } -impl From for ISOCodeException { - fn from(_: hex::FromHexError) -> Self { - ISOCodeException::FromHexError - } -} - -impl From for JSONParseException { - fn from(err: XRPRangeException) -> Self { - JSONParseException::XRPRangeError(err) - } -} - -impl From for JSONParseException { - fn from(err: ISOCodeException) -> Self { - JSONParseException::ISOCodeError(err) - } -} - -impl From for JSONParseException { - fn from(err: rust_decimal::Error) -> Self { - JSONParseException::DecimalError(err) +impl From for XRPLUtilsException { + fn from(error: serde_json::Error) -> Self { + XRPLUtilsException::SerdeJsonError(error.into()) } } @@ -118,3 +97,6 @@ impl alloc::error::Error for XRPRangeException {} #[cfg(feature = "std")] impl alloc::error::Error for ISOCodeException {} + +#[cfg(feature = "std")] +impl alloc::error::Error for XRPLUtilsException {} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 953b6e98..2b87a8d6 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,7 +2,7 @@ pub mod exceptions; pub mod time_conversion; -#[cfg(feature = "transaction-models")] +#[cfg(feature = "models")] pub(crate) mod transactions; pub mod xrpl_conversion; diff --git a/src/utils/time_conversion.rs b/src/utils/time_conversion.rs index 62e92e04..6cf8d63d 100644 --- a/src/utils/time_conversion.rs +++ b/src/utils/time_conversion.rs @@ -6,18 +6,21 @@ use chrono::TimeZone; use chrono::Utc; use chrono::{DateTime, LocalResult}; +use super::exceptions::XRPLUtilsResult; + /// The "Ripple Epoch" of 2000-01-01T00:00:00 UTC pub const RIPPLE_EPOCH: i64 = 946684800; /// The maximum time that can be expressed on the XRPL pub const MAX_XRPL_TIME: i64 = i64::pow(2, 32); /// Ensures time does not exceed max representable on XRPL. -fn _ripple_check_max(time: i64, ok: T) -> Result { +fn _ripple_check_max(time: i64, ok: T) -> XRPLUtilsResult { if !(0..=MAX_XRPL_TIME).contains(&time) { Err(XRPLTimeRangeException::UnexpectedTimeOverflow { max: MAX_XRPL_TIME, found: time, - }) + } + .into()) } else { Ok(ok) } @@ -29,13 +32,11 @@ fn _ripple_check_max(time: i64, ok: T) -> Result { /// /// [`chrono::DateTime`]: mod@chrono::DateTime /// ``` -pub(crate) fn ripple_time_to_datetime( - ripple_time: i64, -) -> Result, XRPLTimeRangeException> { +pub(crate) fn ripple_time_to_datetime(ripple_time: i64) -> XRPLUtilsResult> { let datetime = Utc.timestamp_opt(ripple_time + RIPPLE_EPOCH, 0); match datetime { LocalResult::Single(dt) => _ripple_check_max(ripple_time, dt), - _ => Err(XRPLTimeRangeException::InvalidLocalTime), + _ => Err(XRPLTimeRangeException::InvalidLocalTime.into()), } } @@ -45,7 +46,7 @@ pub(crate) fn ripple_time_to_datetime( /// /// [`chrono::DateTime`]: mod@chrono::DateTime /// ``` -pub(crate) fn datetime_to_ripple_time(dt: DateTime) -> Result { +pub(crate) fn datetime_to_ripple_time(dt: DateTime) -> XRPLUtilsResult { let ripple_time = dt.timestamp() - RIPPLE_EPOCH; _ripple_check_max(ripple_time, ripple_time) } @@ -59,20 +60,20 @@ pub(crate) fn datetime_to_ripple_time(dt: DateTime) -> Result = match ripple_time_to_posix(946684801) { /// Ok(time) => Some(time), /// Err(e) => match e { -/// XRPLTimeRangeException::InvalidTimeBeforeEpoch { min: _, found: _} => None, -/// XRPLTimeRangeException::UnexpectedTimeOverflow { max: _, found: _ } => None, +/// XRPLUtilsException::XRPLTimeRangeError(XRPLTimeRangeException::InvalidTimeBeforeEpoch { min: _, found: _}) => None, +/// XRPLUtilsException::XRPLTimeRangeError(XRPLTimeRangeException::UnexpectedTimeOverflow { max: _, found: _ }) => None, /// _ => None, /// }, /// }; /// /// assert_eq!(Some(1893369601), posix); /// ``` -pub fn ripple_time_to_posix(ripple_time: i64) -> Result { +pub fn ripple_time_to_posix(ripple_time: i64) -> XRPLUtilsResult { _ripple_check_max(ripple_time, ripple_time + RIPPLE_EPOCH) } @@ -85,28 +86,26 @@ pub fn ripple_time_to_posix(ripple_time: i64) -> Result = match posix_to_ripple_time(946684801) { /// Ok(time) => Some(time), /// Err(e) => match e { -/// XRPLTimeRangeException::InvalidTimeBeforeEpoch { min: _, found: _} => None, -/// XRPLTimeRangeException::UnexpectedTimeOverflow { max: _, found: _ } => None, +/// XRPLUtilsException::XRPLTimeRangeError(XRPLTimeRangeException::InvalidTimeBeforeEpoch { min: _, found: _}) => None, +/// XRPLUtilsException::XRPLTimeRangeError(XRPLTimeRangeException::UnexpectedTimeOverflow { max: _, found: _ }) => None, /// _ => None, /// }, /// }; /// /// assert_eq!(Some(1), timestamp); /// ``` -pub fn posix_to_ripple_time(timestamp: i64) -> Result { +pub fn posix_to_ripple_time(timestamp: i64) -> XRPLUtilsResult { let ripple_time = timestamp - RIPPLE_EPOCH; _ripple_check_max(ripple_time, ripple_time) } #[cfg(test)] mod test { - use anyhow::{anyhow, Result}; - use super::*; #[test] @@ -119,7 +118,7 @@ mod test { fn test_datetime_to_ripple_time() { let actual = match Utc.timestamp_opt(RIPPLE_EPOCH, 0) { LocalResult::Single(dt) => datetime_to_ripple_time(dt), - _ => Err(XRPLTimeRangeException::InvalidLocalTime), + _ => Err(XRPLTimeRangeException::InvalidLocalTime.into()), }; assert_eq!(Ok(0_i64), actual); } @@ -147,10 +146,10 @@ mod test { } #[test] - fn accept_datetime_round_trip() -> Result<()> { + fn accept_datetime_round_trip() -> XRPLUtilsResult<()> { let current_time: DateTime = match Utc.timestamp_opt(Utc::now().timestamp(), 0) { LocalResult::Single(dt) => dt, - _ => return Err(anyhow!("Invalid local time")), + _ => return Err(XRPLTimeRangeException::InvalidLocalTime.into()), }; let ripple_time: i64 = datetime_to_ripple_time(current_time).unwrap(); let round_trip_time = ripple_time_to_datetime(ripple_time); @@ -161,10 +160,10 @@ mod test { } #[test] - fn accept_ripple_epoch() -> Result<()> { + fn accept_ripple_epoch() -> XRPLUtilsResult<()> { let expected = match Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0) { LocalResult::Single(dt) => dt, - _ => return Err(anyhow!("Invalid local time")), + _ => return Err(XRPLTimeRangeException::InvalidLocalTime.into()), }; assert_eq!(Ok(expected), ripple_time_to_datetime(0)); @@ -173,10 +172,10 @@ mod test { /// "Ripple Epoch" time starts in the year 2000 #[test] - fn accept_datetime_underflow() -> Result<()> { + fn accept_datetime_underflow() -> XRPLUtilsResult<()> { let datetime: DateTime = match Utc.with_ymd_and_hms(1999, 1, 1, 0, 0, 0) { LocalResult::Single(dt) => dt, - _ => return Err(anyhow!("Invalid local time")), + _ => return Err(XRPLTimeRangeException::InvalidLocalTime.into()), }; assert!(datetime_to_ripple_time(datetime).is_err()); @@ -185,10 +184,10 @@ mod test { /// "Ripple Epoch" time starts in the year 2000 #[test] - fn accept_posix_underflow() -> Result<()> { + fn accept_posix_underflow() -> XRPLUtilsResult<()> { let datetime: DateTime = match Utc.with_ymd_and_hms(1999, 1, 1, 0, 0, 0) { LocalResult::Single(dt) => dt, - _ => return Err(anyhow!("Invalid local time")), + _ => return Err(XRPLTimeRangeException::InvalidLocalTime.into()), }; assert!(posix_to_ripple_time(datetime.timestamp()).is_err()); @@ -201,10 +200,10 @@ mod test { /// starting 30 years after UNIX time's signed /// 32-bit int. #[test] - fn accept_datetime_overflow() -> Result<()> { + fn accept_datetime_overflow() -> XRPLUtilsResult<()> { let datetime: DateTime = match Utc.with_ymd_and_hms(2137, 1, 1, 0, 0, 0) { LocalResult::Single(dt) => dt, - _ => return Err(anyhow!("Invalid local time")), + _ => return Err(XRPLTimeRangeException::InvalidLocalTime.into()), }; assert!(datetime_to_ripple_time(datetime).is_err()); @@ -212,10 +211,10 @@ mod test { } #[test] - fn accept_posix_overflow() -> Result<()> { + fn accept_posix_overflow() -> XRPLUtilsResult<()> { let datetime: DateTime = match Utc.with_ymd_and_hms(2137, 1, 1, 0, 0, 0) { LocalResult::Single(dt) => dt, - _ => return Err(anyhow!("Invalid local time")), + _ => return Err(XRPLTimeRangeException::InvalidLocalTime.into()), }; assert!(posix_to_ripple_time(datetime.timestamp()).is_err()); diff --git a/src/utils/transactions.rs b/src/utils/transactions.rs index c932b913..489f9a27 100644 --- a/src/utils/transactions.rs +++ b/src/utils/transactions.rs @@ -1,39 +1,35 @@ use core::fmt::Debug; -use anyhow::Result; +use alloc::string::ToString; use serde::{de::DeserializeOwned, Serialize}; use strum::IntoEnumIterator; -use crate::{ - models::transactions::{exceptions::XRPLTransactionFieldException, Transaction}, - Err, -}; +use crate::{models::transactions::Transaction, XRPLSerdeJsonError}; -pub fn get_transaction_field_value<'a, F, T, R>(transaction: &T, field_name: &str) -> Result +use super::exceptions::XRPLUtilsResult; + +pub fn get_transaction_field_value<'a, F, T, R>( + transaction: &T, + field_name: &str, +) -> XRPLUtilsResult where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Serialize, R: DeserializeOwned, { - match serde_json::to_value(transaction) { - Ok(transaction_json) => match transaction_json.get(field_name) { - Some(common_field_value) => { - match serde_json::from_value::(common_field_value.clone()) { - Ok(val) => Ok(val), - Err(error) => Err!(error), - } - } - None => Err!(XRPLTransactionFieldException::FieldMissing(field_name)), - }, - Err(error) => Err!(error), - } + let txn_value = serde_json::to_value(transaction)?; + let common_field_value = txn_value + .get(field_name) + .ok_or(XRPLSerdeJsonError::InvalidNoneError(field_name.to_string()))?; + + Ok(serde_json::from_value::(common_field_value.clone())?) } pub fn set_transaction_field_value<'a, F, T, V>( transaction: &mut T, field_name: &str, field_value: V, -) -> Result<()> +) -> XRPLUtilsResult<()> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Serialize + DeserializeOwned, @@ -41,43 +37,30 @@ where { match serde_json::to_value(&mut *transaction) { Ok(mut transaction_json) => { - transaction_json[field_name] = match serde_json::to_value(field_value) { - Ok(json_value) => json_value, - Err(error) => return Err!(error), - }; + transaction_json[field_name] = serde_json::to_value(field_value)?; match serde_json::from_value::(transaction_json) { Ok(val) => { *transaction = val; Ok(()) } - Err(error) => Err!(error), + Err(error) => Err(error.into()), } } - Err(error) => Err!(error), + Err(error) => Err(error.into()), } } -pub fn validate_transaction_has_field<'a, T, F>(transaction: &T, field_name: &str) -> Result<()> +pub fn validate_transaction_has_field<'a, T, F>( + transaction: &T, + field_name: &str, +) -> XRPLUtilsResult<()> where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Serialize, { - match serde_json::to_value(transaction) { - Ok(transaction_json) => match transaction_json.get(field_name) { - Some(_) => Ok(()), - None => Err!(XRPLTransactionFieldException::FieldMissing(field_name)), - }, - Err(error) => Err!(error), - } -} + serde_json::to_value(transaction)? + .get(field_name) + .ok_or(XRPLSerdeJsonError::InvalidNoneError(field_name.to_string()))?; -pub fn validate_common_fied(common_field_name: &str) -> Result<()> { - match common_field_name { - "Account" | "TransactionType" | "Fee" | "Sequence" | "AccountTxnID" | "Flags" - | "LastLedgerSequence" | "Memos" | "NetworkID" | "Signers" | "SourceTag" - | "SigningPubKey" | "TicketSequence" | "TxnSignature" => Ok(()), - _ => Err!(XRPLTransactionFieldException::InvalidCommonField( - common_field_name - )), - } + Ok(()) } diff --git a/src/utils/xrpl_conversion.rs b/src/utils/xrpl_conversion.rs index 33598f24..f34788d0 100644 --- a/src/utils/xrpl_conversion.rs +++ b/src/utils/xrpl_conversion.rs @@ -9,6 +9,8 @@ use regex::Regex; use rust_decimal::prelude::*; use rust_decimal::Decimal; +use super::exceptions::XRPLUtilsResult; + /// Indivisible unit of XRP pub(crate) const _ONE_DROP: Decimal = Decimal::from_parts(1, 0, 0, false, 6); @@ -72,7 +74,7 @@ fn checked_mul(first: &BigDecimal, second: &BigDecimal) -> Option { /// TODO Make less bootleg /// Get the precision of a number. -fn _calculate_precision(value: &str) -> Result { +fn _calculate_precision(value: &str) -> XRPLUtilsResult { let decimal = BigDecimal::from_str(value)?.normalized(); let regex = Regex::new("[^1-9]").expect("_calculate_precision"); @@ -92,7 +94,7 @@ fn _calculate_precision(value: &str) -> Result { /// Ensure that the value after being multiplied by the /// exponent does not contain a decimal. -fn _verify_no_decimal(decimal: BigDecimal) -> Result<(), XRPRangeException> { +fn _verify_no_decimal(decimal: BigDecimal) -> XRPLUtilsResult<()> { let (mantissa, scale) = decimal.as_bigint_and_exponent(); let decimal = BigDecimal::from_i64(scale).expect("_verify_no_decimal"); @@ -107,7 +109,7 @@ fn _verify_no_decimal(decimal: BigDecimal) -> Result<(), XRPRangeException> { }; if value.contains('.') { - Err(XRPRangeException::InvalidValueContainsDecimal) + Err(XRPRangeException::InvalidValueContainsDecimal.into()) } else { Ok(()) } @@ -122,7 +124,7 @@ fn _verify_no_decimal(decimal: BigDecimal) -> Result<(), XRPRangeException> { /// /// ``` /// use xrpl::utils::xrp_to_drops; -/// use xrpl::utils::exceptions::XRPRangeException; +/// use xrpl::utils::exceptions::{XRPRangeException, XRPLUtilsException}; /// /// let xrp: &str = "100.000001"; /// let drops: String = "100000001".to_string(); @@ -130,27 +132,29 @@ fn _verify_no_decimal(decimal: BigDecimal) -> Result<(), XRPRangeException> { /// let conversion: Option = match xrp_to_drops(xrp) { /// Ok(xrp) => Some(xrp), /// Err(e) => match e { -/// XRPRangeException::InvalidXRPAmountTooLarge { max: _, found: _ } => None, -/// XRPRangeException::InvalidXRPAmountTooSmall { min: _, found: _ } => None, +/// XRPLUtilsException::XRPRangeError(XRPRangeException::InvalidXRPAmountTooLarge { max: _, found: _ }) => None, +/// XRPLUtilsException::XRPRangeError(XRPRangeException::InvalidXRPAmountTooSmall { min: _, found: _ }) => None, /// _ => None, /// }, /// }; /// /// assert_eq!(Some(drops), conversion); /// ``` -pub fn xrp_to_drops(xrp: &str) -> Result { +pub fn xrp_to_drops(xrp: &str) -> XRPLUtilsResult { let xrp_d = Decimal::from_str(xrp)?; if xrp_d < _ONE_DROP && xrp_d != Decimal::ZERO { Err(XRPRangeException::InvalidXRPAmountTooSmall { min: ONE_DROP.to_string(), found: xrp.to_string(), - }) + } + .into()) } else if xrp_d.gt(&Decimal::new(MAX_XRP as i64, 0)) { Err(XRPRangeException::InvalidXRPAmountTooLarge { max: MAX_XRP, found: xrp.into(), - }) + } + .into()) } else { Ok(format!("{}", (xrp_d / _ONE_DROP).trunc())) } @@ -165,7 +169,7 @@ pub fn xrp_to_drops(xrp: &str) -> Result { /// /// ``` /// use xrpl::utils::drops_to_xrp; -/// use xrpl::utils::exceptions::XRPRangeException; +/// use xrpl::utils::exceptions::{XRPRangeException, XRPLUtilsException}; /// /// let drops: &str = "100000000"; /// let xrp: String = "100".to_string(); @@ -173,14 +177,14 @@ pub fn xrp_to_drops(xrp: &str) -> Result { /// let conversion: Option = match drops_to_xrp(drops) { /// Ok(xrp) => Some(xrp), /// Err(e) => match e { -/// XRPRangeException::InvalidDropsAmountTooLarge { max: _, found: _ } => None, +/// XRPLUtilsException::XRPRangeError(XRPRangeException::InvalidDropsAmountTooLarge { max: _, found: _ }) => None, /// _ => None, /// }, /// }; /// /// assert_eq!(Some(xrp), conversion); /// ``` -pub fn drops_to_xrp(drops: &str) -> Result { +pub fn drops_to_xrp(drops: &str) -> XRPLUtilsResult { let drops_d = Decimal::from_str(drops)?; let xrp = drops_d * _ONE_DROP; @@ -188,7 +192,8 @@ pub fn drops_to_xrp(drops: &str) -> Result { Err(XRPRangeException::InvalidDropsAmountTooLarge { max: MAX_XRP.to_string(), found: drops.to_string(), - }) + } + .into()) } else { Ok(xrp.normalize().to_string()) } @@ -202,20 +207,20 @@ pub fn drops_to_xrp(drops: &str) -> Result { /// /// ``` /// use xrpl::utils::verify_valid_xrp_value; -/// use xrpl::utils::exceptions::XRPRangeException; +/// use xrpl::utils::exceptions::{XRPRangeException, XRPLUtilsException}; /// /// let valid: bool = match verify_valid_xrp_value("0.000001") { /// Ok(()) => true, /// Err(e) => match e { -/// XRPRangeException::InvalidXRPAmountTooSmall { min: _, found: _ } => false, -/// XRPRangeException::InvalidXRPAmountTooLarge { max: _, found: _ } => false, +/// XRPLUtilsException::XRPRangeError(XRPRangeException::InvalidXRPAmountTooSmall { min: _, found: _ }) => false, +/// XRPLUtilsException::XRPRangeError(XRPRangeException::InvalidXRPAmountTooLarge { max: _, found: _ }) => false, /// _ => false, /// }, /// }; /// /// assert!(valid); /// ``` -pub fn verify_valid_xrp_value(xrp_value: &str) -> Result<(), XRPRangeException> { +pub fn verify_valid_xrp_value(xrp_value: &str) -> XRPLUtilsResult<()> { let decimal = Decimal::from_str(xrp_value)?; let max = Decimal::new(MAX_DROPS as i64, 0); @@ -225,13 +230,15 @@ pub fn verify_valid_xrp_value(xrp_value: &str) -> Result<(), XRPRangeException> xrp if xrp.lt(&_ONE_DROP) => Err(XRPRangeException::InvalidXRPAmountTooSmall { min: ONE_DROP.to_string(), found: xrp.to_string(), - }), + } + .into()), xrp if xrp.gt(&max) => Err(XRPRangeException::InvalidDropsAmountTooLarge { max: MAX_XRP.to_string(), found: xrp.to_string(), - }), + } + .into()), // Should never occur - _ => Err(XRPRangeException::InvalidXRPAmount), + _ => Err(XRPRangeException::InvalidXRPAmount.into()), } } @@ -243,20 +250,20 @@ pub fn verify_valid_xrp_value(xrp_value: &str) -> Result<(), XRPRangeException> /// /// ``` /// use xrpl::utils::verify_valid_ic_value; -/// use xrpl::utils::exceptions::XRPRangeException; +/// use xrpl::utils::exceptions::{XRPRangeException, XRPLUtilsException}; /// /// let valid: bool = match verify_valid_ic_value("1111111111111111.0") { /// Ok(()) => true, /// Err(e) => match e { -/// XRPRangeException::InvalidICPrecisionTooSmall { min: _, found: _ } => false, -/// XRPRangeException::InvalidICPrecisionTooLarge { max: _, found: _ } => false, +/// XRPLUtilsException::XRPRangeError(XRPRangeException::InvalidICPrecisionTooSmall { min: _, found: _ }) => false, +/// XRPLUtilsException::XRPRangeError(XRPRangeException::InvalidICPrecisionTooLarge { max: _, found: _ }) => false, /// _ => false, /// }, /// }; /// /// assert!(valid); /// ``` -pub fn verify_valid_ic_value(ic_value: &str) -> Result<(), XRPRangeException> { +pub fn verify_valid_ic_value(ic_value: &str) -> XRPLUtilsResult<()> { let decimal = BigDecimal::from_str(ic_value)?.normalized(); let scale = -(decimal.fractional_digit_count() as i32); let prec = _calculate_precision(ic_value)?; @@ -267,13 +274,15 @@ pub fn verify_valid_ic_value(ic_value: &str) -> Result<(), XRPRangeException> { Err(XRPRangeException::InvalidICPrecisionTooLarge { max: MAX_IOU_EXPONENT, found: scale, - }) + } + .into()) } _ if prec > MAX_IOU_PRECISION as usize || scale < MIN_IOU_EXPONENT => { Err(XRPRangeException::InvalidICPrecisionTooSmall { min: MIN_IOU_EXPONENT, found: scale, - }) + } + .into()) } _ => _verify_no_decimal(decimal), } diff --git a/src/wallet/exceptions.rs b/src/wallet/exceptions.rs new file mode 100644 index 00000000..d185cc81 --- /dev/null +++ b/src/wallet/exceptions.rs @@ -0,0 +1,12 @@ +use thiserror_no_std::Error; + +use crate::core::exceptions::XRPLCoreException; + +pub type XRPLWalletResult = core::result::Result; + +#[derive(Debug, PartialEq, Error)] +#[non_exhaustive] +pub enum XRPLWalletException { + #[error("XRPL Core error: {0}")] + XRPLCoreError(#[from] XRPLCoreException), +} diff --git a/src/wallet/faucet_generation.rs b/src/wallet/faucet_generation.rs index c35c50e8..dab79d9b 100644 --- a/src/wallet/faucet_generation.rs +++ b/src/wallet/faucet_generation.rs @@ -1,10 +1,10 @@ use super::Wallet; use crate::asynch::{ clients::{XRPLAsyncClient, XRPLFaucet}, + exceptions::XRPLHelperResult, wallet::generate_faucet_wallet as async_generate_faucet_wallet, }; use alloc::borrow::Cow; -use anyhow::Result; use embassy_futures::block_on; use url::Url; @@ -16,7 +16,7 @@ pub fn generate_faucet_wallet<'a, C>( faucet_host: Option, usage_context: Option>, user_agent: Option>, -) -> Result +) -> XRPLHelperResult where C: XRPLFaucet + XRPLAsyncClient, { diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 36fa2c36..08c67a8f 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -1,22 +1,19 @@ //! Methods for working with XRPL wallets. -#[cfg(feature = "wallet-helpers")] -mod faucet_generation; +pub mod exceptions; +#[cfg(feature = "helpers")] +pub mod faucet_generation; use crate::constants::CryptoAlgorithm; use crate::core::addresscodec::classic_address_to_xaddress; -use crate::core::addresscodec::exceptions::XRPLAddressCodecException; use crate::core::keypairs::derive_classic_address; use crate::core::keypairs::derive_keypair; -use crate::core::keypairs::exceptions::XRPLKeypairsException; use crate::core::keypairs::generate_seed; use alloc::string::String; use core::fmt::Display; +use exceptions::XRPLWalletResult; use zeroize::Zeroize; -#[cfg(feature = "wallet-helpers")] -pub use faucet_generation::*; - /// The cryptographic keys needed to control an /// XRP Ledger account. /// @@ -59,7 +56,7 @@ impl Drop for Wallet { impl Wallet { /// Generate a new Wallet. - pub fn new(seed: &str, sequence: u64) -> Result { + pub fn new(seed: &str, sequence: u64) -> XRPLWalletResult { let (public_key, private_key) = derive_keypair(seed, false)?; let classic_address = derive_classic_address(&public_key)?; @@ -73,9 +70,7 @@ impl Wallet { } /// Generates a new seed and Wallet. - pub fn create( - crypto_algorithm: Option, - ) -> Result { + pub fn create(crypto_algorithm: Option) -> XRPLWalletResult { Self::new(&generate_seed(None, crypto_algorithm)?, 0) } @@ -84,8 +79,12 @@ impl Wallet { &self, tag: Option, is_test_network: bool, - ) -> Result { - classic_address_to_xaddress(&self.classic_address, tag, is_test_network) + ) -> XRPLWalletResult { + Ok(classic_address_to_xaddress( + &self.classic_address, + tag, + is_test_network, + )?) } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 6c55fb26..2cdf1bc9 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -15,6 +15,8 @@ pub async fn open_websocket( ) -> Result< AsyncWebSocketClient<4096, FromTokio, OsRng, SingleExecutorMutex, WebSocketOpen>, > { + use anyhow::anyhow; + let port = uri.port().unwrap_or(80); let url = format!("{}:{}", uri.host_str().unwrap(), port); @@ -23,7 +25,7 @@ pub async fn open_websocket( let rng = OsRng; match AsyncWebSocketClient::open(stream, uri, rng, None, None).await { Ok(client) => Ok(client), - Err(e) => Err(e), + Err(e) => Err(anyhow!(e)), } } @@ -31,5 +33,5 @@ pub async fn open_websocket( pub async fn open_websocket( uri: Url, ) -> Result> { - AsyncWebSocketClient::open(uri).await + AsyncWebSocketClient::open(uri).await.map_err(Into::into) }