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

Add callbacks-example fixture from uniffi-rs #172

Merged
merged 1 commit into from
Dec 10, 2024
Merged
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
20 changes: 20 additions & 0 deletions fixtures/callbacks-example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"] }
5 changes: 5 additions & 0 deletions fixtures/callbacks-example/README.md
Original file line number Diff line number Diff line change
@@ -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).

9 changes: 9 additions & 0 deletions fixtures/callbacks-example/build.rs
Original file line number Diff line number Diff line change
@@ -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();
}
28 changes: 28 additions & 0 deletions fixtures/callbacks-example/src/callbacks.udl
Original file line number Diff line number Diff line change
@@ -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();
};
94 changes: 94 additions & 0 deletions fixtures/callbacks-example/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<uniffi::UnexpectedUniFFICallbackError> 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<Arc<dyn SimCard>> {
vec![Arc::new(RustySim {})]
}

// The call-answer, callback interface.
pub trait CallAnswerer {
fn answer(&self) -> Result<String, TelephoneError>;
}

#[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<dyn SimCard>,
answerer: Box<dyn CallAnswerer>,
) -> Result<String, TelephoneError> {
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<Self> {
Arc::new(Self)
}

// Exact same signature as the UDL version.
pub fn call(
&self,
sim: Arc<dyn SimCard>,
answerer: Box<dyn CallAnswerer>,
) -> Result<String, TelephoneError> {
if sim.name() != "rusty!" {
Ok(format!("{} est bon marché", sim.name()))
} else {
answerer.answer()
}
}
}

uniffi::include_scaffolding!("callbacks");
Original file line number Diff line number Diff line change
@@ -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();
});
5 changes: 5 additions & 0 deletions fixtures/callbacks-example/tests/test_generated_bindings.rs
Original file line number Diff line number Diff line change
@@ -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/
*/
Empty file.
2 changes: 1 addition & 1 deletion fixtures/callbacks-regression/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
Loading