-
Notifications
You must be signed in to change notification settings - Fork 234
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow interfaces to be errors. (#1963)
- Loading branch information
Showing
23 changed files
with
623 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
[package] | ||
name = "uniffi-fixture-error-types" | ||
version = "0.22.0" | ||
edition = "2021" | ||
license = "MPL-2.0" | ||
publish = false | ||
|
||
[lib] | ||
crate-type = ["lib", "cdylib"] | ||
name = "uniffi_error_types" | ||
|
||
[dependencies] | ||
uniffi = {path = "../../uniffi"} | ||
anyhow = "1" | ||
thiserror = "1.0" | ||
|
||
[build-dependencies] | ||
uniffi = {path = "../../uniffi", features = ["build"] } | ||
|
||
[dev-dependencies] | ||
uniffi = {path = "../../uniffi", features = ["bindgen-tests"] } | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
Tests for objects as errors. | ||
|
||
This works well with explicit type wrangling: | ||
|
||
```rust | ||
fn oops() -> Result<(), Arc<MyError>> { | ||
let e = anyhow::Error::msg("oops"); | ||
Err(Arc::new(e.into())) | ||
} | ||
``` | ||
|
||
But a goal is to allow: | ||
|
||
```rust | ||
// doesn't work | ||
fn oops() -> anyhow::Result<()> { | ||
anyhow::bail!("oops"); | ||
} | ||
``` | ||
|
||
# Stuck! | ||
|
||
the above uniffi expands to: | ||
|
||
```rust | ||
extern "C" fn uniffi_uniffi_error_types_fn_func_oops( | ||
call_status: &mut ::uniffi::RustCallStatus, | ||
) -> <::std::result::Result< | ||
(), | ||
std::sync::Arc<ErrorInterface>, | ||
> as ::uniffi::LowerReturn<crate::UniFfiTag>>::ReturnType { | ||
... | ||
::uniffi::rust_call( | ||
call_status, | ||
|| { | ||
<::std::result::Result<(), std::sync::Arc<ErrorInterface>> as ::uniffi::LowerReturn ...>::lower_return( | ||
match uniffi_lift_args() { | ||
Ok(uniffi_args) => oops().map_err(::std::convert::Into::into), | ||
Err((arg_name, anyhow_error)) => ... | ||
}, | ||
) | ||
}, | ||
) | ||
} | ||
``` | ||
|
||
# Problem is: | ||
```rust | ||
Ok(uniffi_args) => oops().map_err(::std::convert::Into::into), | ||
``` | ||
|
||
map_err has `anyhow::Error<>`, we want `Arc<ErrorInterface>`, `::into` can't do that. | ||
|
||
This works for enum because all `Arc<ErrorInterface>`s above are `ErrorEnum`. So above call is more like: | ||
map_err has `CrateInternalError`, we want `CratePublicError`, `::into` can do that. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
fn main() { | ||
uniffi::generate_scaffolding("./src/error_types.udl").unwrap(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
namespace error_types { | ||
[Throws=ErrorInterface] | ||
void oops(); | ||
|
||
ErrorInterface get_error(string message); | ||
|
||
[Throws=RichError] | ||
void throw_rich(string message); | ||
}; | ||
|
||
interface TestInterface { | ||
constructor(); | ||
|
||
[Throws=ErrorInterface, Name="fallible_new"] | ||
constructor(); | ||
|
||
[Throws=ErrorInterface] | ||
void oops(); | ||
}; | ||
|
||
[Traits=(Debug, Display)] | ||
interface ErrorInterface { | ||
sequence<string> chain(); | ||
string? link(u64 index); | ||
}; | ||
|
||
// Kotlin replaces a trailing "Error" with "Exception" | ||
// for enums, so we should check it does for objects too. | ||
[Traits=(Debug, Display)] | ||
interface RichError { | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
use std::sync::Arc; | ||
|
||
#[derive(Debug, thiserror::Error)] | ||
#[error("{e:?}")] | ||
pub struct ErrorInterface { | ||
e: anyhow::Error, | ||
} | ||
|
||
impl ErrorInterface { | ||
fn chain(&self) -> Vec<String> { | ||
self.e.chain().map(ToString::to_string).collect() | ||
} | ||
fn link(&self, ndx: u64) -> Option<String> { | ||
self.e.chain().nth(ndx as usize).map(ToString::to_string) | ||
} | ||
} | ||
|
||
// A conversion into our ErrorInterface from anyhow::Error. | ||
// We can't use this implicitly yet, but it still helps. | ||
impl From<anyhow::Error> for ErrorInterface { | ||
fn from(e: anyhow::Error) -> Self { | ||
Self { e } | ||
} | ||
} | ||
|
||
// must do explicit conversion... | ||
fn oops() -> Result<(), Arc<ErrorInterface>> { | ||
Err(Arc::new( | ||
anyhow::Error::msg("oops") | ||
.context("because uniffi told me so") | ||
.into(), | ||
)) | ||
} | ||
|
||
#[uniffi::export] | ||
fn toops() -> Result<(), Arc<dyn ErrorTrait>> { | ||
Err(Arc::new(ErrorTraitImpl { | ||
m: "trait-oops".to_string(), | ||
})) | ||
} | ||
|
||
#[uniffi::export] | ||
async fn aoops() -> Result<(), Arc<ErrorInterface>> { | ||
Err(Arc::new(anyhow::Error::msg("async-oops").into())) | ||
} | ||
|
||
fn get_error(message: String) -> std::sync::Arc<ErrorInterface> { | ||
Arc::new(anyhow::Error::msg(message).into()) | ||
} | ||
|
||
#[uniffi::export] | ||
pub trait ErrorTrait: Send + Sync + std::fmt::Debug + std::error::Error { | ||
fn msg(&self) -> String; | ||
} | ||
|
||
#[derive(Debug, thiserror::Error)] | ||
#[error("{m:?}")] | ||
struct ErrorTraitImpl { | ||
m: String, | ||
} | ||
|
||
impl ErrorTrait for ErrorTraitImpl { | ||
fn msg(&self) -> String { | ||
self.m.clone() | ||
} | ||
} | ||
|
||
fn throw_rich(e: String) -> Result<(), RichError> { | ||
Err(RichError { e }) | ||
} | ||
|
||
// Exists to test trailing "Error" mapping in bindings | ||
#[derive(Debug, thiserror::Error)] | ||
#[error("RichError: {e:?}")] | ||
pub struct RichError { | ||
e: String, | ||
} | ||
|
||
impl RichError {} | ||
|
||
pub struct TestInterface {} | ||
|
||
impl TestInterface { | ||
fn new() -> Self { | ||
TestInterface {} | ||
} | ||
|
||
fn fallible_new() -> Result<Self, Arc<ErrorInterface>> { | ||
Err(Arc::new(anyhow::Error::msg("fallible_new").into())) | ||
} | ||
|
||
fn oops(&self) -> Result<(), Arc<ErrorInterface>> { | ||
Err(Arc::new( | ||
anyhow::Error::msg("oops") | ||
.context("because the interface told me so") | ||
.into(), | ||
)) | ||
} | ||
} | ||
|
||
#[uniffi::export] | ||
impl TestInterface { | ||
// can't define this in UDL due to #1915 | ||
async fn aoops(&self) -> Result<(), Arc<ErrorInterface>> { | ||
Err(Arc::new(anyhow::Error::msg("async-oops").into())) | ||
} | ||
} | ||
|
||
// A procmacro as an error | ||
#[derive(Debug, uniffi::Object, thiserror::Error)] | ||
#[uniffi::export(Debug, Display)] | ||
pub struct ProcErrorInterface { | ||
e: String, | ||
} | ||
|
||
#[uniffi::export] | ||
impl ProcErrorInterface { | ||
fn message(&self) -> String { | ||
self.e.clone() | ||
} | ||
} | ||
|
||
impl std::fmt::Display for ProcErrorInterface { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "ProcErrorInterface({})", self.e) | ||
} | ||
} | ||
|
||
#[uniffi::export] | ||
fn throw_proc_error(e: String) -> Result<(), Arc<ProcErrorInterface>> { | ||
Err(Arc::new(ProcErrorInterface { e })) | ||
} | ||
|
||
#[uniffi::export] | ||
fn return_proc_error(e: String) -> Arc<ProcErrorInterface> { | ||
Arc::new(ProcErrorInterface { e }) | ||
} | ||
|
||
// Enums have good coverage elsewhere, but simple coverage here is good. | ||
#[derive(thiserror::Error, uniffi::Error, Debug)] | ||
pub enum EnumError { | ||
#[error("Oops")] | ||
Oops, | ||
} | ||
|
||
#[uniffi::export] | ||
fn oops_enum() -> Result<(), EnumError> { | ||
Err(EnumError::Oops) | ||
} | ||
|
||
uniffi::include_scaffolding!("error_types"); |
Oops, something went wrong.