From 09afbb6730497a71e4efb3ed1de82d27d7cd3f38 Mon Sep 17 00:00:00 2001 From: Chayim Date: Mon, 20 Mar 2023 12:40:41 +0200 Subject: [PATCH] Breaking API changes for SM.STATE and SM.TRANSITION (#6) --- .gitignore | 1 + README.md | 5 +-- commands.json | 47 +++++++++++---------------- src/function_delete.rs | 28 ---------------- src/function_state.rs | 72 ++++++++++++++++++++++++++++++------------ src/lib.rs | 9 ++---- tests/test_commands.py | 39 +++++++---------------- 7 files changed, 88 insertions(+), 113 deletions(-) delete mode 100644 src/function_delete.rs diff --git a/.gitignore b/.gitignore index a8b6e6c..a907b9c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ target libredis_state.so libredis_state.Linux-ubuntu20.04-x86_64.1.0.1.zip results.xml +redis diff --git a/README.md b/README.md index 4b8a2c6..ad4aa76 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # redis-state-machine [![Latest Release](https://img.shields.io/github/v/release/redislabsmodules/redis-state-machine?label=latest)](https://github.com/redislabsmodules/redis-state-machine/releases/latest) +[![Dockerhub](https://img.shields.io/badge/dockerhub-redislabs/redisstatemachine-blue)](https://hub.docker.com/r/redislabs/redisstatemachine/tags/) A [Redis module](https://redis.io/docs/modules) that maintains a state machine on the server side. @@ -69,7 +70,7 @@ r.execute_command("SM.SET", "mystatemachine", json.dumps(tmpl)) If we try to change our statemachine to an invalid state, Redis returns a nil. ```python -x = r.execute_command("SM.TRANSITION", "mystatemachine", "notastate") +x = r.execute_command("SM.MUTATE", "mystatemachine", "notastate") print(x) >>> None ``` @@ -77,7 +78,7 @@ print(x) If we try to change states to a valid state, we receive an ok. ```python -x = r.execute_command("SM.TRANSITION", "mystatemachine", "blee") +x = r.execute_command("SM.MUTATE", "mystatemachine", "blee") print(x) >>> OK ``` diff --git a/commands.json b/commands.json index edcc573..c84036a 100644 --- a/commands.json +++ b/commands.json @@ -1,16 +1,4 @@ { - "SM.DEL": { - "summary": "Deletes a state machine", - "since": "0.1.0", - "complexity": "O(N)", - "group": "statemachine", - "arguments": [ - { - "name": "key", - "type": "key" - } - ] - }, "SM.GET": { "summary": "Gets a state machine, or the current state of a machine", "since": "0.1.0", @@ -43,18 +31,6 @@ } ] }, - "SM.STATES": { - "summary": "Gets the current state associated with a state machine", - "since": "0.1.0", - "complexity": "O(N)", - "group": "statemachine", - "arguments": [ - { - "name": "key", - "type": "key" - } - ] - }, "SM.RESET": { "summary": "Resets a state machine to its initial state", "since": "0.1.0", @@ -67,7 +43,7 @@ } ] }, - "SM.CURRENT": { + "SM.STATE": { "summary": "Retrieves the current state machine state", "since": "0.1.0", "complexity": "O(N)", @@ -76,7 +52,20 @@ { "name": "key", "type": "key" + }, + { + "name": "codition", + "type": "string", + "optional": true, + "arguments": [ + { + "name": "list", + "type": "pure-token", + "token": "LIST" + } + ] } + ] }, "SM.CREATE": { @@ -84,6 +73,7 @@ "since": "0.1.0", "complexity": "O(N)", "group": "statemachine", + "arity": 2, "arguments": [ { "name": "key", @@ -95,9 +85,10 @@ "summary": "Return the json template used for constructing a state machine", "since": "0.1.0", "complexity": "O(1)", - "group": "statemachine" + "group": "statemachine", + "arity": 1 }, - "SM.TRANSITION": { + "SM.MUTATE": { "summary": "Transition the state machine to the specific state", "since": "0.1.0", "complexity": "O(1)", @@ -119,7 +110,7 @@ { "name": "force", "type": "pure-token", - "token": "F" + "token": "FORCE" } ] } diff --git a/src/function_delete.rs b/src/function_delete.rs deleted file mode 100644 index 3dcfa67..0000000 --- a/src/function_delete.rs +++ /dev/null @@ -1,28 +0,0 @@ -use redis_module::{ - key::RedisKeyWritable, Context, NextArg, RedisError, RedisResult, RedisString, RedisValue, -}; - -use crate::rdb::REDIS_SM_TYPE; -use crate::types::StateMachine; - -// Delete a state machine -pub(crate) fn delete(ctx: &Context, args: Vec) -> RedisResult { - let mut args = args.into_iter().skip(1); - let key = args.next_arg()?; - if args.len() > 0 { - return Err(RedisError::WrongArity); - } - if key.is_empty() { - return Err(RedisError::Str("Empty key specified")); - } - - let kk = RedisKeyWritable::open(ctx.ctx, &key); - - let v = kk.get_value::(&REDIS_SM_TYPE)?; - - if v.is_none() { - Ok(RedisValue::Null) - } else { - kk.delete() - } -} diff --git a/src/function_state.rs b/src/function_state.rs index eb57693..1b19182 100644 --- a/src/function_state.rs +++ b/src/function_state.rs @@ -5,45 +5,75 @@ use redis_module::{ Context, NextArg, RedisError, RedisResult, RedisString, RedisValue, REDIS_OK, }; -pub(crate) fn current_state(ctx: &Context, args: Vec) -> RedisResult { - if args.len() != 2 { +pub(crate) fn state(ctx: &Context, args: Vec) -> RedisResult { + if args.len() < 2 || args.len() > 3{ return Err(RedisError::WrongArity); } let mut args = args.into_iter().skip(1); let key = args.next_arg()?; + let list = args.next_arg(); let rkey = RedisKey::open(ctx.ctx, &key); let value = rkey.get_value::(&REDIS_SM_TYPE)?; - value.map_or_else( - || Ok(RedisValue::Null), - |sm| Ok(RedisValue::SimpleString(sm.current().to_string())), - ) -} - -pub(crate) fn states(ctx: &Context, args: Vec) -> RedisResult { - if args.len() != 2 { - return Err(RedisError::WrongArity); + if value.is_none() { + return Ok(RedisValue::Null); } - let mut args = args.into_iter().skip(1); - let key = args.next_arg()?; - let rkey = RedisKey::open(ctx.ctx, &key); - let value = rkey.get_value::(&REDIS_SM_TYPE)?; + // return value.map_or_else( + // || Ok(RedisValue::Null), + // |sm| Ok(RedisValue::SimpleString(sm.current().to_string())), + // ); + // } - if let Some(sm) = value { - let mut keys: Vec = Vec::new(); + let sm = value.unwrap(); + let mut keys: Vec = Vec::new(); + if ! list.is_err() { for x in sm.map().keys() { keys.push(RedisValue::SimpleString(x.to_string())); } - Ok(RedisValue::Array(keys)) + } else if list.unwrap().to_string().to_uppercase() == "LIST" { + keys.push(RedisValue::SimpleString(sm.current().to_string())); } else { - Ok(RedisValue::Null) + return Ok(RedisValue::Null); } + Ok(RedisValue::Array(keys)) + + // if let Some(sm) = value { + // let mut keys: Vec = Vec::new(); + // for x in sm.map().keys() { + // keys.push(RedisValue::SimpleString(x.to_string())); + // } + // Ok(RedisValue::Array(keys)) + // } else { + // Ok(RedisValue::Null) + // } + } -pub(crate) fn transition(ctx: &Context, args: Vec) -> RedisResult { +// pub(crate) fn states(ctx: &Context, args: Vec) -> RedisResult { +// if args.len() != 2 { +// return Err(RedisError::WrongArity); +// } +// let mut args = args.into_iter().skip(1); +// let key = args.next_arg()?; + +// let rkey = RedisKey::open(ctx.ctx, &key); +// let value = rkey.get_value::(&REDIS_SM_TYPE)?; + +// if let Some(sm) = value { +// let mut keys: Vec = Vec::new(); +// for x in sm.map().keys() { +// keys.push(RedisValue::SimpleString(x.to_string())); +// } +// Ok(RedisValue::Array(keys)) +// } else { +// Ok(RedisValue::Null) +// } +// } + +pub(crate) fn mutate(ctx: &Context, args: Vec) -> RedisResult { if args.len() < 3 || args.len() > 5 { return Err(RedisError::WrongArity); } @@ -67,7 +97,7 @@ pub(crate) fn transition(ctx: &Context, args: Vec) -> RedisResult { return Ok(RedisValue::Null); } sm.set_current(target.to_string()); - } else if force.unwrap().to_string() == "F" { + } else if force.unwrap().to_string().to_uppercase() == "FORCE" { sm.set_current(target.to_string()); } else { return Ok(RedisValue::Null); diff --git a/src/lib.rs b/src/lib.rs index 9d7b8bd..8b7c791 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ pub const REDIS_SM_TYPE_VERSION: i32 = 1; pub const MODULE_NAME: &str = "RedisStateMachine"; pub const MODULE_TYPE: &str = "RedisStateMachine"; -mod function_delete; mod function_get; mod function_set; mod function_state; @@ -21,13 +20,11 @@ redis_module! { commands: [ ["SM.GET", function_get::get, "readonly", 0, 0, 0], ["SM.SET", function_set::set, "write deny-oom", 1, 1, 1], - ["SM.CURRENT", function_state::current_state, "readonly", 0, 0, 0], - ["SM.STATES", function_state::states, "readonly", 0, 0, 0], - ["SM.DEL", function_delete::delete, "write", 1, 1, 1], + ["SM.STATE", function_state::state, "readonly", 0, 0, 0], + // ["SM.STATES", function_state::states, "readonly", 0, 0, 0], ["SM.CREATE", function_set::create, "write deny-oom", 1, 1, 1], ["SM.TEMPLATE", function_get::template, "readonly", 0, 0, 0], - // ["SM.FORCE", function_set::force_set, "write deny-oom", 1, 1, 1], ["SM.RESET", function_set::reset, "write deny-oom", 1, 1, 1], - ["SM.TRANSITION", function_state::transition, "write deny-oom", 1, 1, 1], + ["SM.MUTATE", function_state::mutate, "write deny-oom", 1, 1, 1], ], } diff --git a/tests/test_commands.py b/tests/test_commands.py index 069fefe..f76c5ef 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -41,7 +41,7 @@ def test_set_get(r): assert bar == valid_with_current -def test_get_current(r): +def test_get_current_state(r): r.flushdb() initial = "begin" mapstates = { @@ -50,7 +50,7 @@ def test_get_current(r): } validmap = {"initial": initial, "map": mapstates, "current": "begin"} assert r.execute_command("SM.SET", "fooforcurrent", json.dumps(validmap)) - assert r.execute_command("SM.CURRENT", "fooforcurrent") == "begin" + assert r.execute_command("SM.STATE", "fooforcurrent") == ["begin"] def test_get_states(r): @@ -62,7 +62,7 @@ def test_get_states(r): } validmap = {"initial": initial, "map": mapstates, "current": "too"} assert r.execute_command("SM.SET", "foostates", json.dumps(validmap)) - states = r.execute_command("SM.STATES", "foostates") + states = r.execute_command("SM.STATE", "foostates", "list") mapkeys = list(mapstates.keys()) mapkeys.sort() @@ -79,7 +79,7 @@ def test_set_del(r): } validmap = {"initial": initial, "map": mapstates, "current": "shmm"} assert r.execute_command("SM.SET", "foostates", json.dumps(validmap)) - assert r.execute_command("SM.DEL", "foostates") + assert r.delete("foostates") keys = r.keys() assert "foostates" not in keys @@ -95,28 +95,14 @@ def test_reset(r): validmap = {"initial": initial, "map": mapstates, "current": "too"} assert r.execute_command("SM.SET", "foostates", json.dumps(validmap)) r.execute_command("SM.RESET", "foostates") - assert r.execute_command("SM.CURRENT", "foostates") == initial - - -# def test_force_set(r): -# r.flushdb() -# initial = "begin" -# mapstates = { -# "a": ["this", "maps", "states"], -# "b": ["this", "too", "maps", "somewhere"], -# } -# validmap = {"initial": initial, "map": mapstates, "current": "maps"} -# assert r.execute_command("SM.SET", "foostates", json.dumps(validmap)) -# assert r.execute_command("SM.FORCE", "foostates", "too") - -# assert r.execute_command("SM.CURRENT", "foostates") == "too" + assert r.execute_command("SM.STATE", "foostates") == [initial] def test_create(r): r.flushdb() key = "foo" assert r.execute_command("SM.CREATE", key) - assert r.execute_command("SM.CURRENT", key) == "" + assert r.execute_command("SM.STATE", key) == [''] res = r.execute_command("SM.GET", key) val = json.loads(res) assert val["initial"] == "" @@ -124,9 +110,6 @@ def test_create(r): assert val["current"] == "" -# r.execute_command("SM.FORCE", "foostates", "too") - - def test_template(r): res = r.execute_command("SM.TEMPLATE") val = json.loads(res) @@ -134,7 +117,7 @@ def test_template(r): assert val["map"] == {} assert val["current"] == "" -def test_transition(r): +def test_mutate(r): r.flushdb() initial = "begin" current = "begin" @@ -146,11 +129,11 @@ def test_transition(r): valid_with_current = {"initial": initial, "map": mapstates, "current": current} assert r.execute_command("SM.SET", "bar", json.dumps(valid_with_current)) - assert r.execute_command("SM.TRANSITION", "bar", "smurfy") == None - assert r.execute_command("SM.TRANSITION", "bar", "too") + assert r.execute_command("SM.MUTATE", "bar", "smurfy") == None + assert r.execute_command("SM.MUTATE", "bar", "too") # force state r.flushdb() assert r.execute_command("SM.SET", "bar", json.dumps(valid_with_current)) - assert r.execute_command("SM.TRANSITION", "bar", "banna", "foo") == None - assert r.execute_command("SM.TRANSITION", "bar", "banna", "F") \ No newline at end of file + assert r.execute_command("SM.MUTATE", "bar", "banna", "foo") == None + assert r.execute_command("SM.MUTATE", "bar", "banna", "F") \ No newline at end of file