use anyhow::Result;
use crux_core::{
App as _, Command,
macros::effect,
render::{RenderOperation, render},
};
use serde::{Deserialize, Serialize};
use crate::{
KeyValueOperation, KeyValueResult,
command::KeyValue,
error::KeyValueError,
protocol::{KeyValueResponse, Value},
};
#[derive(Default)]
pub struct App;
#[derive(Debug, Serialize, Deserialize)]
pub enum Event {
Get,
Set,
Delete,
Exists,
ListKeys,
GetThenSet,
GetResponse(Result<Option<Vec<u8>>, KeyValueError>),
SetResponse(Result<Option<Vec<u8>>, KeyValueError>),
ExistsResponse(Result<bool, KeyValueError>),
ListKeysResponse(Result<(Vec<String>, u64), KeyValueError>),
}
#[derive(Debug, Default)]
pub struct Model {
pub value: i32,
pub keys: Vec<String>,
pub cursor: u64,
pub successful: bool,
}
#[derive(Serialize, Deserialize, Default)]
pub struct ViewModel {
pub result: String,
}
impl crux_core::App for App {
type Event = Event;
type Model = Model;
type ViewModel = ViewModel;
type Effect = Effect;
fn update(&self, event: Event, model: &mut Model) -> Command<Effect, Event> {
let key = "test".to_string();
match event {
Event::Get => KeyValue::get(key).then_send(Event::GetResponse),
Event::Set => {
KeyValue::set(key, 42i32.to_ne_bytes().to_vec()).then_send(Event::SetResponse)
}
Event::Delete => KeyValue::delete(key).then_send(Event::SetResponse),
Event::Exists => KeyValue::exists(key).then_send(Event::ExistsResponse),
Event::ListKeys => {
KeyValue::list_keys("test:".to_string(), 0).then_send(Event::ListKeysResponse)
}
Event::GetThenSet => Command::new(|ctx| async move {
let Result::Ok(Some(value)) = KeyValue::get("test_num".to_string())
.into_future(ctx.clone())
.await
else {
panic!("expected get response with a value");
};
let num = i32::from_ne_bytes(value.try_into().unwrap());
let result =
KeyValue::set("test_num".to_string(), (num + 1).to_ne_bytes().to_vec())
.into_future(ctx.clone())
.await;
ctx.send_event(Event::SetResponse(result));
}),
Event::GetResponse(Ok(Some(value))) => {
let (int_bytes, _rest) = value.split_at(std::mem::size_of::<i32>());
model.value = i32::from_ne_bytes(int_bytes.try_into().unwrap());
Command::done()
}
Event::GetResponse(Ok(None)) => {
panic!("expected value");
}
Event::SetResponse(Ok(_response)) => {
model.successful = true;
render()
}
Event::ExistsResponse(Ok(_response)) => {
model.successful = true;
render()
}
Event::ListKeysResponse(Ok((keys, cursor))) => {
model.keys = keys;
model.cursor = cursor;
render()
}
Event::GetResponse(Err(error))
| Event::SetResponse(Err(error))
| Event::ExistsResponse(Err(error))
| Event::ListKeysResponse(Err(error)) => {
panic!("Error: {error:?}");
}
}
}
fn view(&self, model: &Self::Model) -> Self::ViewModel {
ViewModel {
result: format!("Success: {}, Value: {}", model.successful, model.value),
}
}
}
#[effect]
pub enum Effect {
KeyValue(KeyValueOperation),
Render(RenderOperation),
}
#[test]
fn test_get() {
let app = App;
let mut model = Model::default();
let mut cmd = app.update(Event::Get, &mut model);
cmd.expect_no_events();
let mut request = cmd.expect_one_effect().expect_key_value();
assert_eq!(
request.operation,
KeyValueOperation::Get {
key: "test".to_string()
}
);
request
.resolve(KeyValueResult::Ok {
response: KeyValueResponse::Get {
value: 42i32.to_ne_bytes().to_vec().into(),
},
})
.expect("effect should resolve");
let event = cmd.expect_one_event();
app.update(event, &mut model).expect_no_effect_or_events();
assert_eq!(model.value, 42);
}
#[test]
fn test_set() {
let app = App;
let mut model = Model::default();
let mut cmd = app.update(Event::Set, &mut model);
cmd.expect_no_events();
let mut request = cmd.expect_one_effect().expect_key_value();
assert_eq!(
request.operation,
KeyValueOperation::Set {
key: "test".to_string(),
value: 42i32.to_ne_bytes().to_vec(),
}
);
request
.resolve(KeyValueResult::Ok {
response: KeyValueResponse::Set {
previous: Value::None,
},
})
.expect("effect should resolve");
let event = cmd.expect_one_event();
app.update(event, &mut model)
.expect_one_effect()
.expect_render();
assert!(model.successful);
}
#[test]
fn test_delete() {
let app = App;
let mut model = Model::default();
let mut cmd = app.update(Event::Delete, &mut model);
cmd.expect_no_events();
let mut request = cmd.expect_one_effect().expect_key_value();
assert_eq!(
request.operation,
KeyValueOperation::Delete {
key: "test".to_string()
}
);
request
.resolve(KeyValueResult::Ok {
response: KeyValueResponse::Delete {
previous: Value::None,
},
})
.expect("effect should resolve");
let event = cmd.expect_one_event();
app.update(event, &mut model)
.expect_one_effect()
.expect_render();
assert!(model.successful);
}
#[test]
fn test_exists() {
let app = App;
let mut model = Model::default();
let mut cmd = app.update(Event::Exists, &mut model);
cmd.expect_no_events();
let mut request = cmd.expect_one_effect().expect_key_value();
assert_eq!(
request.operation,
KeyValueOperation::Exists {
key: "test".to_string()
}
);
request
.resolve(KeyValueResult::Ok {
response: KeyValueResponse::Exists { is_present: true },
})
.expect("effect should resolve");
let event = cmd.expect_one_event();
app.update(event, &mut model)
.expect_one_effect()
.expect_render();
assert!(model.successful);
}
#[test]
fn test_list_keys() {
let app = App;
let mut model = Model::default();
let mut cmd = app.update(Event::ListKeys, &mut model);
cmd.expect_no_events();
let mut request = cmd.expect_one_effect().expect_key_value();
assert_eq!(
request.operation,
KeyValueOperation::ListKeys {
prefix: "test:".to_string(),
cursor: 0,
}
);
request
.resolve(KeyValueResult::Ok {
response: KeyValueResponse::ListKeys {
keys: vec!["test:1".to_string(), "test:2".to_string()],
next_cursor: 2,
},
})
.expect("effect should resolve");
let event = cmd.expect_one_event();
app.update(event, &mut model)
.expect_one_effect()
.expect_render();
assert_eq!(model.keys, vec!["test:1".to_string(), "test:2".to_string()]);
assert_eq!(model.cursor, 2);
}
#[test]
pub fn test_kv_async() {
let app = App;
let mut model = Model::default();
let mut cmd = app.update(Event::GetThenSet, &mut model);
cmd.expect_no_events();
let mut request = cmd.expect_one_effect().expect_key_value();
assert_eq!(
request.operation,
KeyValueOperation::Get {
key: "test_num".to_string()
}
);
request
.resolve(KeyValueResult::Ok {
response: KeyValueResponse::Get {
value: 17u32.to_ne_bytes().to_vec().into(),
},
})
.expect("effect should resolve");
let mut request = cmd.expect_one_effect().expect_key_value();
assert_eq!(
request.operation,
KeyValueOperation::Set {
key: "test_num".to_string(),
value: 18u32.to_ne_bytes().to_vec(),
}
);
request
.resolve(KeyValueResult::Ok {
response: KeyValueResponse::Set {
previous: Value::None,
},
})
.expect("effect should resolve");
let event = cmd.expect_one_event();
app.update(event, &mut model)
.expect_one_effect()
.expect_render();
assert!(model.successful);
}
#[test]
fn test_kv_operation_debug_repr() {
{
let op = KeyValueOperation::Get {
key: "my key".into(),
};
let repr = format!("{op:?}");
assert_eq!(repr, r#"Get { key: "my key" }"#);
}
{
let op = KeyValueOperation::Set {
key: "my key".into(),
value: b"my value".to_vec(),
};
let repr = format!("{op:?}");
assert_eq!(repr, r#"Set { key: "my key", value: "my value" }"#);
}
{
let op = KeyValueOperation::Set {
key: "my key".into(),
value:
"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstu😀😀😀😀😀😀".as_bytes().to_vec(),
};
let repr = format!("{op:?}");
assert_eq!(
repr,
r#"Set { key: "my key", value: "abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstu😀😀"... }"#
);
}
{
let op = KeyValueOperation::Set {
key: "my key".into(),
value: vec![255, 255],
};
let repr = format!("{op:?}");
assert_eq!(
repr,
r#"Set { key: "my key", value: <binary data - 2 bytes> }"#
);
}
}