Skip to content

Commit 0c761aa

Browse files
committed
eval::Rules - a Vec<Rule> but skips invalid Rule on Deserialization
1 parent 5b3767c commit 0c761aa

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

primitives/src/targeting/eval.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use std::{
1010
str::FromStr,
1111
};
1212

13+
pub use rules::Rules;
14+
1315
use super::{
1416
input::{channel::Getter as ChannelGetter, Get},
1517
Input, Output,
@@ -72,6 +74,59 @@ impl fmt::Display for Error {
7274

7375
impl std::error::Error for Error {}
7476

77+
mod rules {
78+
use serde::{
79+
de::{SeqAccess, Visitor},
80+
Deserialize, Deserializer, Serialize,
81+
};
82+
use std::fmt;
83+
84+
use super::Rule;
85+
86+
#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
87+
#[serde(transparent)]
88+
/// The Rules is just a `Vec<Rule>` with one difference:
89+
/// When Deserializing it will skip invalid `Rule` instead of returning an error
90+
pub struct Rules(Vec<Rule>);
91+
92+
impl<'de> Deserialize<'de> for Rules {
93+
fn deserialize<D>(deserializer: D) -> Result<Rules, D::Error>
94+
where
95+
D: Deserializer<'de>,
96+
{
97+
deserializer.deserialize_seq(RulesVisitor)
98+
}
99+
}
100+
101+
struct RulesVisitor;
102+
103+
impl<'de> Visitor<'de> for RulesVisitor {
104+
type Value = Rules;
105+
106+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
107+
formatter.write_str("a sequence of Rules")
108+
}
109+
110+
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
111+
where
112+
A: SeqAccess<'de>,
113+
{
114+
let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or(0));
115+
116+
// Since we want to filter wrong Rules, instead of returning an error
117+
// we transpose the `Result<Option<T>, ..>` to `Option<Result<T, ..>>`
118+
while let Some(result) = seq.next_element().transpose() {
119+
// push only valid rules
120+
if let Ok(rule) = result {
121+
vec.push(rule);
122+
}
123+
}
124+
125+
Ok(Rules(vec))
126+
}
127+
}
128+
}
129+
75130
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
76131
#[serde(untagged)]
77132
pub enum Rule {

primitives/src/targeting/eval_test.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,62 @@ fn get_default_input() -> Input {
3838
.with_balances(input_balances)
3939
}
4040

41+
mod rules_test {
42+
use super::{Function, Rule, Rules, Value};
43+
use serde_json::{from_value, json};
44+
45+
#[test]
46+
fn test_rules_should_be_empty_when_single_invalid_rule() {
47+
let rule = json!([
48+
{
49+
"onlyShowIf": {
50+
"undefined": [
51+
[],
52+
{"get":"userAgentOS"}
53+
]
54+
}
55+
}
56+
]);
57+
58+
let deser = from_value::<Rules>(rule).expect("should deserialize by skipping invalid rule");
59+
60+
assert!(deser.0.is_empty())
61+
}
62+
63+
#[test]
64+
fn test_rules_should_not_be_empty_when_one_invalid_rule() {
65+
let rule = json!([
66+
{
67+
"intersects": [
68+
{"get": "adSlot.categories"},
69+
["News", "Bitcoin"]
70+
]
71+
},
72+
{
73+
"onlyShowIf": {
74+
"undefined": [
75+
[],
76+
{"get":"userAgentOS"}
77+
]
78+
}
79+
}
80+
]);
81+
82+
let deser = from_value::<Rules>(rule).expect("should deserialize by skipping invalid rule");
83+
84+
assert_eq!(1, deser.0.len());
85+
86+
let expected = Rule::Function(Function::new_intersects(
87+
Rule::Function(Function::new_get("adSlot.categories")),
88+
Rule::Value(Value::Array(vec![
89+
Value::new_string("News"),
90+
Value::new_string("Bitcoin"),
91+
])),
92+
));
93+
assert_eq!(expected, deser.0[0])
94+
}
95+
}
96+
4197
mod dsl_test {
4298
use super::*;
4399

0 commit comments

Comments
 (0)