Skip to content

Commit

Permalink
Add async API to crux_time, move to using chrono::DateTime<Utc> as re…
Browse files Browse the repository at this point in the history
…presentation
  • Loading branch information
charypar committed Jan 26, 2024
1 parent 6b878b6 commit f7284b4
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 22 deletions.
87 changes: 87 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crux_time/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ anyhow.workspace = true
crux_core = { version = "0.7", path = "../crux_core" }
crux_macros = { version = "0.3", path = "../crux_macros" }
serde = { workspace = true, features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
32 changes: 23 additions & 9 deletions crux_time/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,35 @@
//! interface to do so.
//!
//! This is still work in progress and as such very basic. It returns time as an IS08601 string.
use chrono::{DateTime, Utc};
use crux_core::capability::{CapabilityContext, Operation};
use crux_macros::Capability;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TimeRequest;

// TODO revisit this
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TimeResponse(pub String);

impl Operation for TimeRequest {
type Output = TimeResponse;
type Output = DateTime<Utc>;
}

/// The Time capability API.
/// The Time capability API. Uses the `chrono` crate's timezone aware representation
/// of the current date and time.
///
/// The time value serializes as an RFC3339 string across the FFI boundary.
#[derive(Capability)]
pub struct Time<Ev> {
context: CapabilityContext<TimeRequest, Ev>,
}

impl<Ev> Clone for Time<Ev> {
fn clone(&self) -> Self {
Self {
context: self.context.clone(),
}
}
}

impl<Ev> Time<Ev>
where
Ev: 'static,
Expand All @@ -34,11 +42,11 @@ where
Self { context }
}

/// Request current time, which will be passed to the app as `TimeResponse`
/// Request current time, which will be passed to the app as `chrono::DateTime<Utc>`
/// wrapped in the event produced by the `callback`.
pub fn get<F>(&self, callback: F)
pub fn now<F>(&self, callback: F)
where
F: Fn(TimeResponse) -> Ev + Send + Sync + 'static,
F: Fn(DateTime<Utc>) -> Ev + Send + Sync + 'static,
{
self.context.spawn({
let context = self.context.clone();
Expand All @@ -49,4 +57,10 @@ where
}
});
}

/// Request current time, which will be returned as `chrono::DateTime<Utc>`.
/// This is an async call to use with [`crux_core::compose::Compose`].
pub async fn now_async(&self) -> DateTime<Utc> {
self.context.request_from_shell(TimeRequest).await
}
}
59 changes: 46 additions & 13 deletions crux_time/tests/time_test.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
mod shared {
use crux_core::render::Render;
use crux_macros::Effect;
use crux_time::{Time, TimeResponse};
use crux_time::Time;
use serde::{Deserialize, Serialize};

#[derive(Default)]
pub struct App;

#[derive(Serialize, Deserialize)]
pub enum Event {
TimeGet,
TimeSet(TimeResponse),
Get,
GetAsync,
Set(chrono::DateTime<chrono::Utc>),
}

#[derive(Default, Serialize, Deserialize)]
Expand All @@ -31,9 +32,16 @@ mod shared {

fn update(&self, event: Event, model: &mut Model, caps: &Capabilities) {
match event {
Event::TimeGet => caps.time.get(Event::TimeSet),
Event::TimeSet(time) => {
model.time = time.0;
Event::Get => caps.time.now(Event::Set),
Event::GetAsync => caps.compose.spawn(|ctx| {
let caps = caps.clone();

async move {
ctx.update_app(Event::Set(caps.time.now_async().await));
}
}),
Event::Set(time) => {
model.time = time.to_rfc3339();
caps.render.render()
}
}
Expand All @@ -46,21 +54,24 @@ mod shared {
}
}

#[derive(Effect)]
#[derive(Effect, Clone)]
pub struct Capabilities {
pub time: Time<Event>,
pub render: Render<Event>,
#[effect(skip)]
pub compose: crux_core::compose::Compose<Event>,
}
}

mod shell {
use super::shared::{App, Effect, Event};
use chrono::{DateTime, Utc};
use crux_core::{Core, Request};
use crux_time::{TimeRequest, TimeResponse};
use crux_time::TimeRequest;
use std::collections::VecDeque;

pub enum Outcome {
Time(Request<TimeRequest>, TimeResponse),
Time(Request<TimeRequest>, DateTime<Utc>),
}

enum CoreMessage {
Expand All @@ -71,7 +82,7 @@ mod shell {
pub fn run(core: &Core<Effect, App>) {
let mut queue: VecDeque<CoreMessage> = VecDeque::new();

queue.push_back(CoreMessage::Event(Event::TimeGet));
queue.push_back(CoreMessage::Event(Event::Get));

while !queue.is_empty() {
let msg = queue.pop_front();
Expand All @@ -88,7 +99,7 @@ mod shell {
if let Effect::Time(request) = effect {
queue.push_back(CoreMessage::Response(Outcome::Time(
request,
TimeResponse("2022-12-01T01:47:12.746202562+00:00".to_string()),
"2022-12-01T01:47:12.746202562+00:00".parse().unwrap(),
)));
}
}
Expand All @@ -98,10 +109,11 @@ mod shell {

mod tests {
use crate::{
shared::{App, Effect},
shared::{App, Effect, Event, Model},
shell::run,
};
use crux_core::Core;
use chrono::{DateTime, Utc};
use crux_core::{testing::AppTester, Core};

#[test]
pub fn test_time() {
Expand All @@ -111,4 +123,25 @@ mod tests {

assert_eq!(core.view().time, "2022-12-01T01:47:12.746202562+00:00");
}

#[test]
pub fn test_time_async() {
let app = AppTester::<App, _>::default();
let mut model = Model::default();

let update = app.update(Event::GetAsync, &mut model);

let effect = update.into_effects().next().unwrap();
let Effect::Time(mut request) = effect else {
panic!("Expected Time effect");
};

let now: DateTime<Utc> = "2022-12-01T01:47:12.746202562+00:00".parse().unwrap();
let update = app.resolve(&mut request, now).unwrap();

let event = update.events.into_iter().next().unwrap();
app.update(event, &mut model);

assert_eq!(app.view(&model).time, "2022-12-01T01:47:12.746202562+00:00");
}
}

0 comments on commit f7284b4

Please sign in to comment.