Skip to content

Commit

Permalink
Merge pull request #260 from redbadger/push-wvqmnkkprpyz
Browse files Browse the repository at this point in the history
crux_http and crux_kv now register their additional types
  • Loading branch information
StuartHarris authored Jul 26, 2024
2 parents f283be6 + ce12b19 commit ed80cdd
Show file tree
Hide file tree
Showing 22 changed files with 186 additions and 51 deletions.
1 change: 1 addition & 0 deletions crux_http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
59 changes: 46 additions & 13 deletions crux_http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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::<HttpError>()?;
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`).
26 changes: 24 additions & 2 deletions crux_http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
// #![warn(missing_docs)]

use crux_core::capability::CapabilityContext;
use crux_core::macros::Capability;
use http::Method;
use url::Url;

Expand Down Expand Up @@ -37,12 +36,35 @@ use client::Client;
pub type Result<T> = std::result::Result<T, HttpError>;

/// The Http capability API.
#[derive(Capability)]
pub struct Http<Ev> {
context: CapabilityContext<protocol::HttpRequest, Ev>,
client: Client,
}

impl<Ev> crux_core::Capability<Ev> for Http<Ev> {
type Operation = protocol::HttpRequest;

type MappedSelf<MappedEv> = Http<MappedEv>;

fn map_event<F, NewEv>(&self, f: F) -> Self::MappedSelf<NewEv>
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::<HttpError>()?;
generator.register_type::<Self::Operation>()?;
generator
.register_type::<<Self::Operation as crux_core::capability::Operation>::Output>()?;
Ok(())
}
}

impl<Ev> Clone for Http<Ev> {
fn clone(&self) -> Self {
Self {
Expand Down
8 changes: 4 additions & 4 deletions crux_http/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl std::fmt::Debug for HttpRequest {
} else {
format!("<binary data - {} bytes>", 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);
Expand Down Expand Up @@ -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!" }"#
);
}

Expand All @@ -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😀😀"... }"#
);
}

Expand All @@ -328,7 +328,7 @@ mod tests {
let repr = format!("{req:?}");
assert_eq!(
repr,
r#"HttpReqeuest { method: "POST", url: "http://example.com", body: <binary data - 4 bytes> }"#
r#"HttpRequest { method: "POST", url: "http://example.com", body: <binary data - 4 bytes> }"#
);
}
}
Expand Down
3 changes: 3 additions & 0 deletions crux_kv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
56 changes: 49 additions & 7 deletions crux_kv/README.md
Original file line number Diff line number Diff line change
@@ -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`).
27 changes: 25 additions & 2 deletions crux_kv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -116,11 +115,35 @@ impl Operation for KeyValueOperation {
type Output = KeyValueResult;
}

#[derive(Capability)]
pub struct KeyValue<Ev> {
context: CapabilityContext<KeyValueOperation, Ev>,
}

impl<Ev> crux_core::Capability<Ev> for KeyValue<Ev> {
type Operation = KeyValueOperation;

type MappedSelf<MappedEv> = KeyValue<MappedEv>;

fn map_event<F, NewEv>(&self, f: F) -> Self::MappedSelf<NewEv>
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::<KeyValueResponse>()?;
generator.register_type::<KeyValueError>()?;
generator.register_type::<Value>()?;
generator.register_type::<Self::Operation>()?;
generator.register_type::<<Self::Operation as Operation>::Output>()?;
Ok(())
}
}

impl<Ev> Clone for KeyValue<Ev> {
fn clone(&self) -> Self {
Self {
Expand Down
4 changes: 2 additions & 2 deletions crux_macros/src/capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down Expand Up @@ -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))
}
Expand Down
11 changes: 5 additions & 6 deletions crux_macros/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>()?;

Expand Down Expand Up @@ -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::<Event>::register_types(generator)?;
generator.register_type::<EffectFfi>()?;
generator.register_type::<::crux_core::bridge::Request<EffectFfi>>()?;
Expand Down Expand Up @@ -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::<MyEvent>::register_types(generator)?;
KeyValue::<MyEvent>::register_types(generator)?;
Platform::<MyEvent>::register_types(generator)?;
Expand Down Expand Up @@ -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::<MyEvent>::register_types(generator)?;
KeyValue::<MyEvent>::register_types(generator)?;
Platform::<MyEvent>::register_types(generator)?;
Expand Down Expand Up @@ -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::<Event>::register_types(generator)?;
generator.register_type::<MyEffectFfi>()?;
generator.register_type::<::crux_core::bridge::Request<MyEffectFfi>>()?;
Expand Down
4 changes: 2 additions & 2 deletions docs/src/getting_started/Android/android.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading

0 comments on commit ed80cdd

Please sign in to comment.