Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Wasm error handling #344

Merged
merged 3 commits into from
Aug 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bindings/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ console_error_panic_hook = { version = "0.1" }
futures = { version = "0.3" }
js-sys = { version = "0.3" }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", default-features = false }
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
wasm-bindgen-futures = { version = "0.4", default-features = false }

Expand Down
24 changes: 12 additions & 12 deletions bindings/wasm/src/credential/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use identity::credential::CredentialBuilder;
use identity::credential::Subject;
use wasm_bindgen::prelude::*;

use crate::utils::err;
use crate::error::wasm_error;
use crate::wasm_document::WasmDocument;

#[wasm_bindgen(inspectable)]
Expand All @@ -22,7 +22,7 @@ pub struct VerifiableCredential(pub(crate) Credential);
impl VerifiableCredential {
#[wasm_bindgen]
pub fn extend(value: &JsValue) -> Result<VerifiableCredential, JsValue> {
let mut base: Object = value.into_serde().map_err(err)?;
let mut base: Object = value.into_serde().map_err(wasm_error)?;

if !base.contains_key("credentialSubject") {
return Err("Missing property: `credentialSubject`".into());
Expand All @@ -35,23 +35,23 @@ impl VerifiableCredential {
if !base.contains_key("@context") {
base.insert(
"@context".into(),
Credential::<()>::base_context().serde_into().map_err(err)?,
Credential::<()>::base_context().serde_into().map_err(wasm_error)?,
);
}

let mut types: Vec<String> = match base.remove("type") {
Some(value) => value.serde_into().map(OneOrMany::into_vec).map_err(err)?,
Some(value) => value.serde_into().map(OneOrMany::into_vec).map_err(wasm_error)?,
None => Vec::new(),
};

types.insert(0, Credential::<()>::base_type().into());
base.insert("type".into(), types.serde_into().map_err(err)?);
base.insert("type".into(), types.serde_into().map_err(wasm_error)?);

if !base.contains_key("issuanceDate") {
base.insert("issuanceDate".into(), Timestamp::now_utc().to_string().into());
}

base.serde_into().map_err(err).map(Self)
base.serde_into().map_err(wasm_error).map(Self)
}

#[wasm_bindgen]
Expand All @@ -61,8 +61,8 @@ impl VerifiableCredential {
credential_type: Option<String>,
credential_id: Option<String>,
) -> Result<VerifiableCredential, JsValue> {
let subjects: OneOrMany<Subject> = subject_data.into_serde().map_err(err)?;
let issuer_url: Url = Url::parse(issuer_doc.0.id().as_str()).map_err(err)?;
let subjects: OneOrMany<Subject> = subject_data.into_serde().map_err(wasm_error)?;
let issuer_url: Url = Url::parse(issuer_doc.0.id().as_str()).map_err(wasm_error)?;
let mut builder: CredentialBuilder = CredentialBuilder::default().issuer(issuer_url);

for subject in subjects.into_vec() {
Expand All @@ -74,21 +74,21 @@ impl VerifiableCredential {
}

if let Some(credential_id) = credential_id {
builder = builder.id(Url::parse(credential_id).map_err(err)?);
builder = builder.id(Url::parse(credential_id).map_err(wasm_error)?);
}

builder.build().map(Self).map_err(err)
builder.build().map(Self).map_err(wasm_error)
}

/// Serializes a `VerifiableCredential` object as a JSON object.
#[wasm_bindgen(js_name = toJSON)]
pub fn to_json(&self) -> Result<JsValue, JsValue> {
JsValue::from_serde(&self.0).map_err(err)
JsValue::from_serde(&self.0).map_err(wasm_error)
}

/// Deserializes a `VerifiableCredential` object from a JSON object.
#[wasm_bindgen(js_name = fromJSON)]
pub fn from_json(json: &JsValue) -> Result<VerifiableCredential, JsValue> {
json.into_serde().map_err(err).map(Self)
json.into_serde().map_err(wasm_error).map(Self)
}
}
14 changes: 7 additions & 7 deletions bindings/wasm/src/credential/presentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use identity::credential::Presentation;
use identity::credential::PresentationBuilder;
use wasm_bindgen::prelude::*;

use crate::utils::err;
use crate::error::wasm_error;
use crate::wasm_document::WasmDocument;

#[wasm_bindgen(inspectable)]
Expand All @@ -24,8 +24,8 @@ impl VerifiablePresentation {
presentation_type: Option<String>,
presentation_id: Option<String>,
) -> Result<VerifiablePresentation, JsValue> {
let credentials: OneOrMany<Credential> = credential_data.into_serde().map_err(err)?;
let holder_url: Url = Url::parse(holder_doc.0.id().as_str()).map_err(err)?;
let credentials: OneOrMany<Credential> = credential_data.into_serde().map_err(wasm_error)?;
let holder_url: Url = Url::parse(holder_doc.0.id().as_str()).map_err(wasm_error)?;

let mut builder: PresentationBuilder = PresentationBuilder::default().holder(holder_url);

Expand All @@ -38,21 +38,21 @@ impl VerifiablePresentation {
}

if let Some(presentation_id) = presentation_id {
builder = builder.id(Url::parse(presentation_id).map_err(err)?);
builder = builder.id(Url::parse(presentation_id).map_err(wasm_error)?);
}

builder.build().map_err(err).map(Self)
builder.build().map_err(wasm_error).map(Self)
}

/// Serializes a `VerifiablePresentation` object as a JSON object.
#[wasm_bindgen(js_name = toJSON)]
pub fn to_json(&self) -> Result<JsValue, JsValue> {
JsValue::from_serde(&self.0).map_err(err)
JsValue::from_serde(&self.0).map_err(wasm_error)
}

/// Deserializes a `VerifiablePresentation` object from a JSON object.
#[wasm_bindgen(js_name = fromJSON)]
pub fn from_json(json: &JsValue) -> Result<VerifiablePresentation, JsValue> {
json.into_serde().map_err(err).map(Self)
json.into_serde().map_err(wasm_error).map(Self)
}
}
10 changes: 5 additions & 5 deletions bindings/wasm/src/crypto/key_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use wasm_bindgen::prelude::*;
use crate::crypto::Digest;
use crate::crypto::KeyPair;
use crate::crypto::KeyType;
use crate::utils::err;
use crate::error::wasm_error;

#[derive(Deserialize, Serialize)]
struct JsonData {
Expand All @@ -41,7 +41,7 @@ impl KeyCollection {
/// Creates a new `KeyCollection` with the specified key type.
#[wasm_bindgen(constructor)]
pub fn new(type_: KeyType, count: usize) -> Result<KeyCollection, JsValue> {
KeyCollection_::new(type_.into(), count).map_err(err).map(Self)
KeyCollection_::new(type_.into(), count).map_err(wasm_error).map(Self)
}

/// Returns the number of keys in the collection.
Expand Down Expand Up @@ -123,13 +123,13 @@ impl KeyCollection {
type_: self.0.type_().into(),
};

JsValue::from_serde(&data).map_err(err)
JsValue::from_serde(&data).map_err(wasm_error)
}

/// Deserializes a `KeyCollection` object from a JSON object.
#[wasm_bindgen(js_name = fromJSON)]
pub fn from_json(json: &JsValue) -> Result<KeyCollection, JsValue> {
let data: JsonData = json.into_serde().map_err(err)?;
let data: JsonData = json.into_serde().map_err(wasm_error)?;

let iter: _ = data.keys.iter().flat_map(|data| {
let pk: PublicKey = decode_b58(&data.public).ok()?.into();
Expand All @@ -139,7 +139,7 @@ impl KeyCollection {
});

KeyCollection_::from_iterator(data.type_.into(), iter)
.map_err(err)
.map_err(wasm_error)
.map(Self)
}
}
12 changes: 6 additions & 6 deletions bindings/wasm/src/crypto/key_pair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use identity::crypto::SecretKey;
use wasm_bindgen::prelude::*;

use crate::crypto::KeyType;
use crate::utils::err;
use crate::error::wasm_error;

#[derive(Deserialize, Serialize)]
struct JsonData {
Expand All @@ -31,14 +31,14 @@ impl KeyPair {
/// Generates a new `KeyPair` object.
#[wasm_bindgen(constructor)]
pub fn new(type_: KeyType) -> Result<KeyPair, JsValue> {
KeyPair_::new(type_.into()).map_err(err).map(Self)
KeyPair_::new(type_.into()).map_err(wasm_error).map(Self)
}

/// Parses a `KeyPair` object from base58-encoded public/secret keys.
#[wasm_bindgen(js_name = fromBase58)]
pub fn from_base58(type_: KeyType, public_key: &str, secret_key: &str) -> Result<KeyPair, JsValue> {
let public: PublicKey = decode_b58(public_key).map_err(err)?.into();
let secret: SecretKey = decode_b58(secret_key).map_err(err)?.into();
let public: PublicKey = decode_b58(public_key).map_err(wasm_error)?.into();
let secret: SecretKey = decode_b58(secret_key).map_err(wasm_error)?.into();

Ok(Self((type_.into(), public, secret).into()))
}
Expand All @@ -64,13 +64,13 @@ impl KeyPair {
secret: self.secret(),
};

JsValue::from_serde(&data).map_err(err)
JsValue::from_serde(&data).map_err(wasm_error)
}

/// Deserializes a `KeyPair` object from a JSON object.
#[wasm_bindgen(js_name = fromJSON)]
pub fn from_json(json: &JsValue) -> Result<KeyPair, JsValue> {
let data: JsonData = json.into_serde().map_err(err)?;
let data: JsonData = json.into_serde().map_err(wasm_error)?;

Self::from_base58(data.type_, &data.public, &data.secret)
}
Expand Down
91 changes: 91 additions & 0 deletions bindings/wasm/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::borrow::Cow;

use wasm_bindgen::JsValue;

/// Convert an error into an idiomatic [js_sys::Error].
pub fn wasm_error<'a, T>(error: T) -> JsValue
where
T: Into<WasmError<'a>>,
{
let wasm_err: WasmError = error.into();
JsValue::from(wasm_err)
}

/// Convenience struct to convert internal errors to [js_sys::Error]. Uses [std::borrow::Cow]
/// internally to avoid unnecessary clones.
///
/// This is a workaround for orphan rules so we can implement [core::convert::From] on errors from
/// dependencies.
#[derive(Debug, Clone)]
pub struct WasmError<'a> {
pub name: Cow<'a, str>,
pub message: Cow<'a, str>,
}

impl<'a> WasmError<'a> {
pub fn new(name: Cow<'a, str>, message: Cow<'a, str>) -> Self {
Self { name, message }
}
}

/// Convert [WasmError] into [js_sys::Error] for idiomatic error handling.
impl From<WasmError<'_>> for js_sys::Error {
fn from(error: WasmError<'_>) -> Self {
let js_error = js_sys::Error::new(&error.message);
js_error.set_name(&error.name);
js_error
}
}

/// Convert [WasmError] into [wasm_bindgen::JsValue].
impl From<WasmError<'_>> for JsValue {
fn from(error: WasmError<'_>) -> Self {
JsValue::from(js_sys::Error::from(error))
}
}

/// Implement WasmError for each type individually rather than a trait due to Rust's orphan rules.
/// Each type must implement `Into<&'static str> + Display`. The `Into<&'static str>` trait can be
/// derived using `strum::IntoStaticStr`.
#[macro_export]
macro_rules! impl_wasm_error_from {
( $($t:ty),* ) => {
$(impl From<$t> for WasmError<'_> {
fn from(error: $t) -> Self {
Self {
message: Cow::Owned(error.to_string()),
name: Cow::Borrowed(error.into()),
}
}
})*
}
}

impl_wasm_error_from!(
identity::comm::Error,
identity::core::Error,
identity::credential::Error,
identity::did::Error,
identity::iota::Error
);

impl From<serde_json::Error> for WasmError<'_> {
fn from(error: serde_json::Error) -> Self {
Self {
name: Cow::Borrowed("serde_json::Error"), // the exact error code is embedded in the message
message: Cow::Owned(error.to_string()),
}
}
}

impl From<identity::iota::BeeMessageError> for WasmError<'_> {
fn from(error: identity::iota::BeeMessageError) -> Self {
Self {
name: Cow::Borrowed("bee_message::Error"),
message: Cow::Owned(error.to_string()),
}
}
}
4 changes: 3 additions & 1 deletion bindings/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use wasm_bindgen::prelude::*;

#[macro_use]
mod macros;
mod utils;

#[macro_use]
pub mod error;

pub mod credential;
pub mod crypto;
Expand Down
10 changes: 5 additions & 5 deletions bindings/wasm/src/message/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use identity::comm;
use wasm_bindgen::prelude::*;

use crate::utils::err;
use crate::error::wasm_error;

#[wasm_bindgen(inspectable)]
#[derive(Clone, Debug, PartialEq)]
Expand All @@ -14,12 +14,12 @@ pub struct AuthenticationRequest(pub(crate) comm::AuthenticationRequest);
impl AuthenticationRequest {
#[wasm_bindgen(js_name = toJSON)]
pub fn to_json(&self) -> Result<JsValue, JsValue> {
JsValue::from_serde(&self.0).map_err(err)
JsValue::from_serde(&self.0).map_err(wasm_error)
}

#[wasm_bindgen(js_name = fromJSON)]
pub fn from_json(value: &JsValue) -> Result<AuthenticationRequest, JsValue> {
value.into_serde().map_err(err).map(Self)
value.into_serde().map_err(wasm_error).map(Self)
}
}

Expand All @@ -31,11 +31,11 @@ pub struct AuthenticationResponse(pub(crate) comm::AuthenticationResponse);
impl AuthenticationResponse {
#[wasm_bindgen(js_name = toJSON)]
pub fn to_json(&self) -> Result<JsValue, JsValue> {
JsValue::from_serde(&self.0).map_err(err)
JsValue::from_serde(&self.0).map_err(wasm_error)
}

#[wasm_bindgen(js_name = fromJSON)]
pub fn from_json(value: &JsValue) -> Result<AuthenticationResponse, JsValue> {
value.into_serde().map_err(err).map(Self)
value.into_serde().map_err(wasm_error).map(Self)
}
}
Loading