Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.bitcoindevkit

import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.runner.RunWith
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

@RunWith(AndroidJUnit4::class)
class ErrorsTest {
@Test
fun bip39ErrorDisplaysBadWordCount() {
val thirteenWordMnemonic = "awesome awesome awesome awesome awesome awesome awesome awesome awesome awesome awesome awesome awesome"

// Building a Mnemonic fails with BadWordCount exception
val exception = assertFailsWith<Bip39Exception.BadWordCount> {
Mnemonic.fromString(thirteenWordMnemonic)
}

// The toString() method on the exception is the Display trait exported from Rust
assertEquals(
expected = "the word count 13 is not supported",
actual = exception.toString()
)

// The exception contains a field `wordCount` correctly populated
assertEquals(
expected = 13uL,
actual = exception.wordCount
)

// The `message` field on the exception is the concatenation of all fields on the type
assertEquals(
expected = "wordCount=13",
actual = exception.message
)
}

@Test
fun bip32ErrorDisplaysInvalidChildNumberFormat() {
// A derivation path cannot contain words and fails with an InvalidChildNumberFormat exception
val exception = assertFailsWith<Bip32Exception.InvalidChildNumberFormat> {
DerivationPath("invalid/path/string")
}

// The toString() method on the exception is the Display trait exported from Rust
assertEquals(
expected = "invalid format for child number",
actual = exception.toString()
)

// The `message` field on the exception is the concatenation of all fields on the type (in this case none)
assertEquals(
expected = "",
actual = exception.message
)
}
}
30 changes: 29 additions & 1 deletion bdk-ffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use bdk_wallet::keys::bip39::Error as BdkBip39Error;
use bdk_wallet::migration::PreV1MigrationError as BdkPreV1MigrationError;
use bdk_wallet::miniscript::descriptor::DescriptorKeyParseError as BdkDescriptorKeyParseError;
use bdk_wallet::miniscript::psbt::Error as BdkPsbtFinalizeError;
#[allow(deprecated)]
use bdk_wallet::signer::SignerError as BdkSignerError;
use bdk_wallet::tx_builder::AddForeignUtxoError as BdkAddForeignUtxoError;
use bdk_wallet::tx_builder::AddUtxoError;
Expand All @@ -37,6 +36,7 @@ use std::convert::TryInto;
// ------------------------------------------------------------------------

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum AddForeignUtxoError {
#[error("foreign utxo outpoint txid does not match PSBT input txid")]
InvalidTxid,
Expand All @@ -52,6 +52,7 @@ pub enum AddForeignUtxoError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum AddressParseError {
#[error("base58 address encoding error")]
Base58,
Expand Down Expand Up @@ -86,6 +87,7 @@ pub enum AddressParseError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum Bip32Error {
#[error("cannot derive from a hardened key")]
CannotDeriveFromHardenedKey,
Expand Down Expand Up @@ -122,6 +124,7 @@ pub enum Bip32Error {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum Bip39Error {
#[error("the word count {word_count} is not supported")]
BadWordCount { word_count: u64 },
Expand All @@ -140,6 +143,7 @@ pub enum Bip39Error {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum CalculateFeeError {
#[error("missing transaction output: {out_points:?}")]
MissingTxOut { out_points: Vec<OutPoint> },
Expand All @@ -149,12 +153,14 @@ pub enum CalculateFeeError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum CannotConnectError {
#[error("cannot include height: {height}")]
Include { height: u32 },
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum CreateTxError {
#[error("descriptor error: {error_message}")]
Descriptor { error_message: String },
Expand Down Expand Up @@ -224,6 +230,7 @@ pub enum CreateTxError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the PR title/changelog describe exporting Display on errors broadly, should this export be applied to the other uniffi::Error enums in this file too, or should the PR scope be narrowed to CreateWithPersistError only? Right now this is the only error type getting Debug/Display exported to the bindings.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep I should add it for all errors! Initially I just wanted to open this up for discussion and write up the PR description going into details. But now I'm ready to add it everywhere and will push to this PR soon.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating this to cover the other error enums too, that addresses the scope question from my earlier review.

One follow-up question: should this PR add at least one binding-facing test/example for the generated error display behavior? Since the PR description calls out different downstream behavior in Kotlin and Swift, it may be useful to assert one representative error path so we know the exported Display/Debug behavior stays covered.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great idea. Let me add a few tests right now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating this to cover the other error enums too. That addresses my scope question.

pub enum CreateWithPersistError {
#[error("sqlite persistence error: {error_message}")]
Persist { error_message: String },
Expand All @@ -236,6 +243,7 @@ pub enum CreateWithPersistError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum DescriptorError {
#[error("invalid hd key path")]
InvalidHdKeyPath,
Expand Down Expand Up @@ -278,6 +286,7 @@ pub enum DescriptorError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum DescriptorKeyError {
#[error("error parsing descriptor key: {error_message}")]
Parse { error_message: String },
Expand All @@ -293,6 +302,7 @@ pub enum DescriptorKeyError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum ElectrumError {
#[error("{error_message}")]
IOError { error_message: String },
Expand Down Expand Up @@ -347,6 +357,7 @@ pub enum ElectrumError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum EsploraError {
#[error("minreq error: {error_message}")]
Minreq { error_message: String },
Expand Down Expand Up @@ -392,6 +403,7 @@ pub enum EsploraError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum ExtractTxError {
#[error("an absurdly high fee rate of {fee_rate} sat/vbyte")]
AbsurdFeeRate { fee_rate: u64 },
Expand All @@ -407,13 +419,16 @@ pub enum ExtractTxError {
)]
OtherExtractTxErr,
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum FeeRateError {
#[error("arithmetic overflow")]
ArithmeticOverflow,
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum FromScriptError {
#[error("script is not a p2pkh, p2sh or witness program")]
UnrecognizedScript,
Expand All @@ -436,6 +451,7 @@ pub enum RequestBuilderError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum LoadWithPersistError {
#[error("sqlite persistence error: {error_message}")]
Persist { error_message: String },
Expand All @@ -448,6 +464,7 @@ pub enum LoadWithPersistError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum MiniscriptError {
#[error("absolute locktime error")]
AbsoluteLockTime,
Expand Down Expand Up @@ -562,6 +579,7 @@ pub enum MiniscriptError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum ParseAmountError {
#[error("amount out of range")]
OutOfRange,
Expand All @@ -584,12 +602,14 @@ pub enum ParseAmountError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum PersistenceError {
#[error("persistence error: {error_message}")]
Reason { error_message: String },
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum PreV1MigrationError {
#[error("migration helper is only available for sqlite-backed persisters")]
SqliteOnly,
Expand All @@ -605,6 +625,7 @@ pub enum PreV1MigrationError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum PsbtError {
#[error("invalid magic")]
InvalidMagic,
Expand Down Expand Up @@ -709,6 +730,7 @@ pub enum PsbtError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum PsbtParseError {
#[error("error in internal psbt data structure: {error_message}")]
PsbtEncoding { error_message: String },
Expand All @@ -718,12 +740,14 @@ pub enum PsbtParseError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum SighashParseError {
#[error("invalid sighash type: {error_message}")]
Invalid { error_message: String },
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum PsbtFinalizeError {
#[error("an input at index {index} is invalid: {reason}")]
InputError { reason: String, index: u32 },
Expand All @@ -734,6 +758,7 @@ pub enum PsbtFinalizeError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum SignerError {
#[error("missing key for signing")]
MissingKey,
Expand Down Expand Up @@ -788,6 +813,7 @@ pub enum SignerError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum TransactionError {
#[error("io error")]
Io,
Expand All @@ -813,12 +839,14 @@ pub enum TransactionError {
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum TxidParseError {
#[error("invalid txid: {txid}")]
InvalidTxid { txid: String },
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi::export(Debug, Display)]
pub enum CbfError {
#[error("the node is no longer running")]
NodeStopped,
Expand Down
Loading