Skip to content

Commit

Permalink
Breaking API changes for SM.STATE and SM.TRANSITION (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
chayim authored Mar 20, 2023
1 parent ff52441 commit 09afbb6
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 113 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ target
libredis_state.so
libredis_state.Linux-ubuntu20.04-x86_64.1.0.1.zip
results.xml
redis
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -69,15 +70,15 @@ 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
```

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
```
Expand Down
47 changes: 19 additions & 28 deletions commands.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -67,7 +43,7 @@
}
]
},
"SM.CURRENT": {
"SM.STATE": {
"summary": "Retrieves the current state machine state",
"since": "0.1.0",
"complexity": "O(N)",
Expand All @@ -76,14 +52,28 @@
{
"name": "key",
"type": "key"
},
{
"name": "codition",
"type": "string",
"optional": true,
"arguments": [
{
"name": "list",
"type": "pure-token",
"token": "LIST"
}
]
}

]
},
"SM.CREATE": {
"summary": "Creates a blank state machine and stores it in the named key",
"since": "0.1.0",
"complexity": "O(N)",
"group": "statemachine",
"arity": 2,
"arguments": [
{
"name": "key",
Expand All @@ -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)",
Expand All @@ -119,7 +110,7 @@
{
"name": "force",
"type": "pure-token",
"token": "F"
"token": "FORCE"
}
]
}
Expand Down
28 changes: 0 additions & 28 deletions src/function_delete.rs

This file was deleted.

72 changes: 51 additions & 21 deletions src/function_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,75 @@ use redis_module::{
Context, NextArg, RedisError, RedisResult, RedisString, RedisValue, REDIS_OK,
};

pub(crate) fn current_state(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
if args.len() != 2 {
pub(crate) fn state(ctx: &Context, args: Vec<RedisString>) -> 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::<StateMachine>(&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<RedisString>) -> 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::<StateMachine>(&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<RedisValue> = Vec::new();
let sm = value.unwrap();
let mut keys: Vec<RedisValue> = 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<RedisValue> = 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<RedisString>) -> RedisResult {
// pub(crate) fn states(ctx: &Context, args: Vec<RedisString>) -> 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::<StateMachine>(&REDIS_SM_TYPE)?;

// if let Some(sm) = value {
// let mut keys: Vec<RedisValue> = 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<RedisString>) -> RedisResult {
if args.len() < 3 || args.len() > 5 {
return Err(RedisError::WrongArity);
}
Expand All @@ -67,7 +97,7 @@ pub(crate) fn transition(ctx: &Context, args: Vec<RedisString>) -> 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);
Expand Down
9 changes: 3 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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],
],
}
39 changes: 11 additions & 28 deletions tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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):
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -95,46 +95,29 @@ 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"] == ""
assert val["map"] == {}
assert val["current"] == ""


# r.execute_command("SM.FORCE", "foostates", "too")


def test_template(r):
res = r.execute_command("SM.TEMPLATE")
val = json.loads(res)
assert val["initial"] == ""
assert val["map"] == {}
assert val["current"] == ""

def test_transition(r):
def test_mutate(r):
r.flushdb()
initial = "begin"
current = "begin"
Expand All @@ -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")
assert r.execute_command("SM.MUTATE", "bar", "banna", "foo") == None
assert r.execute_command("SM.MUTATE", "bar", "banna", "F")

0 comments on commit 09afbb6

Please sign in to comment.