From ce4c22e2a60b534d57041064f1cb3b09416dc25e Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Wed, 14 Apr 2021 14:18:08 +0200 Subject: [PATCH] Add JSON support for exprs (fixes #146) --- Cargo.lock | 18 ++++++++++++ Cargo.toml | 1 + src/value/attr_value/attr_value_expr.rs | 27 +++++++++++++++++ src/value/attr_value/parser.rs | 39 ++++++++++++++++++++++--- src/value/primitive.rs | 16 ++++++++++ 5 files changed, 97 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39d7cd3..e51908c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,6 +326,7 @@ dependencies = [ "regex", "roxmltree", "serde", + "serde_json", "simple-signal", "smart-default", "structopt", @@ -830,6 +831,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + [[package]] name = "lasso" version = "0.3.1" @@ -1424,6 +1431,17 @@ dependencies = [ "syn 1.0.64", ] +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "signal-hook-registry" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 296cce2..48057b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ derive_more = "0.99" maplit = "1" structopt = "0.3" serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" extend = "1" grass = "0.10" num = "0.4" diff --git a/src/value/attr_value/attr_value_expr.rs b/src/value/attr_value/attr_value_expr.rs index 321b97f..770a806 100644 --- a/src/value/attr_value/attr_value_expr.rs +++ b/src/value/attr_value/attr_value_expr.rs @@ -58,6 +58,7 @@ pub enum AttrValueExpr { BinOp(Box, BinOp, Box), UnaryOp(UnaryOp, Box), IfElse(Box, Box, Box), + JsonAccessIndex(Box, Box), } impl std::fmt::Display for AttrValueExpr { @@ -68,6 +69,7 @@ impl std::fmt::Display for AttrValueExpr { AttrValueExpr::BinOp(l, op, r) => write!(f, "({} {} {})", l, op, r), AttrValueExpr::UnaryOp(op, x) => write!(f, "{}{}", op, x), AttrValueExpr::IfElse(a, b, c) => write!(f, "(if {} then {} else {})", a, b, c), + AttrValueExpr::JsonAccessIndex(value, index) => write!(f, "{}[{}]", value, index), } } } @@ -101,6 +103,7 @@ impl AttrValueExpr { IfElse(box a, box b, box c) => { Ok(IfElse(box a.resolve_refs(variables)?, box b.resolve_refs(variables)?, box c.resolve_refs(variables)?)) } + JsonAccessIndex(box a, box b) => Ok(JsonAccessIndex(box a.resolve_refs(variables)?, box b.resolve_refs(variables)?)), } } @@ -121,6 +124,11 @@ impl AttrValueExpr { refs.append(&mut c.var_refs()); refs } + JsonAccessIndex(box a, box b) => { + let mut refs = a.var_refs(); + refs.append(&mut b.var_refs()); + refs + } } } @@ -163,6 +171,25 @@ impl AttrValueExpr { no.eval(values) } } + AttrValueExpr::JsonAccessIndex(val, index) => { + let val = val.eval(values)?; + let index = index.eval(values)?; + match val.as_json_value()? { + serde_json::Value::Array(val) => { + let index = index.as_i32()?; + let indexed_value = val.get(index as usize).unwrap_or(&serde_json::Value::Null); + Ok(PrimitiveValue::from(indexed_value)) + } + serde_json::Value::Object(val) => { + let indexed_value = val + .get(&index.as_string()?) + .or_else(|| val.get(&format!("{}", index.as_i32().ok()?))) + .unwrap_or(&serde_json::Value::Null); + Ok(PrimitiveValue::from(indexed_value)) + } + _ => bail!("Unable to index into value {}", val), + } + } } } diff --git a/src/value/attr_value/parser.rs b/src/value/attr_value/parser.rs index 475e1aa..4132e82 100644 --- a/src/value/attr_value/parser.rs +++ b/src/value/attr_value/parser.rs @@ -70,12 +70,22 @@ fn parse_factor(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> { )) } -fn parse_term3(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> { +fn parse_object_index(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> { let (i, initial) = parse_factor(i)?; let (i, remainder) = many0(alt(( - map(preceded(tag("*"), parse_factor), |x| (BinOp::Times, x)), - map(preceded(tag("/"), parse_factor), |x| (BinOp::Div, x)), - map(preceded(tag("%"), parse_factor), |x| (BinOp::Mod, x)), + delimited(tag("["), ws(parse_expr), tag("]")), + map(preceded(tag("."), parse_identifier), |x| AttrValueExpr::Literal(AttrValue::from_primitive(x))), + )))(i)?; + let indexes = remainder.into_iter().fold(initial, |acc, index| AttrValueExpr::JsonAccessIndex(box acc, box index)); + Ok((i, indexes)) +} + +fn parse_term3(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> { + let (i, initial) = parse_object_index(i)?; + let (i, remainder) = many0(alt(( + map(preceded(tag("*"), parse_object_index), |x| (BinOp::Times, x)), + map(preceded(tag("/"), parse_object_index), |x| (BinOp::Div, x)), + map(preceded(tag("%"), parse_object_index), |x| (BinOp::Mod, x)), )))(i)?; let exprs = remainder.into_iter().fold(initial, |acc, (op, expr)| AttrValueExpr::BinOp(box acc, op, box expr)); @@ -172,6 +182,27 @@ mod test { ), AttrValueExpr::parse("if a then b else c").unwrap() ); + assert_eq!( + AttrValueExpr::JsonAccessIndex( + box AttrValueExpr::VarRef(VarName("array".to_string())), + box AttrValueExpr::BinOp( + box AttrValueExpr::Literal(AttrValue::from_primitive("1")), + BinOp::Plus, + box AttrValueExpr::Literal(AttrValue::from_primitive("2")) + ) + ), + AttrValueExpr::parse(r#"(array)[1+2]"#).unwrap() + ); + assert_eq!( + AttrValueExpr::JsonAccessIndex( + box AttrValueExpr::JsonAccessIndex( + box AttrValueExpr::VarRef(VarName("object".to_string())), + box AttrValueExpr::Literal(AttrValue::from_primitive("field".to_string())), + ), + box AttrValueExpr::Literal(AttrValue::from_primitive("field2".to_string())), + ), + AttrValueExpr::parse(r#"object.field.field2"#).unwrap() + ); } #[test] fn test_complex() { diff --git a/src/value/primitive.rs b/src/value/primitive.rs index fe51a4c..d818681 100644 --- a/src/value/primitive.rs +++ b/src/value/primitive.rs @@ -77,6 +77,17 @@ impl From<&str> for PrimitiveValue { } } +impl From<&serde_json::Value> for PrimitiveValue { + fn from(v: &serde_json::Value) -> Self { + PrimitiveValue( + v.as_str() + .map(|x| x.to_string()) + .or_else(|| serde_json::to_string(v).ok()) + .unwrap_or_else(|| "".to_string()), + ) + } +} + impl PrimitiveValue { pub fn from_string(s: String) -> Self { PrimitiveValue(s) @@ -106,6 +117,11 @@ impl PrimitiveValue { pub fn as_vec(&self) -> Result> { parse_vec(self.0.to_owned()).map_err(|e| anyhow!("Couldn't convert {:#?} to a vec: {}", &self, e)) } + + pub fn as_json_value(&self) -> Result { + serde_json::from_str::(&self.0) + .with_context(|| format!("Couldn't convert {:#?} to a json object", &self)) + } } fn parse_vec(a: String) -> Result> {