Skip to content

Commit 5a25bd8

Browse files
committed
Simplify internal wasm error handling
Derive From for conveting internal errors to JsValue with Into::into.
1 parent 9474010 commit 5a25bd8

File tree

23 files changed

+300
-168
lines changed

23 files changed

+300
-168
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ members = [
1111
"identity-iota",
1212

1313
"flat-enum",
14+
"flat-enum-derive",
1415
"flat-enum-tests",
1516

1617
"examples",

bindings/wasm/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ js-sys = { version = "0.3" }
2020
serde = { version = "1.0", features = ["derive"] }
2121
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
2222
wasm-bindgen-futures = { version = "0.4", default-features = false }
23+
flat-enum = { version = "0.1", default-features = false, path = "../../flat-enum" }
2324

2425
[dependencies.identity]
2526
version = "=0.3.0"
@@ -29,6 +30,7 @@ features = ["comm", "wasm"]
2930

3031
[dev-dependencies]
3132
wasm-bindgen-test = { version = "0.3" }
33+
serde_json = { version = "1.0" }
3234

3335
[target.'cfg(target_arch = "wasm32")'.dependencies]
3436
chrono = { version = "0.4", features = ["wasmbind"] }

bindings/wasm/src/credential/credential.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,23 @@ impl VerifiableCredential {
3535
if !base.contains_key("@context") {
3636
base.insert(
3737
"@context".into(),
38-
Credential::<()>::base_context().serde_into().map_err(err)?,
38+
Credential::<()>::base_context().serde_into()?,
3939
);
4040
}
4141

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

4747
types.insert(0, Credential::<()>::base_type().into());
48-
base.insert("type".into(), types.serde_into().map_err(err)?);
48+
base.insert("type".into(), types.serde_into()?);
4949

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

54-
base.serde_into().map_err(err).map(Self)
54+
base.serde_into().map_err(Into::into).map(Self)
5555
}
5656

5757
#[wasm_bindgen]

bindings/wasm/src/credential/presentation.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ impl VerifiablePresentation {
2525
presentation_id: Option<String>,
2626
) -> Result<VerifiablePresentation, JsValue> {
2727
let credentials: OneOrMany<Credential> = credential_data.into_serde().map_err(err)?;
28-
let holder_url: Url = Url::parse(holder_doc.0.id().as_str()).map_err(err)?;
28+
let holder_url: Url = Url::parse(holder_doc.0.id().as_str())?;
2929

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

@@ -38,10 +38,10 @@ impl VerifiablePresentation {
3838
}
3939

4040
if let Some(presentation_id) = presentation_id {
41-
builder = builder.id(Url::parse(presentation_id).map_err(err)?);
41+
builder = builder.id(Url::parse(presentation_id)?);
4242
}
4343

44-
builder.build().map_err(err).map(Self)
44+
builder.build().map_err(Into::into).map(Self)
4545
}
4646

4747
/// Serializes a `VerifiablePresentation` object as a JSON object.

bindings/wasm/src/crypto/key_pair.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ impl KeyPair {
3131
/// Generates a new `KeyPair` object.
3232
#[wasm_bindgen(constructor)]
3333
pub fn new(type_: KeyType) -> Result<KeyPair, JsValue> {
34-
KeyPair_::new(type_.into()).map_err(err).map(Self)
34+
KeyPair_::new(type_.into()).map_err(Into::into).map(Self)
3535
}
3636

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

4343
Ok(Self((type_.into(), public, secret).into()))
4444
}

bindings/wasm/src/utils.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,42 @@
33

44
use wasm_bindgen::JsValue;
55

6-
/// Convert errors so they are readable in JS
6+
/// Convert errors to strings so they are readable in JS
77
pub fn err<T>(error: T) -> JsValue
88
where
99
T: ToString,
1010
{
1111
error.to_string().into()
1212
}
13+
14+
// Alternative to implementing From for JsValue when deriving FlatEnum
15+
// /// Convert errors that derive FlatEnum into JS
16+
// pub fn flat_err<T, V>(error: T) -> JsValue
17+
// where
18+
// T: flat_enum::IntoFlatEnum<V>,
19+
// V: flat_enum::FlatEnum,
20+
// {
21+
// // TODO: check that unwrap is infallible here?
22+
// JsValue::from_serde(&error.into_flat_enum()).unwrap()
23+
// }
24+
25+
#[cfg(test)]
26+
mod tests {
27+
use flat_enum::IntoFlatEnum;
28+
29+
#[test]
30+
fn test_js_error() {
31+
let err = identity::credential::Error::DIDError(
32+
identity::did::Error::CoreError(
33+
identity::core::Error::DecodeBitmap(
34+
std::io::Error::new(std::io::ErrorKind::Other, "something went wrong!")
35+
)
36+
)
37+
);
38+
println!("{}", err.to_string()); // Failed to decode roaring bitmap: something went wrong!
39+
let json_str = serde_json::to_string(&err.to_string()).unwrap();
40+
println!("{}", json_str); // "Failed to decode roaring bitmap: something went wrong!"
41+
let json = serde_json::to_string(&err.into_flat_enum()).unwrap();
42+
println!("{}", json); // {"code":"DIDError","description":"Failed to decode roaring bitmap: something went wrong!"}
43+
}
44+
}

flat-enum-derive/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "flat-enum-derive"
3+
version = "0.1.0"
4+
edition = "2018"
5+
license = "Apache-2.0"
6+
publish = false
7+
8+
[lib]
9+
name = "flat_enum_derive"
10+
proc-macro = true
11+
12+
[features]
13+
default = []
14+
wasm = []
15+
16+
[dependencies]
17+
proc-macro-error = { version = "1.0", default-features = false }
18+
quote = { version = "1.0", default-features = false }
19+
syn = { version = "1.0", features = ["extra-traits", "parsing", "derive"] }

flat-enum-derive/src/lib.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
use proc_macro::TokenStream;
2+
3+
use syn::__private::TokenStream2;
4+
use syn::spanned::Spanned;
5+
use syn::{Data, DeriveInput, Fields};
6+
7+
#[proc_macro_derive(FlatEnum)]
8+
#[proc_macro_error::proc_macro_error]
9+
pub fn derive_flat_enum(input: TokenStream) -> TokenStream {
10+
let ast: DeriveInput = syn::parse_macro_input!(input as DeriveInput);
11+
12+
// Build the flat error struct and enum
13+
gen_flat_enum(&ast)
14+
}
15+
16+
fn gen_flat_enum(ast: &syn::DeriveInput) -> TokenStream {
17+
let name = &ast.ident;
18+
let data = &ast.data;
19+
20+
let flat_struct_name = quote::format_ident!("Flat{}", name.to_string());
21+
let flat_enum_code_name = quote::format_ident!("Flat{}Code", name.to_string());
22+
23+
let mut flat_enum_code_variants;
24+
let mut flat_enum_code_from_cases;
25+
26+
match data {
27+
Data::Enum(data_enum) => {
28+
flat_enum_code_variants = TokenStream2::new();
29+
flat_enum_code_from_cases = TokenStream2::new();
30+
31+
// Iterate over enum variants
32+
for variant in &data_enum.variants {
33+
let variant_name = &variant.ident;
34+
35+
// Enum variants can:
36+
// - have unnamed fields like `Variant(i32, i64)`
37+
// - have named fields like `Variant {x: i32, y: i32}`
38+
// - be a Unit like `Variant`
39+
let fields_in_variant = match &variant.fields {
40+
Fields::Unnamed(_) => quote::quote_spanned! { variant.span()=> (..) },
41+
Fields::Unit => quote::quote_spanned! { variant.span()=> },
42+
Fields::Named(_) => quote::quote_spanned! { variant.span()=> {..} },
43+
};
44+
45+
// Generate a flat C-style enum variant
46+
let variant_doc_string = format!("[{}::{}]", name.to_string(), variant_name.to_string());
47+
flat_enum_code_variants.extend(quote::quote_spanned! {variant.span()=>
48+
#[doc = #variant_doc_string]
49+
#variant_name,
50+
});
51+
52+
// Implement From<OriginalEnum> cases
53+
// TODO: improve description strings, currently requires Debug to be implemented on the
54+
// original enum which includes the name in the string.
55+
// e.g. Enum::A => "A" - do we want "" instead?
56+
// e.g. Enum::B(1,2) => "B(1,2)" - do we want "(1,2)" instead?
57+
// e.g. Enum::C{"a","b"} => "C { "a", "b" }" - do we want "{ "a", "b" }" instead?
58+
flat_enum_code_from_cases.extend(quote::quote_spanned! {variant.span()=>
59+
#name::#variant_name #fields_in_variant => #flat_struct_name::new(#flat_enum_code_name::#variant_name, input.to_string()),
60+
});
61+
}
62+
}
63+
_ => proc_macro_error::abort_call_site!("FlatError is only supported for enums"),
64+
};
65+
66+
// Optionally implement From<*> for JsValue
67+
// Workaround since cannot implement From<T> for JsValue where T: IntoFlatEnum<dyn FlatEnum>
68+
// nor T: IntoFlatEnum<#flat_struct_name>
69+
#[allow(unused_mut, unused_assignments)]
70+
let mut impl_js_value_from = quote::quote!{};
71+
#[cfg(feature = "wasm")]
72+
{
73+
impl_js_value_from = quote::quote! {
74+
use flat_enum::IntoFlatEnum;
75+
impl From<#name> for wasm_bindgen::JsValue
76+
{
77+
fn from(input: #name) -> Self {
78+
// TODO: check that unwrap always works?
79+
wasm_bindgen::JsValue::from_serde(&input.into_flat_enum()).unwrap()
80+
}
81+
}
82+
};
83+
}
84+
85+
// Generate Flat* struct and Flat*Code enum
86+
let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl();
87+
let expanded = quote::quote! {
88+
// Serializable wrapper struct
89+
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
90+
#[repr(C)]
91+
pub struct #flat_struct_name {
92+
pub code: #flat_enum_code_name,
93+
pub description: String,
94+
}
95+
96+
impl #flat_struct_name {
97+
pub fn new(code: #flat_enum_code_name, description: String) -> Self {
98+
Self {
99+
code,
100+
description,
101+
}
102+
}
103+
}
104+
105+
// FlatEnum trait
106+
impl flat_enum::FlatEnum for #flat_struct_name {}
107+
108+
// C-style enum
109+
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
110+
#[repr(C)]
111+
pub enum #flat_enum_code_name {
112+
#flat_enum_code_variants
113+
}
114+
115+
// From implementation
116+
impl #impl_generics From<#name #type_generics> for #flat_struct_name #where_clause {
117+
fn from(input: #name #type_generics) -> Self {
118+
match &input {
119+
#flat_enum_code_from_cases
120+
}
121+
}
122+
}
123+
124+
// Add to_flat_enum() function to the original enum
125+
impl #impl_generics flat_enum::IntoFlatEnum<#flat_struct_name> for #name #type_generics #where_clause {
126+
fn into_flat_enum(self) -> #flat_struct_name {
127+
#flat_struct_name::from(self)
128+
}
129+
}
130+
131+
#impl_js_value_from
132+
};
133+
134+
TokenStream::from(expanded)
135+
}

flat-enum-tests/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ license = "Apache-2.0"
66
publish = false
77

88
[dev-dependencies]
9-
flat-enum = { version = "*", path = "../flat-enum" }
9+
flat-enum = { version = "*", path = "../flat-enum", default-features = false, features = ["derive"] }
1010
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }

flat-enum-tests/tests/test.rs

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,69 +2,76 @@
22
extern crate alloc;
33

44
use alloc::borrow::ToOwned;
5-
use alloc::format;
65
use alloc::string::String;
7-
use flat_enum::FlatEnum;
6+
use flat_enum::IntoFlatEnum;
7+
use core::fmt::Formatter;
8+
use alloc::string::ToString;
89

9-
#[derive(Debug, FlatEnum)]
10-
pub enum Enum {
10+
#[derive(Debug, flat_enum::derive::FlatEnum)]
11+
pub enum BasicEnum {
1112
A,
1213
B(String),
1314
C { a: String },
1415
D { a: String, b: i64 },
1516
}
1617

18+
impl core::fmt::Display for BasicEnum {
19+
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
20+
f.write_fmt(format_args!("{:?}", self))
21+
}
22+
}
23+
1724
#[test]
1825
fn test_flat_enum_basic() {
19-
let a = FlatEnum {
20-
code: FlatEnumCode::A,
26+
let a = FlatBasicEnum {
27+
code: FlatBasicEnumCode::A,
2128
description: "A".to_owned(),
2229
};
23-
assert_eq!(a.code, FlatEnumCode::A);
30+
assert_eq!(a.code, FlatBasicEnumCode::A);
2431
assert_eq!(a.description, "A");
2532
}
2633

2734
#[test]
2835
fn test_flat_enum_from() {
29-
let a = FlatEnum::from(Enum::A);
30-
assert_eq!(a.code, FlatEnumCode::A);
36+
let a = FlatBasicEnum::from(BasicEnum::A);
37+
assert_eq!(a.code, FlatBasicEnumCode::A);
3138
assert_eq!(a.description, "A");
3239

33-
let b = FlatEnum::from(Enum::B("bee".to_owned()));
34-
assert_eq!(b.code, FlatEnumCode::B);
40+
let b = FlatBasicEnum::from(BasicEnum::B("bee".to_owned()));
41+
assert_eq!(b.code, FlatBasicEnumCode::B);
3542
assert_eq!(b.description, r#"B("bee")"#);
3643

37-
let c = FlatEnum::from(Enum::C { a: "c".to_owned() });
38-
assert_eq!(c.code, FlatEnumCode::C);
44+
let c = FlatBasicEnum::from(BasicEnum::C { a: "c".to_owned() });
45+
assert_eq!(c.code, FlatBasicEnumCode::C);
3946
assert_eq!(c.description, r#"C { a: "c" }"#);
4047

41-
let d = FlatEnum::from(Enum::D {
48+
let d = FlatBasicEnum::from(BasicEnum::D {
4249
a: "d".to_owned(),
4350
b: 123,
4451
});
45-
assert_eq!(d.code, FlatEnumCode::D);
52+
assert_eq!(d.code, FlatBasicEnumCode::D);
4653
assert_eq!(d.description, r#"D { a: "d", b: 123 }"#);
4754
}
4855

4956
#[test]
5057
fn test_to_flat_enum() {
51-
let a = Enum::A.to_flat_enum();
52-
assert_eq!(a.code, FlatEnumCode::A);
58+
let a = BasicEnum::A.into_flat_enum();
59+
assert_eq!(a.code, FlatBasicEnumCode::A);
5360
assert_eq!(a.description, "A");
5461

55-
let b = Enum::B("bee".to_owned()).to_flat_enum();
56-
assert_eq!(b.code, FlatEnumCode::B);
62+
let b = BasicEnum::B("bee".to_owned()).into_flat_enum();
63+
assert_eq!(b.code, FlatBasicEnumCode::B);
5764
assert_eq!(b.description, r#"B("bee")"#);
5865

59-
let c = Enum::C { a: "c".to_owned() }.to_flat_enum();
60-
assert_eq!(c.code, FlatEnumCode::C);
66+
let c = BasicEnum::C { a: "c".to_owned() }.into_flat_enum();
67+
assert_eq!(c.code, FlatBasicEnumCode::C);
6168
assert_eq!(c.description, r#"C { a: "c" }"#);
6269

63-
let d = Enum::D {
70+
let d = BasicEnum::D {
6471
a: "d".to_owned(),
6572
b: 123,
6673
}
67-
.to_flat_enum();
68-
assert_eq!(d.code, FlatEnumCode::D);
74+
.into_flat_enum();
75+
assert_eq!(d.code, FlatBasicEnumCode::D);
6976
assert_eq!(d.description, r#"D { a: "d", b: 123 }"#);
7077
}

0 commit comments

Comments
 (0)