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

[PM-13660] implement catchable non generic error types #31

Merged
merged 54 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
4b7ebf3
feat: scaffold error crates
coroiu Nov 5, 2024
2706477
feat: add basic manual implementation
coroiu Nov 5, 2024
03dbeba
refactor: convert into metadata trait/struct
coroiu Nov 5, 2024
e45a994
feat: scaffold proc macro
coroiu Nov 5, 2024
97ff4ae
feat: implement derive macro
coroiu Nov 5, 2024
3393de9
feat: use attribute macro as alias for derive macro
coroiu Nov 5, 2024
9a9c59b
chore: clean up commented old code
coroiu Nov 5, 2024
51b23ff
refactor: move iteration into match_arms variable
coroiu Nov 5, 2024
d164501
feat: add support for complex enums
coroiu Nov 5, 2024
3f00e38
feat: add support for struct
coroiu Nov 5, 2024
d868324
feat: add error handling for unions
coroiu Nov 5, 2024
d6ee79d
feat: add support for getting all variants
coroiu Nov 5, 2024
8e91caf
feat: add support for throwing bitwarden error in wasm
coroiu Nov 6, 2024
eaf33e0
chore: add proc-macro2 as dependency
coroiu Nov 6, 2024
9d81fd8
chore: clean up unused file
coroiu Nov 7, 2024
e3aa732
feat: generate typescript types
coroiu Nov 7, 2024
fd86a64
chore: add missing dependencies
coroiu Nov 7, 2024
e4be0ee
refactor: rename variant to flat error
coroiu Nov 8, 2024
e9087d3
feat: add macro type attribute
coroiu Nov 8, 2024
4fedaac
feat: add basic (ToString) error support
coroiu Nov 8, 2024
c3647ff
feat: add native JSValue support to errors using bitwarden_error
coroiu Nov 8, 2024
8e8b56b
feat: add native wasm support to flat errors
coroiu Nov 11, 2024
e013203
feat: add support for fully serialized errors
coroiu Nov 11, 2024
eba5023
refactor: move basic implementations into separate module
coroiu Nov 11, 2024
3224046
refactor: move flat implementations into separate module
coroiu Nov 11, 2024
33e6bcd
refactor: move full implementations into separate module
coroiu Nov 11, 2024
e32f5b6
refactor: move main attribute into separate module
coroiu Nov 11, 2024
e8901c6
refactor: only allow flat for enums
coroiu Nov 11, 2024
e4622fb
refactor: simplify flat wasm attributes
coroiu Nov 11, 2024
c2511b3
refactor: remove old BitwardenError trait
coroiu Nov 11, 2024
406c845
fix: tests not matching new variant formatting
coroiu Nov 11, 2024
5c8f5ef
refactor: encapsulate macro output
coroiu Nov 12, 2024
d144611
refactor: stop polluting imports with wasm-bindgen re-export
coroiu Nov 12, 2024
6843e4a
chore: clean up unusued file
coroiu Nov 12, 2024
cb45b42
feat: add isError function to basic errors
coroiu Nov 12, 2024
9d3999e
feat: add isError function to flat errors
coroiu Nov 12, 2024
9139493
fix: dependency format
coroiu Nov 12, 2024
fa8d913
chore: clean up old generic error solution
coroiu Nov 12, 2024
2017aea
fix: lint
coroiu Nov 12, 2024
60a5091
fix: clippy issues
coroiu Nov 12, 2024
159f87b
fix: remove `ToString` implementation in tests
coroiu Nov 12, 2024
1ef0bfe
fix: various issues in tests
coroiu Nov 12, 2024
1e6b2e7
refactor: move everything into attribute macro
coroiu Nov 13, 2024
5cfa4d0
feat: allow custom export names
coroiu Nov 13, 2024
7b627cb
feat: add compilation error for `export_as` with `full`
coroiu Nov 13, 2024
5c74f88
feat: change core error export name
coroiu Nov 13, 2024
7c051d6
Merge branch 'main' into PM-13660-implement-catchable-non-generic-errโ€ฆ
coroiu Nov 13, 2024
e4ac54c
refactor: minimize duplication
coroiu Nov 15, 2024
ebaaeef
refactor: use qualified trait name
coroiu Nov 15, 2024
a3db63f
refactor: reduce rename duplication
coroiu Nov 15, 2024
e1fdbe4
feat: add documentation to macro
coroiu Nov 15, 2024
7f9c7dc
feat: add tsify as dev dependency to macro lib
coroiu Nov 18, 2024
2db85bf
fix: lint
coroiu Nov 18, 2024
6347bae
fix: doctest
coroiu Nov 18, 2024
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
220 changes: 188 additions & 32 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ bitwarden-generators = { path = "crates/bitwarden-generators", version = "=1.0.0
bitwarden-send = { path = "crates/bitwarden-send", version = "=1.0.0" }
bitwarden-sm = { path = "bitwarden_license/bitwarden-sm", version = "=1.0.0" }
bitwarden-vault = { path = "crates/bitwarden-vault", version = "=1.0.0" }
bitwarden-error = { path = "crates/bitwarden-error", version = "=1.0.0" }
bitwarden-error-macro = { path = "crates/bitwarden-error-macro", version = "=1.0.0" }

# External crates that are expected to maintain a consistent version across all crates
chrono = { version = ">=0.4.26, <0.5", features = [
Expand Down Expand Up @@ -56,6 +58,7 @@ uniffi = "=0.28.1"
uuid = { version = ">=1.3.3, <2.0", features = ["serde", "v4"] }
validator = { version = "0.18.1", features = ["derive"] }
wasm-bindgen = { version = ">=0.2.91, <0.3", features = ["serde-serialize"] }
js-sys = { version = ">=0.3.72, <0.4" }
wasm-bindgen-futures = "0.4.41"

[workspace.lints.clippy]
Expand Down
9 changes: 8 additions & 1 deletion crates/bitwarden-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ no-memory-hardening = [
] # Disable memory hardening features
uniffi = ["bitwarden-crypto/uniffi", "dep:uniffi"] # Uniffi bindings
secrets = [] # Secrets manager API
wasm = ["dep:wasm-bindgen", "dep:tsify-next"] # WASM support
wasm = [
"bitwarden-error/wasm",
"dep:wasm-bindgen",
"dep:js-sys",
"dep:tsify-next",
] # WASM support

[dependencies]
base64 = ">=0.22.1, <0.23"
Expand Down Expand Up @@ -49,6 +54,8 @@ wasm-bindgen = { workspace = true, optional = true }
zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] }
zxcvbn = { version = ">=3.0.1, <4.0", optional = true }
tsify-next = { workspace = true, optional = true }
js-sys = { workspace = true, optional = true }
bitwarden-error = { workspace = true }

[target.'cfg(not(target_arch="wasm32"))'.dependencies]
# By default, we use rustls as the TLS stack and rust-platform-verifier to support user-installed root certificates
Expand Down
2 changes: 2 additions & 0 deletions crates/bitwarden-core/src/client/encryption_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
use bitwarden_crypto::{AsymmetricCryptoKey, CryptoError, KeyContainer, SymmetricCryptoKey};
#[cfg(feature = "internal")]
use bitwarden_crypto::{AsymmetricEncString, EncString, MasterKey};
use bitwarden_error::prelude::*;
use thiserror::Error;
use uuid::Uuid;

#[cfg(feature = "internal")]
use crate::error::Result;
use crate::VaultLocked;

#[bitwarden_error(flat)]

Check warning on line 14 in crates/bitwarden-core/src/client/encryption_settings.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-core/src/client/encryption_settings.rs#L14

Added line #L14 was not covered by tests
#[derive(Debug, Error)]
pub enum EncryptionSettingsError {
#[error("Cryptography error, {0}")]
Expand Down
2 changes: 2 additions & 0 deletions crates/bitwarden-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use bitwarden_api_api::apis::Error as ApiError;
use bitwarden_api_identity::apis::Error as IdentityError;
use bitwarden_error::prelude::*;
use log::debug;
use reqwest::StatusCode;
use thiserror::Error;
Expand All @@ -12,6 +13,7 @@
#[cfg(feature = "internal")]
use crate::client::encryption_settings::EncryptionSettingsError;

#[bitwarden_error(flat, export_as = "CoreError")]

Check warning on line 16 in crates/bitwarden-core/src/error.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-core/src/error.rs#L16

Added line #L16 was not covered by tests
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Expand Down
33 changes: 33 additions & 0 deletions crates/bitwarden-error-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "bitwarden-error-macro"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
homepage.workspace = true
repository.workspace = true
license-file.workspace = true
keywords.workspace = true

[features]
wasm = ["bitwarden-error/wasm"]

[dependencies]
darling = "0.20.10"
proc-macro2 = "1.0.89"
quote = "1.0.37"
syn = "2.0.87"

[lints]
workspace = true

[lib]
proc-macro = true

[dev-dependencies]
bitwarden-error.workspace = true
serde.workspace = true
thiserror.workspace = true
tsify-next.workspace = true
js-sys.workspace = true
wasm-bindgen.workspace = true
23 changes: 23 additions & 0 deletions crates/bitwarden-error-macro/src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use darling::FromMeta;

#[derive(FromMeta)]

Check warning on line 3 in crates/bitwarden-error-macro/src/args.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-error-macro/src/args.rs#L3

Added line #L3 was not covered by tests
pub(crate) struct BitwardenErrorArgs {
#[darling(flatten)]
pub error_type: BitwardenErrorType,

#[darling(default)]
pub export_as: Option<String>,

Check warning on line 9 in crates/bitwarden-error-macro/src/args.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-error-macro/src/args.rs#L9

Added line #L9 was not covered by tests
}

#[derive(FromMeta)]
#[darling(rename_all = "snake_case")]
pub(crate) enum BitwardenErrorType {
dani-garcia marked this conversation as resolved.
Show resolved Hide resolved
/// The error is going to be converted into a string using the `ToString` trait
Basic,

/// The error is going to be converted into a flat error using the `FlatError` trait
Flat,

/// The entire error stack is going to be made available using `serde`
Full,
}
49 changes: 49 additions & 0 deletions crates/bitwarden-error-macro/src/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use darling::{ast::NestedMeta, FromMeta};
use quote::format_ident;

use crate::args::{BitwardenErrorArgs, BitwardenErrorType};

pub(crate) fn bitwarden_error(
args: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let attr_args = match NestedMeta::parse_meta_list(args.into()) {
Ok(v) => v,
Err(e) => {
return proc_macro::TokenStream::from(darling::Error::from(e).write_errors());

Check warning on line 13 in crates/bitwarden-error-macro/src/attribute.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-error-macro/src/attribute.rs#L12-L13

Added lines #L12 - L13 were not covered by tests
}
};

let args = match BitwardenErrorArgs::from_list(&attr_args) {
Ok(params) => params,
Err(error) => {
return proc_macro::TokenStream::from(error.write_errors());

Check warning on line 20 in crates/bitwarden-error-macro/src/attribute.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-error-macro/src/attribute.rs#L19-L20

Added lines #L19 - L20 were not covered by tests
}
};

let input = syn::parse_macro_input!(item as syn::DeriveInput);
let type_identifier = &input.ident;
let export_as_identifier = &args
.export_as
.as_ref()
.map(|export_as| format_ident!("{}", export_as))
.unwrap_or(input.ident.clone());

match args.error_type {
BitwardenErrorType::Basic => crate::basic::attribute::bitwarden_error_basic(
&input,
type_identifier,
export_as_identifier,
),
BitwardenErrorType::Flat => crate::flat::attribute::bitwarden_error_flat(
&input,
type_identifier,
export_as_identifier,
),
BitwardenErrorType::Full => crate::full::attribute::bitwarden_error_full(
&input,
type_identifier,
export_as_identifier,
),
}
}
61 changes: 61 additions & 0 deletions crates/bitwarden-error-macro/src/basic/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use quote::quote;

pub(crate) fn bitwarden_error_basic(
input: &syn::DeriveInput,
type_identifier: &proc_macro2::Ident,
export_as_identifier: &proc_macro2::Ident,
) -> proc_macro::TokenStream {
let wasm =
cfg!(feature = "wasm").then(|| basic_error_wasm(type_identifier, export_as_identifier));
quote! {
#input

#wasm
}
.into()
}

fn basic_error_wasm(
type_identifier: &proc_macro2::Ident,
export_as_identifier: &proc_macro2::Ident,
) -> proc_macro2::TokenStream {
let export_as_identifier_str = export_as_identifier.to_string();
let is_error_function_name = format!("is{}", export_as_identifier);
let ts_code_str = format!(
r##"r#"
export interface {export_as_identifier} extends Error {{
name: "{export_as_identifier}";
}};

export function {is_error_function_name}(error: any): error is {export_as_identifier};
"#"##
);
let ts_code: proc_macro2::TokenStream = ts_code_str
.parse()
.expect("Could not generate TypeScript code");

quote! {
const _: () = {
use wasm_bindgen::prelude::*;

#[wasm_bindgen(typescript_custom_section)]
const TS_APPEND_CONTENT: &'static str = #ts_code;

#[wasm_bindgen(js_name = #is_error_function_name, skip_typescript)]
pub fn is_error(error: &JsValue) -> bool {
let name_js_value = js_sys::Reflect::get(&error, &JsValue::from_str("name")).unwrap_or(JsValue::NULL);
let name = name_js_value.as_string().unwrap_or_default();
name == #export_as_identifier_str
}

#[automatically_derived]
impl From<#type_identifier> for JsValue {
fn from(error: #type_identifier) -> Self {
let js_error = SdkJsError::new(error.to_string());
js_error.set_name(#export_as_identifier_str.to_owned());
js_error.into()
}
}
};
}
}
1 change: 1 addition & 0 deletions crates/bitwarden-error-macro/src/basic/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod attribute;
115 changes: 115 additions & 0 deletions crates/bitwarden-error-macro/src/flat/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use quote::quote;
use syn::Data;

pub(crate) fn bitwarden_error_flat(
input: &syn::DeriveInput,
type_identifier: &proc_macro2::Ident,
export_as_identifier: &proc_macro2::Ident,
) -> proc_macro::TokenStream {
match &input.data {
Data::Enum(data) => {
let variant_names = data.variants.iter().map(|variant| &variant.ident);
let match_arms = data.variants.iter().map(|variant| {
let variant_ident = &variant.ident;
let variant_str = variant_ident.to_string();

match variant.fields {
syn::Fields::Unit => {
quote! {
#type_identifier::#variant_ident => #variant_str
}
}
syn::Fields::Named(_) => {
quote! {
#type_identifier::#variant_ident { .. } => #variant_str
}
}
syn::Fields::Unnamed(_) => {
quote! {
#type_identifier::#variant_ident(..) => #variant_str
}
}
}
});

let wasm = cfg!(feature = "wasm").then(|| {
flat_error_wasm(
type_identifier,
export_as_identifier,
&variant_names.collect::<Vec<_>>(),
)
});

quote! {
#input
#wasm

#[automatically_derived]
impl ::bitwarden_error::prelude::FlatError for #type_identifier {
fn error_variant(&self) -> &'static str {
match &self {
#(#match_arms), *
}
}
}
}
.into()
}
_ => syn::Error::new_spanned(input, "bitwarden_error can only be used with enums")
.to_compile_error()
.into(),

Check warning on line 60 in crates/bitwarden-error-macro/src/flat/attribute.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-error-macro/src/flat/attribute.rs#L58-L60

Added lines #L58 - L60 were not covered by tests
}
}

fn flat_error_wasm(
type_identifier: &proc_macro2::Ident,
export_as_identifier: &proc_macro2::Ident,
variant_names: &[&proc_macro2::Ident],
) -> proc_macro2::TokenStream {
let export_as_identifier_str = export_as_identifier.to_string();
let is_error_function_name = format!("is{}", export_as_identifier);
let ts_variant_names = variant_names
.iter()
.map(|vn| format!(r#""{vn}""#))
.collect::<Vec<String>>()
.join("|");
let ts_code_str = format!(
r##"r#"
export interface {export_as_identifier_str} extends Error {{
name: "{export_as_identifier_str}";
variant: {ts_variant_names};
}};

export function {is_error_function_name}(error: any): error is {export_as_identifier_str};
"#"##,
);
let ts_code: proc_macro2::TokenStream = ts_code_str
.parse()
.expect("Could not generate TypeScript code");

quote! {
const _: () = {
use wasm_bindgen::prelude::*;

#[wasm_bindgen(typescript_custom_section)]
const TS_APPEND_CONTENT: &'static str = #ts_code;

#[wasm_bindgen(js_name = #is_error_function_name, skip_typescript)]
pub fn is_error(error: &JsValue) -> bool {
let name_js_value = js_sys::Reflect::get(&error, &JsValue::from_str("name")).unwrap_or(JsValue::NULL);
let name = name_js_value.as_string().unwrap_or_default();
name == #export_as_identifier_str
}

#[automatically_derived]
impl From<#type_identifier> for JsValue {
fn from(error: #type_identifier) -> Self {
let js_error = SdkJsError::new(error.to_string());
js_error.set_name(#export_as_identifier_str.to_owned());
js_error.set_variant(error.error_variant().to_owned());
js_error.into()
}
}
};
}
}
1 change: 1 addition & 0 deletions crates/bitwarden-error-macro/src/flat/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod attribute;
28 changes: 28 additions & 0 deletions crates/bitwarden-error-macro/src/full/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use darling::Error;
use quote::quote;

pub(crate) fn bitwarden_error_full(
input: &syn::DeriveInput,
type_identifier: &proc_macro2::Ident,
export_as_identifier: &proc_macro2::Ident,
) -> proc_macro::TokenStream {
if type_identifier != export_as_identifier {
return Error::custom("`bitwarden_error(full)` does not currently support `export_as`")
.write_errors()
.into();

Check warning on line 12 in crates/bitwarden-error-macro/src/full/attribute.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-error-macro/src/full/attribute.rs#L10-L12

Added lines #L10 - L12 were not covered by tests
}

let wasm_attributes = cfg!(feature = "wasm").then(|| {
quote! {
#[derive(tsify_next::Tsify)]
#[tsify(into_wasm_abi)]
}
});

quote! {
#[derive(serde::Serialize)]
#wasm_attributes
#input
}
.into()
}
1 change: 1 addition & 0 deletions crates/bitwarden-error-macro/src/full/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod attribute;
Loading
Loading