diff --git a/crux_http/Cargo.toml b/crux_http/Cargo.toml index 419fbb6c5..c2f6e3d1b 100644 --- a/crux_http/Cargo.toml +++ b/crux_http/Cargo.toml @@ -14,6 +14,7 @@ rust-version.workspace = true default = ["encoding"] # requires web-sys for TextDecoder on wasm encoding = ["encoding_rs", "web-sys"] +typegen = [] [dependencies] anyhow.workspace = true diff --git a/crux_http/README.md b/crux_http/README.md index d8d2c4bcb..af602565a 100644 --- a/crux_http/README.md +++ b/crux_http/README.md @@ -3,29 +3,62 @@ [![Crate version](https://img.shields.io/crates/v/crux_http.svg)](https://crates.io/crates/crux_http) [![Docs](https://img.shields.io/badge/docs.rs-crux_http-green)](https://docs.rs/crux_http/) -This crate contains the `Http` capability, which can be used to ask the Shell to make an HTTP request. +This crate contains the `Http` capability, which can be used to ask the Shell to +make an HTTP request. -For an example of how to use the capability, see the [integration test](./tests/with_shell.rs). +For an example of how to use the capability, see the +[integration test](./tests/with_shell.rs). -The code for this was largely copied from [`surf`](https://github.com/http-rs/surf) with some modifications made to fit into the crux paradigm. +The code for this was largely copied from +[`surf`](https://github.com/http-rs/surf) with some modifications made to fit +into the crux paradigm. ## Getting Started -Add `crux_http` as a dependency. +Add `crux_http` as a dependency in your app's `Cargo.toml`. -Additionally, if using crux type generation, include this line in your shared -types generation script (e.g. `shared_types/build.rs`): +### Typegen -```rust -gen.register_type::()?; +This crate has a feature called `typegen` which supports generation of code +(e.g. in TypeScript, Swift, Kotlin etc.) for the types that the Capability +passes over the bridge. + +Crux apps usually contain a `shared` crate for the behavioural "core" and a +`shared_types` crate that is responsible for generating the types that are +shared between the core and the shell. + +The `shared` crate can re-export the capability with a `typegen` feature that +depends on the `typegen` feature of the Capability crate. This way, the shared +crate can ask the Capability to register its types for type generation. + +e.g. in the `shared` crate's `Cargo.toml`: + +```toml +[features] +typegen = ["crux_core/typegen", "crux_http/typegen"] +``` + +and in the `shared_types` crate's `Cargo.toml`: + +```toml +[build-dependencies] +crux_core = { workspace = true, features = ["typegen"] } +shared = { path = "../shared", features = ["typegen"] } ``` ## About Crux Capabilities -Crux capabilities teach Crux how to interact with the shell when performing side effects. They do the following: +Crux capabilities teach Crux how to interact with the shell when performing side +effects. They do the following: -1. define a `Request` struct to instruct the Shell how to perform the side effect on behalf of the Core -1. define a `Response` struct to hold the data returned by the Shell after the side effect has completed -1. declare one or more convenience methods for invoking the Shell's capability, each of which creates a `Command` (describing the effect and its continuation) that Crux can "execute" +1. define a `Request` struct to instruct the Shell how to perform the side + effect on behalf of the Core +1. define a `Response` struct to hold the data returned by the Shell after the + side effect has completed +1. declare one or more convenience methods for invoking the Shell's capability, + each of which creates a `Command` (describing the effect and its + continuation) that Crux can "execute" -> Note that because Swift has no namespacing, there is currently a requirement to ensure that `Request` and `Response` are unambiguously named (e.g. `HttpRequest` and `HttpResponse`). +> Note that because Swift has no namespacing, there is currently a requirement +> to ensure that `Request` and `Response` are unambiguously named (e.g. +> `HttpRequest` and `HttpResponse`). diff --git a/crux_http/src/lib.rs b/crux_http/src/lib.rs index 4c022bfd0..7364d837a 100644 --- a/crux_http/src/lib.rs +++ b/crux_http/src/lib.rs @@ -6,7 +6,6 @@ // #![warn(missing_docs)] use crux_core::capability::CapabilityContext; -use crux_core::macros::Capability; use http::Method; use url::Url; @@ -37,12 +36,35 @@ use client::Client; pub type Result = std::result::Result; /// The Http capability API. -#[derive(Capability)] pub struct Http { context: CapabilityContext, client: Client, } +impl crux_core::Capability for Http { + type Operation = protocol::HttpRequest; + + type MappedSelf = Http; + + fn map_event(&self, f: F) -> Self::MappedSelf + where + F: Fn(NewEv) -> Ev + Send + Sync + 'static, + Ev: 'static, + NewEv: 'static + Send, + { + Http::new(self.context.map_event(f)) + } + + #[cfg(feature = "typegen")] + fn register_types(generator: &mut crux_core::typegen::TypeGen) -> crux_core::typegen::Result { + generator.register_type::()?; + generator.register_type::()?; + generator + .register_type::<::Output>()?; + Ok(()) + } +} + impl Clone for Http { fn clone(&self) -> Self { Self { diff --git a/crux_http/src/protocol.rs b/crux_http/src/protocol.rs index b4238a27f..666d15695 100644 --- a/crux_http/src/protocol.rs +++ b/crux_http/src/protocol.rs @@ -41,7 +41,7 @@ impl std::fmt::Debug for HttpRequest { } else { format!("", self.body.len()) }; - let mut builder = f.debug_struct("HttpReqeuest"); + let mut builder = f.debug_struct("HttpRequest"); builder .field("method", &self.method) .field("url", &self.url); @@ -303,7 +303,7 @@ mod tests { let repr = format!("{req:?}"); assert_eq!( repr, - r#"HttpReqeuest { method: "POST", url: "http://example.com", headers: [HttpHeader { name: "foo", value: "bar" }], body: "hello world!" }"# + r#"HttpRequest { method: "POST", url: "http://example.com", headers: [HttpHeader { name: "foo", value: "bar" }], body: "hello world!" }"# ); } @@ -316,7 +316,7 @@ mod tests { let repr = format!("{req:?}"); assert_eq!( repr, - r#"HttpReqeuest { method: "POST", url: "http://example.com", body: "abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstu😀😀"... }"# + r#"HttpRequest { method: "POST", url: "http://example.com", body: "abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstu😀😀"... }"# ); } @@ -328,7 +328,7 @@ mod tests { let repr = format!("{req:?}"); assert_eq!( repr, - r#"HttpReqeuest { method: "POST", url: "http://example.com", body: }"# + r#"HttpRequest { method: "POST", url: "http://example.com", body: }"# ); } } diff --git a/crux_kv/Cargo.toml b/crux_kv/Cargo.toml index 7449990fe..be97b8adb 100644 --- a/crux_kv/Cargo.toml +++ b/crux_kv/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true keywords.workspace = true rust-version.workspace = true +[features] +typegen = [] + [dependencies] anyhow.workspace = true crux_core = { version = "0.8.0", path = "../crux_core" } diff --git a/crux_kv/README.md b/crux_kv/README.md index 2efe03cde..41130363c 100644 --- a/crux_kv/README.md +++ b/crux_kv/README.md @@ -1,15 +1,57 @@ # Crux Key-Value Store capability -This crate contains the `KeyValue` capability, which can be used to ask the Shell to read from, and write to, a key-value store. +This crate contains the `KeyValue` capability, which can be used to ask the +Shell to read from, and write to, a key-value store. -Currently it provides an interface for getting, setting, and deleting keys, checking if keys exists in the store, and listing keys that start with a prefix. +Currently it provides an interface for getting, setting, and deleting keys, +checking if keys exists in the store, and listing keys that start with a prefix. + +## Getting Started + +Add `crux_kv` as a dependency in your app's `Cargo.toml`. + +### Typegen + +This crate has a feature called `typegen` which supports generation of code +(e.g. in TypeScript, Swift, Kotlin etc.) for the types that the Capability +passes over the bridge. + +Crux apps usually contain a `shared` crate for the behavioural "core" and a +`shared_types` crate that is responsible for generating the types that are +shared between the core and the shell. + +The `shared` crate can re-export the capability with a `typegen` feature that +depends on the `typegen` feature of the Capability crate. This way, the shared +crate can ask the Capability to register its types for type generation. + +e.g. in the `shared` crate's `Cargo.toml`: + +```toml +[features] +typegen = ["crux_core/typegen", "crux_kv/typegen"] +``` + +and in the `shared_types` crate's `Cargo.toml`: + +```toml +[build-dependencies] +crux_core = { workspace = true, features = ["typegen"] } +shared = { path = "../shared", features = ["typegen"] } +``` ## About Crux Capabilities -Crux capabilities teach Crux how to interact with the shell when performing side effects. They do the following: +Crux capabilities teach Crux how to interact with the shell when performing side +effects. They do the following: -1. define a `Request` struct to instruct the Shell how to perform the side effect on behalf of the Core -1. define a `Response` struct to hold the data returned by the Shell after the side effect has completed -1. declare one or more convenience methods for invoking the Shell's capability, each of which creates a `Command` (describing the effect and its continuation) that Crux can "execute" +1. define a `Request` struct to instruct the Shell how to perform the side + effect on behalf of the Core +1. define a `Response` struct to hold the data returned by the Shell after the + side effect has completed +1. declare one or more convenience methods for invoking the Shell's capability, + each of which creates a `Command` (describing the effect and its + continuation) that Crux can "execute" -> Note that because Swift has no namespacing, there is currently a requirement to ensure that `Request` and `Response` are unambiguously named (e.g. `HttpRequest` and `HttpResponse`). +> Note that because Swift has no namespacing, there is currently a requirement +> to ensure that `Request` and `Response` are unambiguously named (e.g. +> `HttpRequest` and `HttpResponse`). diff --git a/crux_kv/src/lib.rs b/crux_kv/src/lib.rs index c632da86a..06fa03a22 100644 --- a/crux_kv/src/lib.rs +++ b/crux_kv/src/lib.rs @@ -9,7 +9,6 @@ pub mod value; use serde::{Deserialize, Serialize}; use crux_core::capability::{CapabilityContext, Operation}; -use crux_core::macros::Capability; use error::KeyValueError; use value::Value; @@ -116,11 +115,35 @@ impl Operation for KeyValueOperation { type Output = KeyValueResult; } -#[derive(Capability)] pub struct KeyValue { context: CapabilityContext, } +impl crux_core::Capability for KeyValue { + type Operation = KeyValueOperation; + + type MappedSelf = KeyValue; + + fn map_event(&self, f: F) -> Self::MappedSelf + where + F: Fn(NewEv) -> Ev + Send + Sync + 'static, + Ev: 'static, + NewEv: 'static + Send, + { + KeyValue::new(self.context.map_event(f)) + } + + #[cfg(feature = "typegen")] + fn register_types(generator: &mut crux_core::typegen::TypeGen) -> crux_core::typegen::Result { + generator.register_type::()?; + generator.register_type::()?; + generator.register_type::()?; + generator.register_type::()?; + generator.register_type::<::Output>()?; + Ok(()) + } +} + impl Clone for KeyValue { fn clone(&self) -> Self { Self { diff --git a/crux_macros/src/capability.rs b/crux_macros/src/capability.rs index e2a135912..c371613e2 100644 --- a/crux_macros/src/capability.rs +++ b/crux_macros/src/capability.rs @@ -40,7 +40,7 @@ impl ToTokens for CapabilityStructReceiver { where F: Fn(NewEv) -> Ev + Send + Sync + 'static, Ev: 'static, - NewEv: 'static, + NewEv: 'static + Send, { #name::new(self.context.map_event(f)) } @@ -117,7 +117,7 @@ mod tests { where F: Fn(NewEv) -> Ev + Send + Sync + 'static, Ev: 'static, - NewEv: 'static, + NewEv: 'static + Send, { Render::new(self.context.map_event(f)) } diff --git a/crux_macros/src/export.rs b/crux_macros/src/export.rs index 4bacf453c..2d9735037 100644 --- a/crux_macros/src/export.rs +++ b/crux_macros/src/export.rs @@ -52,13 +52,12 @@ impl ToTokens for ExportStructReceiver { } tokens.extend(quote! { - use ::crux_core::capability::Capability; impl ::crux_core::typegen::Export for #ident { #[cfg(feature = "typegen")] fn register_types(generator: &mut ::crux_core::typegen::TypeGen) -> ::crux_core::typegen::Result { + use ::crux_core::capability::Capability; #(#output_type_exports)* - generator.register_type::<#ffi_export_name>()?; generator.register_type::<::crux_core::bridge::Request<#ffi_export_name>>()?; @@ -130,12 +129,12 @@ mod tests { let actual = quote!(#input); insta::assert_snapshot!(pretty_print(&actual), @r###" - use ::crux_core::capability::Capability; impl ::crux_core::typegen::Export for Capabilities { #[cfg(feature = "typegen")] fn register_types( generator: &mut ::crux_core::typegen::TypeGen, ) -> ::crux_core::typegen::Result { + use ::crux_core::capability::Capability; Render::::register_types(generator)?; generator.register_type::()?; generator.register_type::<::crux_core::bridge::Request>()?; @@ -178,12 +177,12 @@ mod tests { let actual = quote!(#input); insta::assert_snapshot!(pretty_print(&actual), @r###" - use ::crux_core::capability::Capability; impl ::crux_core::typegen::Export for MyCapabilities { #[cfg(feature = "typegen")] fn register_types( generator: &mut ::crux_core::typegen::TypeGen, ) -> ::crux_core::typegen::Result { + use ::crux_core::capability::Capability; crux_http::Http::::register_types(generator)?; KeyValue::::register_types(generator)?; Platform::::register_types(generator)?; @@ -214,12 +213,12 @@ mod tests { let actual = quote!(#input); insta::assert_snapshot!(pretty_print(&actual), @r###" - use ::crux_core::capability::Capability; impl ::crux_core::typegen::Export for MyCapabilities { #[cfg(feature = "typegen")] fn register_types( generator: &mut ::crux_core::typegen::TypeGen, ) -> ::crux_core::typegen::Result { + use ::crux_core::capability::Capability; crux_http::Http::::register_types(generator)?; KeyValue::::register_types(generator)?; Platform::::register_types(generator)?; @@ -249,12 +248,12 @@ mod tests { let actual = quote!(#input); insta::assert_snapshot!(pretty_print(&actual), @r###" - use ::crux_core::capability::Capability; impl ::crux_core::typegen::Export for Capabilities { #[cfg(feature = "typegen")] fn register_types( generator: &mut ::crux_core::typegen::TypeGen, ) -> ::crux_core::typegen::Result { + use ::crux_core::capability::Capability; Render::::register_types(generator)?; generator.register_type::()?; generator.register_type::<::crux_core::bridge::Request>()?; diff --git a/docs/src/getting_started/Android/android.md b/docs/src/getting_started/Android/android.md index a7243cf77..9b7a7b454 100644 --- a/docs/src/getting_started/Android/android.md +++ b/docs/src/getting_started/Android/android.md @@ -118,7 +118,7 @@ this: ```admonish The code fence above uses AGP version `8.3.2`. -Currently there is an incompatibiliity between the latest version (`8.4.0`) of the Android Gradle Plugin (AGP) and the Rust Gradle Plugin, which fails with a `duplicate resources` issue when building your shared library for multiple targets. For now, either just target one archtecture or stick with AGP version `8.3.2` until this is resolved. +Currently there is an incompatibiliity between the latest version (`8.4.0`) of the Android Gradle Plugin (AGP) and the Rust Gradle Plugin, which fails with a `duplicate resources` issue when building your shared library for multiple targets. For now, either just target one architecture or stick with AGP version `8.3.2` until this is resolved. ``` Edit the **library**'s `build.gradle` (`/Android/shared/build.gradle`) to look @@ -130,7 +130,7 @@ like this: ``` ```admonish warning title="Sharp edge" -You will need to set the `ndkVersion` to one you have installed, go to "**Tools, SDK Manager, SDK Tooks**" and check "**Show Package Details**" to get your installed version, or to install the version matching `build.gradle` above. +You will need to set the `ndkVersion` to one you have installed, go to "**Tools, SDK Manager, SDK Tools**" and check "**Show Package Details**" to get your installed version, or to install the version matching `build.gradle` above. ``` ```admonish tip diff --git a/docs/src/getting_started/core.md b/docs/src/getting_started/core.md index 0c237c6cc..a170c117c 100644 --- a/docs/src/getting_started/core.md +++ b/docs/src/getting_started/core.md @@ -247,6 +247,17 @@ pub struct Capabilities { } ``` +Additionally, if you are using a Capability that does not need to be exported to the foreign language, you can use the `#[effect(skip)]` attribute to skip exporting it, e.g.: + +```rust,ignore +#[cfg_attr(feature = "typegen", derive(Export))] +#[derive(Effect)] +pub struct Capabilities { + render: Render, + #[effect(skip)] + compose: Compose, +} +``` ```` - Make sure everything builds and foreign types get generated into the diff --git a/examples/bridge_echo/Cargo.toml b/examples/bridge_echo/Cargo.toml index aff3f4e2f..d9eedd0b8 100644 --- a/examples/bridge_echo/Cargo.toml +++ b/examples/bridge_echo/Cargo.toml @@ -13,7 +13,6 @@ rust-version = "1.66" [workspace.dependencies] anyhow = "1.0.86" crux_core = "0.8" -# crux_core = { path = "../../crux_core" } serde = "1.0.203" [workspace.metadata.bin] diff --git a/examples/cat_facts/Cargo.toml b/examples/cat_facts/Cargo.toml index e55a3070d..53b71e655 100644 --- a/examples/cat_facts/Cargo.toml +++ b/examples/cat_facts/Cargo.toml @@ -17,11 +17,6 @@ crux_http = "0.9" crux_kv = "0.4" crux_platform = "0.1" crux_time = { version = "0.4", features = ["chrono"] } -# crux_core = { path = "../../crux_core" } -# crux_http = { path = "../../crux_http" } -# crux_kv = { path = "../../crux_kv" } -# crux_platform = { path = "../../crux_platform" } -# crux_time = { path = "../../crux_time", features = ["chrono"] } serde = "1.0.203" [workspace.metadata.bin] diff --git a/examples/cat_facts/shared_types/build.rs b/examples/cat_facts/shared_types/build.rs index a33174e23..5259a3c3b 100644 --- a/examples/cat_facts/shared_types/build.rs +++ b/examples/cat_facts/shared_types/build.rs @@ -16,9 +16,11 @@ fn main() -> anyhow::Result<()> { gen.register_app::()?; // types from `crux_http` that aren't automatically discovered + // NOTE: in the next version of `crux_http`, these will be automatically registered gen.register_type::()?; // types from `crux_kv` that aren't automatically discovered + // NOTE: in the next version of `crux_kv`, these will be automatically registered gen.register_type::()?; gen.register_type::()?; gen.register_type::()?; diff --git a/examples/counter/Cargo.toml b/examples/counter/Cargo.toml index 3cabeb59e..2f9d62a19 100644 --- a/examples/counter/Cargo.toml +++ b/examples/counter/Cargo.toml @@ -22,8 +22,6 @@ rust-version = "1.66" anyhow = "1.0.86" crux_core = "0.8" crux_http = "0.9" -# crux_core = { path = "../../crux_core" } -# crux_http = { path = "../../crux_http" } serde = "1.0.203" [workspace.metadata.bin] diff --git a/examples/counter/shared_types/build.rs b/examples/counter/shared_types/build.rs index 96c97fc4c..484b0c069 100644 --- a/examples/counter/shared_types/build.rs +++ b/examples/counter/shared_types/build.rs @@ -8,6 +8,9 @@ fn main() -> anyhow::Result<()> { let mut gen = TypeGen::new(); gen.register_app::()?; + + // Register the HttpError type + // NOTE: in the next version of crux_http, this will be done automatically gen.register_type::()?; let output_root = PathBuf::from("./generated"); diff --git a/examples/counter/tauri/src-tauri/Cargo.toml b/examples/counter/tauri/src-tauri/Cargo.toml index 2c300cbec..ce73161c6 100644 --- a/examples/counter/tauri/src-tauri/Cargo.toml +++ b/examples/counter/tauri/src-tauri/Cargo.toml @@ -28,3 +28,9 @@ tauri = { version = "1.6", features = ["shell-open"] } # this feature is used for production builds or when `devPath` points to the filesystem # DO NOT REMOVE!! custom-protocol = ["tauri/custom-protocol"] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(mobile)', + 'cfg(desktop)', +] } diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index 6305ef73a..2e8f8e52b 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -13,7 +13,6 @@ rust-version = "1.66" [workspace.dependencies] anyhow = "1.0.86" crux_core = "0.8" -# crux_core = { path = "../../crux_core" } serde = "1.0.203" [workspace.metadata.bin] diff --git a/examples/notes/Cargo.toml b/examples/notes/Cargo.toml index 2b2fa3525..96b24820f 100644 --- a/examples/notes/Cargo.toml +++ b/examples/notes/Cargo.toml @@ -14,8 +14,6 @@ rust-version = "1.66" anyhow = "1.0" crux_core = "0.8" crux_kv = "0.4" -# crux_core = { path = "../../crux_core" } -# crux_kv = { path = "../../crux_kv" } serde = "1.0" [workspace.metadata.bin] diff --git a/examples/notes/shared_types/build.rs b/examples/notes/shared_types/build.rs index e7d57607b..dbd57ba3f 100644 --- a/examples/notes/shared_types/build.rs +++ b/examples/notes/shared_types/build.rs @@ -13,6 +13,9 @@ fn main() -> anyhow::Result<()> { // Note: currently required as we can't find enums inside enums, see: // https://github.com/zefchain/serde-reflection/tree/main/serde-reflection#supported-features gen.register_type::()?; + + // Register types from crux_kv + // NOTE: in the next version of crux_kv, this will not be necessary gen.register_type::()?; gen.register_type::()?; gen.register_type::()?; diff --git a/examples/simple_counter/Cargo.toml b/examples/simple_counter/Cargo.toml index a7e4f5afb..4feae9db0 100644 --- a/examples/simple_counter/Cargo.toml +++ b/examples/simple_counter/Cargo.toml @@ -13,7 +13,6 @@ rust-version = "1.66" [workspace.dependencies] anyhow = "1.0.86" crux_core = "0.8" -# crux_core = { path = "../../crux_core" } serde = "1.0.203" [workspace.metadata.bin] diff --git a/examples/tap_to_pay/Cargo.toml b/examples/tap_to_pay/Cargo.toml index b405cc62d..30a3defea 100644 --- a/examples/tap_to_pay/Cargo.toml +++ b/examples/tap_to_pay/Cargo.toml @@ -11,7 +11,6 @@ rust-version = "1.68" [workspace.dependencies] anyhow = "1.0.86" crux_core = "0.8" -# crux_core = { path = "../../crux_core" } serde = "1.0.203" [workspace.metadata.bin]