Skip to content

Commit

Permalink
Make Command examples look more like they will IRL
Browse files Browse the repository at this point in the history
  • Loading branch information
charypar committed Jan 30, 2025
1 parent 3ae256e commit f2c5930
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 32 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 56 additions & 32 deletions crux_core/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@
//! enabling, for example, wrapping Commands in one another.
//!
//! # Examples
//! ----
//! 1. Using `Command`s in an app's update function,
//! to request an HTTP POST and render the UI
//!
//! Commands are typically created by a capability and returned from the update function. Capabilities
//! normally return a builder, which can be used in both sync and async context. The basic sync use
//! is to bind th command to an Event which will be sent with the result of the command:
//!
//! ```
//!# use url::Url;
//!# use crux_core::{Command, render};
//!# use crux_http::command::Http;
//!# const API_URL: &str = "";
//!# const API_URL: &str = "https://example.com/";
//!# pub enum Event { Increment, Set(crux_http::Result<crux_http::Response<usize>>) }
//!# #[derive(crux_core::macros::Effect)]
//!# pub struct Capabilities {
Expand All @@ -36,7 +37,6 @@
//!# }
//!# #[derive(Default)] pub struct Model { count: usize }
//!# #[derive(Default)] pub struct App;
//!#
//!# impl crux_core::App for App {
//!# type Event = Event;
//!# type Model = Model;
Expand All @@ -57,8 +57,8 @@
//!
//! Http::post(url)
//! .expect_json()
//! .build()
//! .then_send(Event::Set)
//! .build() // creates a RequestBuilder
//! .then_send(Event::Set) // creates a Command
//! }
//! Event::Set(Ok(mut response)) => {
//! let count = response.take_body().unwrap();
Expand All @@ -73,42 +73,64 @@
//!# }
//!# }
//! ```
//! ----
//! 2. Chaining Commands using the synchronous API
//!
//! Commands can be chained, allowing the outputs of the first effect to be used in constructing the second
//! effect. For example, the following code creates a new post, then fetches the full created post based
//! on a url read from the response to the creation request:
//!
//! ```
//!# use crux_core::Command;
//!# use doctest_support::command::{Effect, Event, AnOperation, AnOperationOutput};
//!# use crux_http::command::Http;
//!# use crux_core::render::render;
//!# use doctest_support::command::{Effect, Event, AnOperation, AnOperationOutput, Post};
//!# const API_URL: &str = "https://example.com/";
//!# let result = {
//! let cmd: Command<Effect, Event> =
//! Command::request_from_shell(AnOperation::One(1))
//! .then_request(|first| {
//! let AnOperationOutput::One(first) = first else {
//! panic!("Expected One")
//! };
//! let second = first + 1;
//! Command::request_from_shell(AnOperation::Two(second))
//! })
//! .then_send(Event::Completed);
//! Http::post(API_URL)
//! .body(serde_json::json!({"title":"New Post", "body":"Hello!"}))
//! .expect_json::<Post>()
//! .build()
//! .then_request(|result| {
//! let post = result.unwrap();
//! let url = &post.body().unwrap().url;
//!
//! Http::get(url).expect_json().build()
//! })
//! .then_send(Event::GotPost);
//!
//! // Run the http request concurrently with notifying the shell to render
//! Command::all([cmd, render()])
//!# };
//! ```
//! ----
//! 3. Chaining Commands using the async API
//!
//! The same can be done with the async API, if you need more complex orchestration that is
//! more naturally expressed in async rust
//!
//! ```
//! # use crux_core::Command;
//! # use doctest_support::command::{Effect, Event, AnOperation, AnOperationOutput};
//! # use crux_http::command::Http;
//! # use doctest_support::command::{Effect, Event, AnOperation, AnOperationOutput, Post};
//!# const API_URL: &str = "";
//! let cmd: Command<Effect, Event> = Command::new(|ctx| async move {
//! let first = ctx.request_from_shell(AnOperation::One(1)).await;
//! let AnOperationOutput::One(first) = first else {
//! panic!("Expected One")
//! };
//! let second = first + 1;
//! let second = ctx.request_from_shell(AnOperation::Two(second)).await;
//! ctx.send_event(Event::Completed(second));
//! let first = Http::post(API_URL)
//! .body(serde_json::json!({"title":"New Post", "body":"Hello!"}))
//! .expect_json::<Post>()
//! .build()
//! .into_future(ctx.clone())
//! .await;
//!
//! let post = first.unwrap();
//! let url = &post.body().unwrap().url;
//!
//! let second = Http::get(url).expect_json().build().into_future(ctx.clone()).await;
//!
//! ctx.send_event(Event::GotPost(second));
//! });
//! ```
//! ----
//! 4. An async example with `spawn`
//!
//! In the async context, you can spawn additional concurrent tasks, which can, for example,
//! communicate with each other via channels, to enable more complex orchstrations, stateful
//! connection handling and other advanced uses.
//!
//! ```
//! # use crux_core::Command;
Expand Down Expand Up @@ -159,7 +181,9 @@
//! let effect = cmd.effects().next();
//! assert!(effect.is_some());
//!
//! let Effect::AnEffect(mut request) = effect.unwrap();
//! let Effect::AnEffect(mut request) = effect.unwrap() else {
//! panic!("Expected a HTTP effect")
//! };
//!
//! assert_eq!(request.operation, AnOperation::One(1));
//!
Expand Down
1 change: 1 addition & 0 deletions doctest_support/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
crux_core = { path = "../crux_core" }
crux_http = { path = "../crux_http" }
serde = { version = "1.0", features = ["derive"] }

[lints]
Expand Down
22 changes: 22 additions & 0 deletions doctest_support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub mod command {

pub enum Effect {
AnEffect(Request<AnOperation>),
Http(Request<crux_http::protocol::HttpRequest>),
Render(Request<crux_core::render::RenderOperation>),
}

impl From<Request<AnOperation>> for Effect {
Expand All @@ -30,11 +32,31 @@ pub mod command {
}
}

impl From<Request<crux_http::protocol::HttpRequest>> for Effect {
fn from(request: Request<crux_http::protocol::HttpRequest>) -> Self {
Self::Http(request)
}
}

impl From<Request<crux_core::render::RenderOperation>> for Effect {
fn from(request: Request<crux_core::render::RenderOperation>) -> Self {
Self::Render(request)
}
}

#[derive(Debug, PartialEq, Deserialize)]
pub struct Post {
pub url: String,
pub title: String,
pub body: String,
}

#[derive(Debug, PartialEq)]
pub enum Event {
Start,
Completed(AnOperationOutput),
Aborted,
GotPost(Result<crux_http::Response<Post>, crux_http::HttpError>),
}
}

Expand Down

0 comments on commit f2c5930

Please sign in to comment.