Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(context): reimplement context as an kv store #65

Merged
merged 2 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@ jobs:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run lib tests
run: cargo test --verbose --lib
- name: Run doc tests
run: cargo test --verbose --doc
- name: Run integration tests
run: cargo test --test '*'
- name: Run tests
run: cargo test -- --nocapture
- uses: actions/checkout@v1
- run: rustup component add clippy
- uses: actions-rs/clippy-check@v1
Expand Down
5 changes: 5 additions & 0 deletions Cargo.lock

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

17 changes: 2 additions & 15 deletions mfm_core/src/contexts/mod.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
use crate::config::Config;
use anyhow::{anyhow, Error};
use mfm_machine::state::context::Context;
use serde_derive::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ConfigSource {
File(String),
}

impl Context for ConfigSource {
fn read(&self) -> Result<Value, Error> {
serde_json::to_value(self).map_err(|e| anyhow!(e))
}

fn write(&mut self, _: &Value) -> Result<(), Error> {
// do nothing
Ok(())
}
TomlFile(String),
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct ReadConfig {
pub config_source: ConfigSource,
pub config: Config,
}
pub const READ_CONFIG: &str = "read_config";
31 changes: 25 additions & 6 deletions mfm_core/src/states/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use mfm_machine::state::{
Tag,
};

use crate::contexts;
use crate::contexts::{self, READ_CONFIG};

#[derive(Debug, Clone, PartialEq, StateMetadataReqs)]
pub struct ReadConfig {
Expand All @@ -16,7 +16,7 @@ pub struct ReadConfig {
}

impl ReadConfig {
fn new() -> Self {
pub fn new() -> Self {
Self {
label: Label::new("read_config").unwrap(),
tags: vec![Tag::new("setup").unwrap()],
Expand All @@ -25,10 +25,20 @@ impl ReadConfig {
}
}
}
impl Default for ReadConfig {
fn default() -> Self {
Self::new()
}
}

impl StateHandler for ReadConfig {
fn handler(&self, context: ContextWrapper) -> StateResult {
let value = context.lock().unwrap().read().unwrap();
let value = context
.lock()
.unwrap()
.read(READ_CONFIG.to_string())
.unwrap();

let data: contexts::ConfigSource = serde_json::from_value(value).unwrap();
println!("data: {:?}", data);
Ok(())
Expand All @@ -37,16 +47,25 @@ impl StateHandler for ReadConfig {

#[cfg(test)]
mod test {
use mfm_machine::state::{context::wrap_context, StateHandler};
use std::collections::HashMap;

use mfm_machine::state::{
context::{wrap_context, Local},
StateHandler,
};
use serde_json::json;

use crate::contexts::ConfigSource;
use crate::contexts::{ConfigSource, READ_CONFIG};

use super::ReadConfig;

#[test]
fn test_readconfig_from_source_file() {
let state = ReadConfig::new();
let ctx_input = wrap_context(ConfigSource::File("test_config.toml".to_string()));
let ctx_input = wrap_context(Local::new(HashMap::from([(
READ_CONFIG.to_string(),
json!(ConfigSource::TomlFile("test_config.toml".to_string())),
)])));
let result = state.handler(ctx_input);
assert!(result.is_ok())
}
Expand Down
86 changes: 49 additions & 37 deletions mfm_machine/src/state/context.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,51 @@
use std::sync::{Arc, Mutex};
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};

use anyhow::Error;
use serde_json::Value;
use anyhow::{anyhow, Error};
use serde_derive::{Deserialize, Serialize};
use serde_json::{json, Value};

pub type ContextWrapper = Arc<Mutex<Box<dyn Context>>>;

// TODO: rethink this implementation of kv store context;
// we should be able to express context constraints for each state
// at the type system level;
#[derive(Default, Serialize, Deserialize)]
pub struct Local {
map: HashMap<String, Value>,
}

impl Local {
pub fn new(map: HashMap<String, Value>) -> Self {
Self { map }
}
}

impl Context for Local {
fn read(&self, key: String) -> Result<Value, Error> {
Ok(self
.map
.get(&key)
.ok_or_else(|| anyhow!("key not found"))?
.clone())
}

fn write(&mut self, key: String, value: &Value) -> Result<(), Error> {
self.map.insert(key, value.clone());
Ok(())
}

fn dump(&self) -> Result<Value, Error> {
Ok(json!(self))
}
}

pub trait Context {
fn read(&self) -> Result<Value, Error>;
fn write(&mut self, value: &Value) -> Result<(), Error>;
fn read(&self, key: String) -> Result<Value, Error>;
fn write(&mut self, key: String, value: &Value) -> Result<(), Error>;
fn dump(&self) -> Result<Value, Error>;
}

pub fn wrap_context<C: Context + 'static>(context: C) -> ContextWrapper {
Expand All @@ -17,45 +55,19 @@ pub fn wrap_context<C: Context + 'static>(context: C) -> ContextWrapper {

#[cfg(test)]
mod test {
use anyhow::anyhow;
use serde_derive::{Deserialize, Serialize};
use serde_json::json;

use super::*;

#[derive(Serialize, Deserialize)]
struct ContextA {
a: String,
b: u64,
}

impl ContextA {
fn _new(a: String, b: u64) -> Self {
Self { a, b }
}
}

impl Context for ContextA {
fn read(&self) -> Result<Value, Error> {
serde_json::to_value(self).map_err(|e| anyhow!(e))
}

fn write(&mut self, value: &Value) -> Result<(), Error> {
let ctx: ContextA = serde_json::from_value(value.clone()).map_err(|e| anyhow!(e))?;
self.a = ctx.a;
self.b = ctx.b;
Ok(())
}
}

#[test]
fn test_read_write() {
let context_a: &mut dyn Context = &mut ContextA::_new(String::from("hello"), 7);
let context_b: &dyn Context = &ContextA::_new(String::from("hellow"), 9);
let context_a: &mut dyn Context = &mut Local::default();

assert_ne!(context_a.read().unwrap(), context_b.read().unwrap());
let body = json!({"b1": "test1"});
let key = "key1".to_string();

context_a.write(&context_b.read().unwrap()).unwrap();
context_a.write(key.clone(), &body).unwrap();

assert_eq!(context_a.read().unwrap(), context_b.read().unwrap());
assert_eq!(context_a.read(key).unwrap(), body);
}
}
Loading
Loading