From 0978683d1bed1ad3ab02efb2286f614c144aa624 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Sat, 9 Nov 2024 23:54:52 +0000 Subject: [PATCH] Add callbacks-example fixture from uniffi-rs --- fixtures/callbacks-example/Cargo.toml | 20 ++++ fixtures/callbacks-example/README.md | 5 + fixtures/callbacks-example/build.rs | 9 ++ fixtures/callbacks-example/src/callbacks.udl | 28 ++++++ fixtures/callbacks-example/src/lib.rs | 94 +++++++++++++++++++ .../tests/bindings/test_callbacks_example.ts | 69 ++++++++++++++ .../tests/test_generated_bindings.rs | 5 + fixtures/callbacks-example/uniffi.toml | 0 fixtures/callbacks-regression/Cargo.toml | 2 +- 9 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 fixtures/callbacks-example/Cargo.toml create mode 100644 fixtures/callbacks-example/README.md create mode 100644 fixtures/callbacks-example/build.rs create mode 100644 fixtures/callbacks-example/src/callbacks.udl create mode 100644 fixtures/callbacks-example/src/lib.rs create mode 100644 fixtures/callbacks-example/tests/bindings/test_callbacks_example.ts create mode 100644 fixtures/callbacks-example/tests/test_generated_bindings.rs create mode 100644 fixtures/callbacks-example/uniffi.toml diff --git a/fixtures/callbacks-example/Cargo.toml b/fixtures/callbacks-example/Cargo.toml new file mode 100644 index 00000000..cda370ec --- /dev/null +++ b/fixtures/callbacks-example/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "uniffi-example-callbacks" +edition = "2021" +version = "0.22.0" +license = "MPL-2.0" +publish = false + +[lib] +crate-type = ["lib", "cdylib"] +name = "callbacks" + +[dependencies] +uniffi = { workspace = true } +thiserror = "1.0" + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } diff --git a/fixtures/callbacks-example/README.md b/fixtures/callbacks-example/README.md new file mode 100644 index 00000000..4c57c1d5 --- /dev/null +++ b/fixtures/callbacks-example/README.md @@ -0,0 +1,5 @@ +This is an example of UniFFI "callbacks". + +Callbacks are the ability to implement Rust traits in foreign languages. These are defined by +[normal UniFFI traits](../../docs/manual/src/foreign_traits.md) or via ["callback" definitions](../../docs/manual/src/udl/callback_interfaces.md). + diff --git a/fixtures/callbacks-example/build.rs b/fixtures/callbacks-example/build.rs new file mode 100644 index 00000000..76803897 --- /dev/null +++ b/fixtures/callbacks-example/build.rs @@ -0,0 +1,9 @@ +/* + * 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/callbacks.udl").unwrap(); +} diff --git a/fixtures/callbacks-example/src/callbacks.udl b/fixtures/callbacks-example/src/callbacks.udl new file mode 100644 index 00000000..1ce79fda --- /dev/null +++ b/fixtures/callbacks-example/src/callbacks.udl @@ -0,0 +1,28 @@ +namespace callbacks { + // `get_sim_cards` is defined via a procmacro. +}; + +// This trait is implemented in Rust and in foreign bindings. +[Trait, WithForeign] +interface SimCard { + string name(); // The name of the carrier/provider. +}; + +[Error] +enum TelephoneError { + "Busy", + "InternalTelephoneError", +}; + +interface Telephone { + constructor(); + [Throws=TelephoneError] + string call(SimCard sim, CallAnswerer answerer); +}; + +// callback interfaces are discouraged now that foreign code can +// implement traits, but here's one! +callback interface CallAnswerer { + [Throws=TelephoneError] + string answer(); +}; diff --git a/fixtures/callbacks-example/src/lib.rs b/fixtures/callbacks-example/src/lib.rs new file mode 100644 index 00000000..b9be75b0 --- /dev/null +++ b/fixtures/callbacks-example/src/lib.rs @@ -0,0 +1,94 @@ +/* + * 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)] +pub enum TelephoneError { + #[error("Busy")] + Busy, + #[error("InternalTelephoneError")] + InternalTelephoneError, +} + +// Need to implement this From<> impl in order to handle unexpected callback errors. See the +// Callback Interfaces section of the handbook for more info. +impl From for TelephoneError { + fn from(_: uniffi::UnexpectedUniFFICallbackError) -> Self { + Self::InternalTelephoneError + } +} + +// SIM cards. +pub trait SimCard: Send + Sync { + fn name(&self) -> String; +} + +struct RustySim; +impl SimCard for RustySim { + fn name(&self) -> String { + "rusty!".to_string() + } +} + +// namespace functions. +#[uniffi::export] +fn get_sim_cards() -> Vec> { + vec![Arc::new(RustySim {})] +} + +// The call-answer, callback interface. +pub trait CallAnswerer { + fn answer(&self) -> Result; +} + +#[derive(Debug, Default, Clone)] +pub struct Telephone; +impl Telephone { + pub fn new() -> Self { + Self {} + } + + pub fn call( + &self, + // Traits are Arc<>, callbacks Box<>. + sim: Arc, + answerer: Box, + ) -> Result { + if sim.name() != "rusty!" { + Ok(format!("{} est bon marché", sim.name())) + } else { + answerer.answer() + } + } +} + +// Some proc-macro definitions of the same thing. +#[derive(uniffi::Object, Debug, Default, Clone)] +pub struct FancyTelephone; + +#[uniffi::export] +impl FancyTelephone { + #[uniffi::constructor] + pub fn new() -> Arc { + Arc::new(Self) + } + + // Exact same signature as the UDL version. + pub fn call( + &self, + sim: Arc, + answerer: Box, + ) -> Result { + if sim.name() != "rusty!" { + Ok(format!("{} est bon marché", sim.name())) + } else { + answerer.answer() + } + } +} + +uniffi::include_scaffolding!("callbacks"); diff --git a/fixtures/callbacks-example/tests/bindings/test_callbacks_example.ts b/fixtures/callbacks-example/tests/bindings/test_callbacks_example.ts new file mode 100644 index 00000000..b4e19e5e --- /dev/null +++ b/fixtures/callbacks-example/tests/bindings/test_callbacks_example.ts @@ -0,0 +1,69 @@ +/* + * 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/ + */ + +import generated, { + CallAnswerer, + Telephone, + getSimCards, + SimCard, + TelephoneError, +} from "../../generated/callbacks"; +import { test } from "@/asserts"; + +// This is only needed in tests. +generated.initialize(); + +class SomeOtherError extends Error {} + +// Simple example just to see it work. +// Pass in a string, get a string back. +// Pass in nothing, get unit back. +class CallAnswererImpl implements CallAnswerer { + constructor(private mode: string) {} + answer(): string { + if (this.mode === "normal") { + return "Bonjour"; + } else if (this.mode === "busy") { + throw new TelephoneError.Busy("I'm busy"); + } else { + throw new SomeOtherError(); + } + } +} + +test("A Rust sim, with a Typescript call answerer", (t) => { + const telephone = new Telephone(); + const sim = getSimCards()[0]; + t.assertEqual(telephone.call(sim, new CallAnswererImpl("normal")), "Bonjour"); + telephone.uniffiDestroy(); +}); + +// Our own sim. +class Sim implements SimCard { + name(): string { + return "typescript"; + } +} +test("A typescript sim with a typescript answerer", (t) => { + const telephone = new Telephone(); + t.assertEqual( + telephone.call(new Sim(), new CallAnswererImpl("normal")), + "typescript est bon marché", + ); + telephone.uniffiDestroy(); +}); + +test("Errors are serialized and returned", (t) => { + const telephone = new Telephone(); + const sim = getSimCards()[0]; + t.assertThrows(TelephoneError.Busy.instanceOf, () => + telephone.call(sim, new CallAnswererImpl("busy")), + ); + t.assertThrows(TelephoneError.InternalTelephoneError.instanceOf, () => + telephone.call(sim, new CallAnswererImpl("something-else")), + ); + telephone.uniffiDestroy(); +}); diff --git a/fixtures/callbacks-example/tests/test_generated_bindings.rs b/fixtures/callbacks-example/tests/test_generated_bindings.rs new file mode 100644 index 00000000..24ffd28d --- /dev/null +++ b/fixtures/callbacks-example/tests/test_generated_bindings.rs @@ -0,0 +1,5 @@ +/* + * 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/ + */ diff --git a/fixtures/callbacks-example/uniffi.toml b/fixtures/callbacks-example/uniffi.toml new file mode 100644 index 00000000..e69de29b diff --git a/fixtures/callbacks-regression/Cargo.toml b/fixtures/callbacks-regression/Cargo.toml index 0839c845..7534ee31 100644 --- a/fixtures/callbacks-regression/Cargo.toml +++ b/fixtures/callbacks-regression/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "uniffi-example-callbacks" +name = "uniffi-example-callbacks-deadlock-regression" edition = "2021" version = "0.22.0" license = "MPL-2.0"