diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/uniffi-example-todolist | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/uniffi-example-todolist')
10 files changed, 485 insertions, 0 deletions
diff --git a/third_party/rust/uniffi-example-todolist/.cargo-checksum.json b/third_party/rust/uniffi-example-todolist/.cargo-checksum.json new file mode 100644 index 0000000000..0f7516a258 --- /dev/null +++ b/third_party/rust/uniffi-example-todolist/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"2cadeccd37f54f3fe48ee85eeed4acdf6e7299acb6b43ed026d16695150cbab7","build.rs":"696168e6dd8a1f181748cb36161bee14a7523b9e5d9b2e3938f02cf24d374e45","src/lib.rs":"338494783dbbcd8abbc7a1b5206f52296064091faa0a04e2d622a0d370ad12fe","src/todolist.udl":"1f8a24049c2340b9184e95facfc191ecdcb91541729ae7f20b4625d67685f13c","tests/bindings/test_todolist.kts":"f3d29b48e0193563fc4f131d91ea697f758174dcdb80ea554f233949e575bf55","tests/bindings/test_todolist.py":"f7430af9347df0daa954d38bc2203ce400affbb9a53fced4bb67a6796afa0664","tests/bindings/test_todolist.rb":"6524b5271a9cc0e2d78ca9f86ccb6973889926688a0843b4505a4f62d48f6dcb","tests/bindings/test_todolist.swift":"d1911b85fe0c8c0b42e5421b5af5d7359c9a65bba477d23560eb4b0f52e80662","tests/test_generated_bindings.rs":"46ef1fbedaac0c4867812ef2632a641eab36ab0ee12f5757567dd037aeddcbd3"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/uniffi-example-todolist/Cargo.toml b/third_party/rust/uniffi-example-todolist/Cargo.toml new file mode 100644 index 0000000000..a6f4895972 --- /dev/null +++ b/third_party/rust/uniffi-example-todolist/Cargo.toml @@ -0,0 +1,40 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "uniffi-example-todolist" +version = "0.22.0" +authors = ["Firefox Sync Team <sync-team@mozilla.com>"] +publish = false +license = "MPL-2.0" + +[lib] +name = "uniffi_todolist" +crate-type = [ + "lib", + "cdylib", +] + +[dependencies] +once_cell = "1.12" +thiserror = "1.0" + +[dependencies.uniffi] +path = "../../uniffi" + +[dev-dependencies.uniffi] +path = "../../uniffi" +features = ["bindgen-tests"] + +[build-dependencies.uniffi] +path = "../../uniffi" +features = ["build"] diff --git a/third_party/rust/uniffi-example-todolist/build.rs b/third_party/rust/uniffi-example-todolist/build.rs new file mode 100644 index 0000000000..7376ae1a20 --- /dev/null +++ b/third_party/rust/uniffi-example-todolist/build.rs @@ -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/todolist.udl").unwrap(); +} diff --git a/third_party/rust/uniffi-example-todolist/src/lib.rs b/third_party/rust/uniffi-example-todolist/src/lib.rs new file mode 100644 index 0000000000..fb2900e615 --- /dev/null +++ b/third_party/rust/uniffi-example-todolist/src/lib.rs @@ -0,0 +1,150 @@ +/* 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, RwLock}; + +use once_cell::sync::Lazy; + +#[derive(Debug, Clone)] +pub struct TodoEntry { + text: String, +} + +// There is a single "default" TodoList that can be shared +// by all consumers of this component. Depending on requirements, +// a real app might like to use a `Weak<>` rather than an `Arc<>` +// here to reduce the risk of circular references. +static DEFAULT_LIST: Lazy<RwLock<Option<Arc<TodoList>>>> = Lazy::new(|| RwLock::new(None)); + +#[derive(Debug, thiserror::Error)] +pub enum TodoError { + #[error("The todo does not exist!")] + TodoDoesNotExist, + #[error("The todolist is empty!")] + EmptyTodoList, + #[error("That todo already exists!")] + DuplicateTodo, + #[error("Empty String error!: {0}")] + EmptyString(String), + #[error("I am a delegated Error: {0}")] + DeligatedError(#[from] std::io::Error), +} + +/// Get a reference to the global default TodoList, if set. +/// +fn get_default_list() -> Option<Arc<TodoList>> { + DEFAULT_LIST.read().unwrap().clone() +} + +/// Set the global default TodoList. +/// +/// This will silently drop any previously set value. +/// +fn set_default_list(list: Arc<TodoList>) { + *DEFAULT_LIST.write().unwrap() = Some(list); +} + +/// Create a new TodoEntry from the given string. +/// +fn create_entry_with<S: Into<String>>(item: S) -> Result<TodoEntry> { + let text = item.into(); + if text.is_empty() { + return Err(TodoError::EmptyString( + "Cannot add empty string as entry".to_string(), + )); + } + Ok(TodoEntry { text }) +} + +type Result<T, E = TodoError> = std::result::Result<T, E>; + +// A simple Todolist. +// UniFFI requires that we use interior mutability for managing mutable state, so we wrap our `Vec` in a RwLock. +// (A Mutex would also work, but a RwLock is more appropriate for this use-case, so we use it). +#[derive(Debug)] +pub struct TodoList { + items: RwLock<Vec<String>>, +} + +impl TodoList { + fn new() -> Self { + Self { + items: RwLock::new(Vec::new()), + } + } + + fn add_item<S: Into<String>>(&self, item: S) -> Result<()> { + let item = item.into(); + if item.is_empty() { + return Err(TodoError::EmptyString( + "Cannot add empty string as item".to_string(), + )); + } + let mut items = self.items.write().unwrap(); + if items.contains(&item) { + return Err(TodoError::DuplicateTodo); + } + items.push(item); + Ok(()) + } + + fn get_last(&self) -> Result<String> { + let items = self.items.read().unwrap(); + items.last().cloned().ok_or(TodoError::EmptyTodoList) + } + + fn get_first(&self) -> Result<String> { + let items = self.items.read().unwrap(); + items.first().cloned().ok_or(TodoError::EmptyTodoList) + } + + fn add_entries(&self, entries: Vec<TodoEntry>) { + let mut items = self.items.write().unwrap(); + items.extend(entries.into_iter().map(|e| e.text)) + } + + fn add_entry(&self, entry: TodoEntry) -> Result<()> { + self.add_item(entry.text) + } + + fn add_items<S: Into<String>>(&self, items: Vec<S>) { + let mut my_items = self.items.write().unwrap(); + my_items.extend(items.into_iter().map(Into::into)) + } + + fn get_items(&self) -> Vec<String> { + let items = self.items.read().unwrap(); + items.clone() + } + + fn get_entries(&self) -> Vec<TodoEntry> { + let items = self.items.read().unwrap(); + items + .iter() + .map(|text| TodoEntry { text: text.clone() }) + .collect() + } + + fn get_last_entry(&self) -> Result<TodoEntry> { + let text = self.get_last()?; + Ok(TodoEntry { text }) + } + + fn clear_item<S: Into<String>>(&self, item: S) -> Result<()> { + let item = item.into(); + let mut items = self.items.write().unwrap(); + let idx = items + .iter() + .position(|s| s == &item) + .ok_or(TodoError::TodoDoesNotExist)?; + items.remove(idx); + Ok(()) + } + + fn make_default(self: Arc<Self>) { + set_default_list(self); + } +} + +include!(concat!(env!("OUT_DIR"), "/todolist.uniffi.rs")); diff --git a/third_party/rust/uniffi-example-todolist/src/todolist.udl b/third_party/rust/uniffi-example-todolist/src/todolist.udl new file mode 100644 index 0000000000..5c923314cd --- /dev/null +++ b/third_party/rust/uniffi-example-todolist/src/todolist.udl @@ -0,0 +1,38 @@ +namespace todolist { + TodoList? get_default_list(); + undefined set_default_list(TodoList list); + + [Throws=TodoError] + TodoEntry create_entry_with(string todo); +}; + +dictionary TodoEntry { + string text; +}; + +[Error] +enum TodoError { + "TodoDoesNotExist", "EmptyTodoList", "DuplicateTodo", "EmptyString", "DeligatedError" +}; + +interface TodoList { + constructor(); + [Throws=TodoError] + void add_item(string todo); + [Throws=TodoError] + void add_entry(TodoEntry entry); + sequence<TodoEntry> get_entries(); + sequence<string> get_items(); + void add_entries(sequence<TodoEntry> entries); + void add_items(sequence<string> items); + [Throws=TodoError] + TodoEntry get_last_entry(); + [Throws=TodoError] + string get_last(); + [Throws=TodoError] + string get_first(); + [Throws=TodoError] + void clear_item(string todo); + [Self=ByArc] + undefined make_default(); +}; diff --git a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.kts b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.kts new file mode 100644 index 0000000000..bb2b292224 --- /dev/null +++ b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.kts @@ -0,0 +1,83 @@ +import uniffi.todolist.* + +val todo = TodoList() + +// This throws an exception: +try { + todo.getLast() + throw RuntimeException("Should have thrown a TodoError!") +} catch (e: TodoException.EmptyTodoList) { + // It's okay, we don't have any items yet! +} + +try { + createEntryWith("") + throw RuntimeException("Should have thrown a TodoError!") +} catch (e: TodoException) { + // It's okay, the string was empty! + assert(e is TodoException.EmptyString) + assert(e !is TodoException.EmptyTodoList) +} + +todo.addItem("Write strings support") + +assert(todo.getLast() == "Write strings support") + +todo.addItem("Write tests for strings support") + +assert(todo.getLast() == "Write tests for strings support") + +val entry = createEntryWith("Write bindings for strings as record members") + +todo.addEntry(entry) +assert(todo.getLast() == "Write bindings for strings as record members") +assert(todo.getLastEntry().text == "Write bindings for strings as record members") + +todo.addItem("Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣") +assert(todo.getLast() == "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣") + +val entry2 = TodoEntry("Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣") +todo.addEntry(entry2) +assert(todo.getLastEntry().text == "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣") + +assert(todo.getEntries().size == 5) + +todo.addEntries(listOf(TodoEntry("foo"), TodoEntry("bar"))) +assert(todo.getEntries().size == 7) +assert(todo.getLastEntry().text == "bar") + +todo.addItems(listOf("bobo", "fofo")) +assert(todo.getItems().size == 9) +assert(todo.getItems()[7] == "bobo") + +assert(getDefaultList() == null) + +// Note that each individual object instance needs to be explicitly destroyed, +// either by using the `.use` helper or explicitly calling its `.destroy` method. +// Failure to do so will leak the underlying Rust object. +TodoList().use { todo2 -> + setDefaultList(todo) + getDefaultList()!!.use { default -> + assert(todo.getEntries() == default.getEntries()) + assert(todo2.getEntries() != default.getEntries()) + } + + todo2.makeDefault() + getDefaultList()!!.use { default -> + assert(todo.getEntries() != default.getEntries()) + assert(todo2.getEntries() == default.getEntries()) + } + + todo.addItem("Test liveness after being demoted from default") + assert(todo.getLast() == "Test liveness after being demoted from default") + + todo2.addItem("Test shared state through local vs default reference") + getDefaultList()!!.use { default -> + assert(default.getLast() == "Test shared state through local vs default reference") + } +} + +// Ensure the kotlin version of deinit doesn't crash, and is idempotent. +todo.destroy() +todo.destroy() + diff --git a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py new file mode 100644 index 0000000000..017e999fb2 --- /dev/null +++ b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py @@ -0,0 +1,44 @@ +from todolist import * + +todo = TodoList() + +entry = TodoEntry("Write bindings for strings in records") + +todo.add_item("Write python bindings") + +assert(todo.get_last() == "Write python bindings") + +todo.add_item("Write tests for bindings") + +assert(todo.get_last() == "Write tests for bindings") + +todo.add_entry(entry) + +assert(todo.get_last() == "Write bindings for strings in records") +assert(todo.get_last_entry().text == "Write bindings for strings in records") + +todo.add_item("Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣") +assert(todo.get_last() == "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣") + +entry2 = TodoEntry("Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣") +todo.add_entry(entry2) +assert(todo.get_last_entry().text == "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣") + +todo2 = TodoList() +assert(todo != todo2) +assert(todo is not todo2) + +assert(get_default_list() is None) + +set_default_list(todo) +assert(todo.get_items() == get_default_list().get_items()) + +todo2.make_default() +assert(todo2.get_items() == get_default_list().get_items()) + +todo.add_item("Test liveness after being demoted from default") +assert(todo.get_last() == "Test liveness after being demoted from default") + +todo2.add_item("Test shared state through local vs default reference") +assert(get_default_list().get_last() == "Test shared state through local vs default reference") + diff --git a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb new file mode 100644 index 0000000000..d9e04f92e7 --- /dev/null +++ b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'test/unit' +require 'todolist' + +include Test::Unit::Assertions +include Todolist + +todo = TodoList.new +entry = TodoEntry.new 'Write bindings for strings in records' + +todo.add_item('Write ruby bindings') + +assert_equal todo.get_last, 'Write ruby bindings' + +todo.add_item('Write tests for bindings') + +assert_equal todo.get_last, 'Write tests for bindings' + +todo.add_entry(entry) + +assert_equal todo.get_last, 'Write bindings for strings in records' +assert_equal todo.get_last_entry.text, 'Write bindings for strings in records' + +todo.add_item("Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣") +assert_equal todo.get_last, "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣" + +entry2 = TodoEntry.new("Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣") +todo.add_entry(entry2) +assert_equal todo.get_last_entry.text, "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣" + +todo2 = TodoList.new +assert todo2.get_items != todo.get_items + +assert Todolist.get_default_list == nil + +Todolist.set_default_list todo +assert todo.get_items == Todolist.get_default_list.get_items + +todo2.make_default +assert todo2.get_items == Todolist.get_default_list.get_items + +todo.add_item "Test liveness after being demoted from default" +assert todo.get_last == "Test liveness after being demoted from default" + +todo2.add_item "Test shared state through local vs default reference" +assert Todolist.get_default_list.get_last == "Test shared state through local vs default reference"
\ No newline at end of file diff --git a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.swift b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.swift new file mode 100644 index 0000000000..6ce72cadb2 --- /dev/null +++ b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.swift @@ -0,0 +1,69 @@ +import todolist + + +let todo = TodoList() +do { + let _ = try todo.getLast() + fatalError("Should have thrown an EmptyTodoList error!") +} catch TodoError.EmptyTodoList{ + //It's okay! There are not todos! +} +try! todo.addItem(todo: "Write swift bindings") +assert( try! todo.getLast() == "Write swift bindings") + +try! todo.addItem(todo: "Write tests for bindings") +assert(try! todo.getLast() == "Write tests for bindings") + +let entry = TodoEntry(text: "Write bindings for strings as record members") +try! todo.addEntry(entry: entry) +assert(try! todo.getLast() == "Write bindings for strings as record members") + +try! todo.addItem(todo: "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣") +assert(try! todo.getLast() == "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣") + +do { + let _ = try createEntryWith(todo: "") + fatalError("Should have thrown an EmptyString error!") +} catch TodoError.EmptyString { + // It's okay! It was an empty string +} + +let entry2 = TodoEntry(text: "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣") +try! todo.addEntry(entry: entry2) +assert(try! todo.getLastEntry() == entry2) + +assert(todo.getEntries().count == 5) + +todo.addEntries(entries: [TodoEntry(text: "foo"), TodoEntry(text: "bar")]) +assert(todo.getEntries().count == 7) +assert(todo.getItems().count == 7) +assert(try! todo.getLast() == "bar") + +todo.addItems(items: ["bobo", "fofo"]) +assert(todo.getItems().count == 9) +assert(todo.getItems()[7] == "bobo") + +// Ensure deinit doesn't crash. +for _ in 0..<10 { + let list = TodoList() + try! list.addItem(todo: "todo") +} + +let todo2 = TodoList() + +assert(getDefaultList() == nil) + +setDefaultList(list: todo) +assert(todo.getItems() == getDefaultList()!.getItems()) +assert(todo2.getItems() != getDefaultList()!.getItems()) + +todo2.makeDefault() +assert(todo.getItems() != getDefaultList()!.getItems()) +assert(todo2.getItems() == getDefaultList()!.getItems()) + +try! todo.addItem(todo: "Test liveness after being demoted from default") +assert(try! todo.getLast() == "Test liveness after being demoted from default") + +try! todo2.addItem(todo: "Test shared state through local vs default reference") +assert(try! getDefaultList()!.getLast() == "Test shared state through local vs default reference") + diff --git a/third_party/rust/uniffi-example-todolist/tests/test_generated_bindings.rs b/third_party/rust/uniffi-example-todolist/tests/test_generated_bindings.rs new file mode 100644 index 0000000000..cefdbfe1dc --- /dev/null +++ b/third_party/rust/uniffi-example-todolist/tests/test_generated_bindings.rs @@ -0,0 +1,6 @@ +uniffi::build_foreign_language_testcases!( + "tests/bindings/test_todolist.kts", + "tests/bindings/test_todolist.swift", + "tests/bindings/test_todolist.rb", + "tests/bindings/test_todolist.py" +); |