Skip to content

Commit

Permalink
feat: error handling (#149)
Browse files Browse the repository at this point in the history
* Added WorkspaceError{,Kind}

* Added workspace error kind for sandbox

* Replaced types errors w/ WorkspaceError

* Moved from ErrorKind to thiserror

* Added SerializationError type

* Added ExecutionError

* Added errors to network related items

* Added error handling to view

* Moved all client anyhow Error to workspace Error

* Rearrange Error to be at top level

* More serialization error for result.rs

* Last bits of result.rs errors

* Added sandbox specific error handling

* Sandbox patch

* Final bit of anyhow::Result removed

* Specific RpcError

* Cleanup

* Added ParseError specific kind

* Surface WorkspacError

* Small cleanup

* Added some docs

* Added more docs

* Moved errors to error module & added RpcError into client.rs

* Changed internal repr of RpcError

* Some more cleanup

* Cleanup Result path

* Fmt

* Clippy

* Fmt again

* Addressed most comments

* Fixed Sync/Send | remove AccountError | made ParseError(String)

* Fix io error from_file

* Moved Error to mostly opaque type

* Added downcasting and docs

* Added deferred error handling on args_{json, borsh}

* Fix test

* Export Result to workspaces root

* Remove exposed from impls

* Addressed comments

* Make less redundant usage of deferred error

* Added error message w/ timeout env var
  • Loading branch information
ChaoticTempest authored Aug 9, 2022
1 parent aed7aa5 commit 7de5010
Show file tree
Hide file tree
Showing 32 changed files with 619 additions and 300 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Then we'll go directly into making a call into the contract, and initialize the
.call(&worker, "new_default_meta")
.args_json(json!({
"owner_id": contract.id(),
}))?
}))
.transact()
.await?;

Expand All @@ -100,7 +100,7 @@ Afterwards, let's mint an NFT via `nft_mint`. This showcases some extra argument
"dscription": "Tallest mountain in charted solar system",
"copies": 1,
},
}))?
}))
.deposit(deposit)
// nft_mint might consume more than default gas, so supply our own gas value:
.gas(near_units::parse_gas("300 T"))
Expand Down Expand Up @@ -160,7 +160,7 @@ async fn call_my_func(worker: Worker<impl Network>, contract: &Contract) -> anyh
contract.call(&worker, "contract_function")
.args_json(serde_json::json!({
"message": msg,
})?
})
.transact()
.await?;
Ok(())
Expand Down Expand Up @@ -220,7 +220,7 @@ Following that we will have to init the contract again with our own metadata. Th
.args_json(serde_json::json!({
"arg1": value1,
"arg2": value2,
}))?
}))
.transact()
.await?;

Expand Down
12 changes: 6 additions & 6 deletions examples/src/croncat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ async fn main() -> anyhow::Result<()> {
"function_id": "increment",
"cadence": "*/1 * * * * *",
"recurring": true,
}))?
}))
.max_gas()
.deposit(parse_near!("1 N"))
.transact()
Expand Down Expand Up @@ -101,7 +101,7 @@ pub async fn run_scheduled_tasks(
// Register the agent to eventually execute the task
let outcome = agent
.call(&worker, contract.id(), "register_agent")
.args_json(json!({}))?
.args_json(json!({}))
.deposit(parse_near!("0.00226 N"))
.transact()
.await?;
Expand All @@ -110,7 +110,7 @@ pub async fn run_scheduled_tasks(
// Check the right agent was registered correctly:
let registered_agent = contract
.call(&worker, "get_agent")
.args_json(json!({ "account_id": agent.id() }))?
.args_json(json!({ "account_id": agent.id() }))
.view()
.await?
.json::<Option<Agent>>()?
Expand Down Expand Up @@ -148,7 +148,7 @@ pub async fn run_scheduled_tasks(
// the manager contract, and we want to eventually withdraw this amount.
let agent_details = contract
.call(&worker, "get_agent")
.args_json(json!({"account_id": agent.id()}))?
.args_json(json!({"account_id": agent.id()}))
.view()
.await?
.json::<Option<Agent>>()?
Expand All @@ -167,7 +167,7 @@ pub async fn run_scheduled_tasks(
// contract:
let agent_details = contract
.call(&worker, "get_agent")
.args_json(json!({"account_id": agent.id() }))?
.args_json(json!({"account_id": agent.id() }))
.view()
.await?
.json::<Option<Agent>>()?
Expand All @@ -191,7 +191,7 @@ pub async fn run_scheduled_tasks(
// Check to see if the agent has been successfully unregistered
let removed_agent: Option<Agent> = contract
.call(&worker, "get_agent")
.args_json(json!({"account_id": agent.id() }))?
.args_json(json!({"account_id": agent.id() }))
.view()
.await?
.json()?;
Expand Down
4 changes: 2 additions & 2 deletions examples/src/nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ async fn main() -> anyhow::Result<()> {
.call(&worker, "new_default_meta")
.args_json(json!({
"owner_id": contract.id(),
}))?
}))
.transact()
.await?;

Expand All @@ -31,7 +31,7 @@ async fn main() -> anyhow::Result<()> {
"dscription": "Tallest mountain in charted solar system",
"copies": 1,
},
}))?
}))
.deposit(deposit)
.transact()
.await?;
Expand Down
24 changes: 12 additions & 12 deletions examples/src/ref_finance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ async fn create_ref(owner: &Account, worker: &Worker<Sandbox>) -> anyhow::Result
"owner_id": ref_finance.id(),
"exchange_fee": 4,
"referral_fee": 1,
}))?
}))
.transact()
.await?;

owner
.call(&worker, ref_finance.id(), "storage_deposit")
.args_json(serde_json::json!({}))?
.args_json(serde_json::json!({}))
.deposit(parse_near!("30 mN"))
.transact()
.await?;
Expand All @@ -69,13 +69,13 @@ async fn create_wnear(owner: &Account, worker: &Worker<Sandbox>) -> anyhow::Resu
.args_json(serde_json::json!({
"owner_id": owner.id(),
"total_supply": parse_near!("1,000,000,000 N"),
}))?
}))
.transact()
.await?;

owner
.call(&worker, wnear.id(), "storage_deposit")
.args_json(serde_json::json!({}))?
.args_json(serde_json::json!({}))
.deposit(parse_near!("0.008 N"))
.transact()
.await?;
Expand Down Expand Up @@ -105,7 +105,7 @@ async fn create_pool_with_liquidity(

ref_finance
.call(worker, "extend_whitelisted_tokens")
.args_json(serde_json::json!({ "tokens": token_ids }))?
.args_json(serde_json::json!({ "tokens": token_ids }))
.transact()
.await?;

Expand All @@ -114,7 +114,7 @@ async fn create_pool_with_liquidity(
.args_json(serde_json::json!({
"tokens": token_ids,
"fee": 25
}))?
}))
.deposit(parse_near!("3 mN"))
.transact()
.await?
Expand All @@ -124,7 +124,7 @@ async fn create_pool_with_liquidity(
.call(&worker, ref_finance.id(), "register_tokens")
.args_json(serde_json::json!({
"token_ids": token_ids,
}))?
}))
.deposit(1)
.transact()
.await?;
Expand All @@ -136,7 +136,7 @@ async fn create_pool_with_liquidity(
.args_json(serde_json::json!({
"pool_id": pool_id,
"amounts": token_amounts,
}))?
}))
.deposit(parse_near!("1 N"))
.transact()
.await?;
Expand All @@ -157,7 +157,7 @@ async fn deposit_tokens(
.call(&worker, contract_id, "storage_deposit")
.args_json(serde_json::json!({
"registration_only": true,
}))?
}))
.deposit(parse_near!("1 N"))
.transact()
.await?;
Expand All @@ -168,7 +168,7 @@ async fn deposit_tokens(
"receiver_id": ref_finance.id(),
"amount": amount.to_string(),
"msg": "",
}))?
}))
.gas(parse_gas!("200 Tgas") as u64)
.deposit(1)
.transact()
Expand All @@ -193,7 +193,7 @@ async fn create_custom_ft(
.args_json(serde_json::json!({
"owner_id": owner.id(),
"total_supply": parse_near!("1,000,000,000 N").to_string(),
}))?
}))
.transact()
.await?;

Expand Down Expand Up @@ -318,7 +318,7 @@ async fn main() -> anyhow::Result<()> {
"amount_in": parse_near!("1 N").to_string(),
"min_amount_out": "1",
})],
}))?
}))
.deposit(1)
.gas(parse_gas!("100 Tgas") as u64)
.transact()
Expand Down
2 changes: 1 addition & 1 deletion examples/src/spooning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async fn deploy_status_contract(
.call(worker, "set_status")
.args_json(serde_json::json!({
"message": msg,
}))?
}))
.transact()
.await?;

Expand Down
2 changes: 1 addition & 1 deletion examples/src/status_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async fn main() -> anyhow::Result<()> {
.call(&worker, "set_status")
.args_json(json!({
"message": "hello_world",
}))?
}))
.transact()
.await?;
println!("set_status: {:?}", outcome);
Expand Down
1 change: 1 addition & 0 deletions workspaces/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ rand = "0.8.4"
reqwest = { version = "0.11", features = ["json"] }
serde = "1.0"
serde_json = "1.0"
thiserror = "1.0"
tokio = { version = "1", features = ["full"] }
tokio-retry = "0.3"
tracing = "0.1"
Expand Down
141 changes: 141 additions & 0 deletions workspaces/src/error/impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use std::borrow::Cow;
use std::fmt;

use super::{Error, ErrorKind, ErrorRepr, RpcErrorCode, SandboxErrorCode};

impl ErrorKind {
pub(crate) fn custom<E>(self, error: E) -> Error
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Error::custom(self, error)
}

pub(crate) fn message<T>(self, msg: T) -> Error
where
T: Into<Cow<'static, str>>,
{
Error::message(self, msg)
}
}

impl Error {
pub(crate) fn full<T, E>(kind: ErrorKind, msg: T, error: E) -> Self
where
T: Into<Cow<'static, str>>,
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Self {
repr: ErrorRepr::Full {
kind,
message: msg.into(),
error: error.into(),
},
}
}

pub(crate) fn custom<E>(kind: ErrorKind, error: E) -> Self
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Self {
repr: ErrorRepr::Custom {
kind,
error: error.into(),
},
}
}

pub(crate) fn message<T>(kind: ErrorKind, msg: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Self {
repr: ErrorRepr::Message {
kind,
message: msg.into(),
},
}
}

pub(crate) fn simple(kind: ErrorKind) -> Self {
Self {
repr: ErrorRepr::Simple(kind),
}
}

/// Returns the corresponding [`ErrorKind`] for this error.
pub fn kind(&self) -> &ErrorKind {
match &self.repr {
ErrorRepr::Simple(kind) => kind,
ErrorRepr::Message { kind, .. } => kind,
ErrorRepr::Custom { kind, .. } => kind,
ErrorRepr::Full { kind, .. } => kind,
}
}

/// Consumes the `Error`, returning its inner error (if any).
///
/// If this [`Error`] was constructed via [`Error::custom`] or [`Error::full`]
/// then this function will return [`Ok`], otherwise it will return [`Err`].
pub fn into_inner(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self> {
match self.repr {
ErrorRepr::Custom { error, .. } => Ok(error),
ErrorRepr::Full { error, .. } => Ok(error),
_ => Err(self),
}
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.repr)
}
}

impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.repr {
ErrorRepr::Custom { error, .. } => error.source(),
ErrorRepr::Full { error, .. } => error.source(),
_ => None,
}
}
}

impl SandboxErrorCode {
pub(crate) fn custom<E>(self, error: E) -> Error
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Error::custom(ErrorKind::Sandbox(self), error)
}
}

impl From<SandboxErrorCode> for Error {
fn from(code: SandboxErrorCode) -> Self {
Error::simple(ErrorKind::Sandbox(code))
}
}

impl RpcErrorCode {
pub(crate) fn message<T>(self, msg: T) -> Error
where
T: Into<Cow<'static, str>>,
{
Error::message(ErrorKind::Rpc(self), msg)
}

pub(crate) fn custom<E>(self, error: E) -> Error
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Error::custom(ErrorKind::Rpc(self), error)
}
}

impl From<RpcErrorCode> for Error {
fn from(code: RpcErrorCode) -> Self {
Error::simple(ErrorKind::Rpc(code))
}
}
Loading

0 comments on commit 7de5010

Please sign in to comment.