Skip to content

Commit

Permalink
command docs
Browse files Browse the repository at this point in the history
  • Loading branch information
StuartHarris committed Jan 30, 2025
1 parent 7fbf8d5 commit e27b6fe
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 14 deletions.
157 changes: 150 additions & 7 deletions crux_core/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,163 @@
//! enabling, for example, wrapping Commands in one another.
//!
//! # Examples
//! ----
//! 1. Using `Command`s with a capability that returns a builder
//!
//! TODO: simple command example with a capability API
//! ```
//!# use std::future::Future;
//!# use crux_core::{Command, command::RequestBuilder, Request};
//!# use doctest_support::command::{Effect, Event, AnOperation, AnOperationOutput};
//! struct Capability;
//!
//! impl Capability
//! where
//! Effect: Send + 'static,
//! Event: Send + 'static,
//! {
//! fn request(value: u8) -> RequestBuilder<Effect, Event, impl Future<Output = AnOperationOutput>> {
//! Command::request_from_shell(AnOperation::One(value))
//! }
//! }
//!
//! // a Command to return from the app's update function
//! let cmd = Capability::request(1).then_send(Event::Completed);
//! ```
//! ----
//! 2. Chaining Commands using the synchronous API
//!
//! ```
//!# use crux_core::Command;
//!# use doctest_support::command::{Effect, Event, AnOperation, AnOperationOutput};
//! 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);
//!
//! ```
//! ----
//! 3. Chaining Commands using the async API
//!
//! ```
//! # use crux_core::Command;
//! # use doctest_support::command::{Effect, Event, AnOperation, AnOperationOutput};
//! 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));
//! });
//! ```
//! ----
//! 4. An async example with `spawn`
//!
//! ```
//! # use crux_core::Command;
//! # use doctest_support::command::{Effect, Event, AnOperation};
//! let mut cmd: Command<Effect, Event> = Command::new(|ctx| async move {
//! let (tx, rx) = async_channel::unbounded();
//!
//! ctx.spawn(|ctx| async move {
//! for i in 0..10u8 {
//! let output = ctx.request_from_shell(AnOperation::One(i)).await;
//! tx.send(output).await.unwrap();
//! }
//! });
//!
//! ctx.spawn(|ctx| async move {
//! while let Ok(value) = rx.recv().await {
//! ctx.send_event(Event::Completed(value));
//! }
//! ctx.send_event(Event::Aborted);
//! });
//! });
//! ```
//! ----
//! 5. A cancellation example
//!
//! ```
//! # use crux_core::Command;
//! # use doctest_support::command::{Effect, Event, AnOperation};
//! let mut cmd: Command<Effect, Event> = Command::all([
//! Command::request_from_shell(AnOperation::One(1)).then_send(Event::Completed),
//! Command::request_from_shell(AnOperation::Two(1)).then_send(Event::Completed),
//! ]);
//!
//! let handle = cmd.abort_handle();
//!
//! assert!(!cmd.was_aborted());
//!
//! ```
//! ----
//! 6. A testing example
//!
//! ```
//! # use crux_core::Command;
//! # use doctest_support::command::{Effect, Event, AnOperation, AnOperationOutput};
//! let mut cmd = Command::request_from_shell(AnOperation::One(1)).then_send(Event::Completed);
//!
//! let effect = cmd.effects().next();
//! assert!(effect.is_some());
//!
//! let Effect::AnEffect(mut request) = effect.unwrap();
//!
//! assert_eq!(request.operation, AnOperation::One(1));
//!
//! request
//! .resolve(AnOperationOutput::One(2))
//! .expect("Resolve should succeed");
//!
//! let event = cmd.events().next().unwrap();
//!
//! TODO: complex example with sync API
//! assert_eq!(event, Event::Completed(AnOperationOutput::One(2)));
//!
//! TODO: basic async example
//! assert!(cmd.is_done())
//! ```
//! ----
//! 7. A composition example
//! ```
//! # use crux_core::{Command, Request};
//! # use doctest_support::command::{Effect, Event, AnOperation, AnOperationOutput};
//! enum ParentEffect {
//! AnEffect(Request<AnOperation>),
//! }
//! #[derive(Debug, PartialEq)]
//! enum ParentEvent {
//! Completed(AnOperationOutput),
//! }
//! let cmd: Command<Effect, Event> =
//! Command::request_from_shell(AnOperation::One(1)).then_send(Event::Completed);
//!
//! TODO: async example with `spawn`
//! let mut mapped_cmd = cmd
//! .map_effect(|ef| match ef {
//! Effect::AnEffect(request) => ParentEffect::AnEffect(request),
//! _ => panic!("unexpected effect"),
//! })
//! .map_event(|ev| match ev {
//! Event::Completed(output) => ParentEvent::Completed(output),
//! _ => panic!("unexpected event"),
//! });
//!
//! TODO: cancellation example
//! let effect = mapped_cmd.effects().next().unwrap();
//! let ParentEffect::AnEffect(mut request) = effect;
//! assert_eq!(request.operation, AnOperation::One(1));
//!
//! TODO: testing example
//! request.resolve(AnOperationOutput::One(2)).expect("should resolve");
//!
//! TODO: composition example
//! let event = mapped_cmd.events().next().unwrap();
//! assert_eq!(event, ParentEvent::Completed(AnOperationOutput::One(2)));
//! ```
mod builder;
mod context;
Expand Down
10 changes: 5 additions & 5 deletions crux_core/src/command/tests/combinators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fn then() {

#[test]
fn chaining() {
let mut cmd: Command<Effect, Event> = Command::request_from_shell(AnOperation::More([3, 4]))
let mut cmd = Command::request_from_shell(AnOperation::More([3, 4]))
.then_request(|first| {
let AnOperationOutput::Other(first) = first else {
// TODO: how do I bail quietly here?
Expand Down Expand Up @@ -122,7 +122,7 @@ fn chaining() {

#[test]
fn long_chain_support() {
let mut cmd: Command<Effect, Event> = Command::request_from_shell(AnOperation::More([3, 4]))
let mut cmd = Command::request_from_shell(AnOperation::More([3, 4]))
.then_request(|first| {
let AnOperationOutput::Other(first) = first else {
// TODO: how do I bail quietly here?
Expand Down Expand Up @@ -380,7 +380,7 @@ fn complex_concurrency() {

#[test]
fn concurrency_mixing_streams_and_requests() {
let mut cmd: Command<Effect, Event> = Command::all([
let mut cmd = Command::all([
Command::stream_from_shell(AnOperation::One)
.then_request(|out| {
let AnOperationOutput::Other([a, b]) = out else {
Expand Down Expand Up @@ -540,7 +540,7 @@ fn stream_followed_by_a_stream() {

#[test]
fn chaining_with_mapping() {
let mut cmd: Command<Effect, Event> = Command::request_from_shell(AnOperation::More([3, 4]))
let mut cmd = Command::request_from_shell(AnOperation::More([3, 4]))
.map(|first| {
let AnOperationOutput::Other(first) = first else {
// TODO: how do I bail quietly here?
Expand Down Expand Up @@ -586,7 +586,7 @@ fn chaining_with_mapping() {

#[test]
fn stream_mapping_and_chaining() {
let mut cmd: Command<Effect, Event> = Command::stream_from_shell(AnOperation::One)
let mut cmd = Command::stream_from_shell(AnOperation::One)
.map(|out| {
let AnOperationOutput::Other([a, b]) = out else {
panic!("Bad output");
Expand Down
12 changes: 10 additions & 2 deletions doctest_support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ pub mod command {
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct AnOperation;
pub enum AnOperation {
One(u8),
Two(u8),
}

#[derive(Debug, PartialEq, Deserialize)]
pub struct AnOperationOutput;
pub enum AnOperationOutput {
One(u8),
Two(u8),
}

impl Operation for AnOperation {
type Output = AnOperationOutput;
Expand All @@ -27,6 +34,7 @@ pub mod command {
pub enum Event {
Start,
Completed(AnOperationOutput),
Aborted,
}
}

Expand Down

0 comments on commit e27b6fe

Please sign in to comment.