Skip to content
This repository has been archived by the owner on Apr 20, 2020. It is now read-only.

Commit

Permalink
fix #55 return the right type on json.num* call (#56)
Browse files Browse the repository at this point in the history
* fix #55 return the right type on json.num* call
  • Loading branch information
gkorland committed Sep 11, 2019
1 parent 4c46496 commit 2143ec1
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 39 deletions.
56 changes: 37 additions & 19 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,58 +274,76 @@ fn json_type(ctx: &Context, args: Vec<String>) -> RedisResult {
/// JSON.NUMINCRBY <key> <path> <number>
///
fn json_num_incrby(ctx: &Context, args: Vec<String>) -> RedisResult {
json_num_op(ctx, args, |num1, num2| num1 + num2)
json_num_op(ctx, args, |i1, i2| i1 + i2, |f1, f2| f1 + f2)
}

///
/// JSON.NUMMULTBY <key> <path> <number>
///
fn json_num_multby(ctx: &Context, args: Vec<String>) -> RedisResult {
json_num_op(ctx, args, |num1, num2| num1 * num2)
json_num_op(ctx, args, |i1, i2| i1 * i2, |f1, f2| f1 * f2)
}

///
/// JSON.NUMPOWBY <key> <path> <number>
///
fn json_num_powby(ctx: &Context, args: Vec<String>) -> RedisResult {
json_num_op(ctx, args, |num1, num2| num1.powf(num2))
json_num_op(ctx, args, |i1, i2| i1.pow(i2 as u32), |f1, f2| f1.powf(f2))
}

fn json_num_op<F>(ctx: &Context, args: Vec<String>, fun: F) -> RedisResult
fn json_num_op<I, F>(ctx: &Context, args: Vec<String>, op_i64: I, op_f64: F) -> RedisResult
where
I: Fn(i64, i64) -> i64,
F: Fn(f64, f64) -> f64,
{
let mut args = args.into_iter().skip(1);

let key = args.next_string()?;
let path = backwards_compat_path(args.next_string()?);
let number: f64 = args.next_string()?.parse()?;
let number = args.next_string()?;

let key = ctx.open_key_writable(&key);

key.get_value::<RedisJSON>(&REDIS_JSON_TYPE)?
.ok_or_else(RedisError::nonexistent_key)
.and_then(|doc| {
doc.value_op(&path, |value| do_json_num_op(&fun, number, value))
.map(|v| v.to_string().into())
.map_err(|e| e.into())
doc.value_op(&path, |value| {
do_json_num_op(&number, value, &op_i64, &op_f64)
})
.map(|v| v.to_string().into())
.map_err(|e| e.into())
})
}

fn do_json_num_op<F>(fun: F, number: f64, value: &Value) -> Result<Value, Error>
fn do_json_num_op<I, F>(
in_value: &str,
curr_value: &Value,
op_i64: I,
op_f64: F,
) -> Result<Value, Error>
where
I: FnOnce(i64, i64) -> i64,
F: FnOnce(f64, f64) -> f64,
{
value
.as_f64()
.ok_or_else(|| err_json(value, "number"))
.and_then(|curr_value| {
let res = fun(curr_value, number);

Number::from_f64(res)
.ok_or(Error::from("ERR cannot represent result as Number"))
.map(Value::Number)
})
if let Value::Number(curr_value) = curr_value {
let in_value = &serde_json::from_str(in_value)?;
if let Value::Number(in_value) = in_value {
let num_res = match (curr_value.as_i64(), in_value.as_i64()) {
(Some(num1), Some(num2)) => op_i64(num1, num2).into(),
_ => {
let num1 = curr_value.as_f64().unwrap();
let num2 = in_value.as_f64().unwrap();
Number::from_f64(op_f64(num1, num2)).unwrap()
}
};

Ok(Value::Number(num_res))
} else {
Err(err_json(in_value, "number"))
}
} else {
Err(err_json(curr_value, "number"))
}
}

fn err_json(value: &Value, expected_value: &'static str) -> Error {
Expand Down
40 changes: 20 additions & 20 deletions test/pytest/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,10 +599,10 @@ def testNumIncrCommand(self):
r.flushdb()

self.assertOk(r.execute_command('JSON.SET', 'test', '.', '{ "foo": 0, "bar": "baz" }'))
# self.assertEqual('1', r.execute_command('JSON.NUMINCRBY', 'test', '.foo', 1))
# self.assertEqual('1', r.execute_command('JSON.GET', 'test', '.foo'))
# self.assertEqual('3', r.execute_command('JSON.NUMINCRBY', 'test', '.foo', 2))
# self.assertEqual('3.5', r.execute_command('JSON.NUMINCRBY', 'test', '.foo', .5))
self.assertEqual('1', r.execute_command('JSON.NUMINCRBY', 'test', '.foo', 1))
self.assertEqual('1', r.execute_command('JSON.GET', 'test', '.foo'))
self.assertEqual('3', r.execute_command('JSON.NUMINCRBY', 'test', '.foo', 2))
self.assertEqual('3.5', r.execute_command('JSON.NUMINCRBY', 'test', '.foo', .5))

# test a wrong type
with self.assertRaises(redis.exceptions.ResponseError) as cm:
Expand All @@ -612,22 +612,22 @@ def testNumIncrCommand(self):
# with self.assertRaises(redis.exceptions.ResponseError) as cm:
# r.execute_command('JSON.NUMINCRBY', 'test', '.fuzz', 1)
#
# # test issue #9
# self.assertOk(r.execute_command('JSON.SET', 'num', '.', '0'))
# self.assertEqual('1', r.execute_command('JSON.NUMINCRBY', 'num', '.', 1))
# self.assertEqual('2.5', r.execute_command('JSON.NUMINCRBY', 'num', '.', 1.5))
#
# # test issue 55
# self.assertOk(r.execute_command('JSON.SET', 'foo', '.', '{"foo":0,"bar":42}'))
# # Get the document once
# r.execute_command('JSON.GET', 'foo', '.')
# self.assertEqual('1', r.execute_command('JSON.NUMINCRBY', 'foo', 'foo', 1))
# self.assertEqual('84', r.execute_command('JSON.NUMMULTBY', 'foo', 'bar', 2))
# res = json.loads(r.execute_command('JSON.GET', 'foo', '.'))
# self.assertEqual(1, res['foo'])
# self.assertEqual(84, res['bar'])
#
#
# test issue #9
self.assertOk(r.execute_command('JSON.SET', 'num', '.', '0'))
self.assertEqual('1', r.execute_command('JSON.NUMINCRBY', 'num', '.', 1))
self.assertEqual('2.5', r.execute_command('JSON.NUMINCRBY', 'num', '.', 1.5))

# test issue 55
self.assertOk(r.execute_command('JSON.SET', 'foo', '.', '{"foo":0,"bar":42}'))
# Get the document once
r.execute_command('JSON.GET', 'foo', '.')
self.assertEqual('1', r.execute_command('JSON.NUMINCRBY', 'foo', 'foo', 1))
self.assertEqual('84', r.execute_command('JSON.NUMMULTBY', 'foo', 'bar', 2))
res = json.loads(r.execute_command('JSON.GET', 'foo', '.'))
self.assertEqual(1, res['foo'])
self.assertEqual(84, res['bar'])


def testStrCommands(self):
"""Test JSON.STRAPPEND and JSON.STRLEN commands"""

Expand Down

0 comments on commit 2143ec1

Please sign in to comment.