Skip to content

Commit ad73d8c

Browse files
committed
primitives::targeting::TryGet - use pointer +tests
1 parent 98aa294 commit ad73d8c

File tree

3 files changed

+114
-61
lines changed

3 files changed

+114
-61
lines changed

primitives/src/big_num.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,14 @@ impl TryFrom<&str> for BigNum {
197197
}
198198
}
199199

200+
impl FromStr for BigNum {
201+
type Err = super::DomainError;
202+
203+
fn from_str(s: &str) -> Result<Self, Self::Err> {
204+
BigNum::try_from(s)
205+
}
206+
}
207+
200208
impl ToString for BigNum {
201209
fn to_string(&self) -> String {
202210
self.0.to_str_radix(10)

primitives/src/targeting.rs

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,16 @@ pub use eval::*;
66

77
mod eval;
88

9-
pub trait TryGet {
10-
const PATTERN: &'static str;
11-
12-
fn try_get(&self, key: &str) -> Result<Value, Error>;
13-
}
14-
15-
impl<T: Serialize> TryGet for T {
9+
pub trait TryGet: Serialize {
1610
const PATTERN: &'static str = ".";
1711

1812
fn try_get(&self, key: &str) -> Result<Value, Error> {
19-
let mut splitn = key.splitn(2, '.');
20-
21-
let (field, remaining_key) = (splitn.next(), splitn.next());
2213
let serde_value = serde_json::json!(self);
14+
let pointer = format!("/{pointer}", pointer = key.replace(Self::PATTERN, "/"));
2315

24-
// filter empty string
25-
let field = field.filter(|s| !s.is_empty());
26-
let remaining_key = remaining_key.filter(|s| !s.is_empty());
27-
28-
// TODO: Check what type of error we should return in each case
29-
match (field, remaining_key, serde_value) {
30-
(Some(field), remaining_key, serde_json::Value::Object(map)) => {
31-
match map.get(field) {
32-
Some(serde_value) => serde_value.try_get(remaining_key.unwrap_or_default()),
33-
None => Err(Error::TypeError),
34-
}
35-
},
36-
(None, None, serde_value) => Value::try_from(serde_value.clone()),
37-
// if we have any other field or remaining_key, it's iligal:
38-
// i.e. `first.second` for values that are not map
39-
_ => Err(Error::UnknownVariable),
16+
match serde_value.pointer(&pointer) {
17+
Some(serde_value) => Value::try_from(serde_value.clone()),
18+
None => Err(Error::UnknownVariable),
4019
}
4120
}
4221
}
@@ -88,7 +67,6 @@ pub struct Global {
8867
pub user_agent_browser_family: String,
8968
}
9069

91-
9270
#[derive(Debug, Serialize, Deserialize)]
9371
#[cfg_attr(test, derive(Default))]
9472
#[serde(rename_all = "camelCase")]
@@ -98,6 +76,12 @@ pub struct AdSlot {
9876
pub alexa_rank: f64,
9977
}
10078

79+
impl TryGet for Input {}
80+
impl TryGet for Global {}
81+
impl TryGet for AdView {}
82+
impl TryGet for AdSlot {}
83+
84+
#[derive(Debug)]
10185
pub struct Output {
10286
/// Whether to show the ad
10387
/// Default: true
@@ -120,14 +104,38 @@ mod test {
120104
#[test]
121105
fn test_try_get_of_input() {
122106
let mut input = Input::default();
107+
input.global.ad_slot_id = "ad_slot_id Value".to_string();
108+
input.global.campaign_budget = BigNum::from(50);
123109
input.ad_view = Some(AdView {
124-
seconds_since_show: 10, has_custom_preferences: false
110+
seconds_since_show: 10,
111+
has_custom_preferences: false,
125112
});
126113

127-
let result = input.try_get("adView.secondsSinceShow").expect("Should get the adView field");
114+
let ad_view_seconds_since_show = input
115+
.try_get("adView.secondsSinceShow")
116+
.expect("Should get the ad_view.seconds_since_show field");
117+
118+
let expected_number =
119+
serde_json::from_str::<serde_json::Number>("10").expect("Should create number");
120+
121+
assert_eq!(Value::Number(expected_number), ad_view_seconds_since_show);
122+
123+
let ad_slot_id = input
124+
.try_get("adSlotId")
125+
.expect("Should get the global.ad_slot_id field");
126+
127+
assert_eq!(Value::String("ad_slot_id Value".to_string()), ad_slot_id);
128+
129+
let get_unknown = input
130+
.try_get("unknownField")
131+
.expect_err("Should return Error");
132+
133+
assert_eq!(Error::UnknownVariable, get_unknown);
128134

129-
let expected = serde_json::from_str::<serde_json::Number>("10").expect("Should create number");
135+
let global_campaign_budget = input
136+
.try_get("campaignBudget")
137+
.expect("Should get the global.campaign_budget field");
130138

131-
assert_eq!(Value::Number(expected), result)
139+
assert_eq!(Value::BigNum(BigNum::from(50)), global_campaign_budget);
132140
}
133141
}

primitives/src/targeting/eval.rs

Lines changed: 67 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ use crate::BigNum;
22
use serde::{Deserialize, Serialize};
33
use serde_json::{value::Value as SerdeValue, Number};
44
use std::convert::TryFrom;
5-
use std::fmt;
5+
use std::{fmt, str::FromStr};
66

77
pub type Map = serde_json::value::Map<String, SerdeValue>;
88

9-
#[derive(Debug)]
9+
use super::{Input, Output, TryGet};
10+
11+
#[derive(Debug, Eq, PartialEq)]
1012
pub enum Error {
1113
TypeError,
1214
UnknownVariable,
@@ -29,7 +31,7 @@ pub enum Rule {
2931
}
3032

3133
impl Rule {
32-
pub fn eval(&self, input: &Map, output: &mut Map) -> Result<Option<Value>, Error> {
34+
pub fn eval(&self, input: &Input, output: &mut Output) -> Result<Option<Value>, Error> {
3335
eval(input, output, self)
3436
}
3537
}
@@ -50,6 +52,34 @@ impl Value {
5052
}
5153
}
5254

55+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
56+
#[serde(from = "BigNum")]
57+
/// Bn (BigNum) function.
58+
/// This struct is also used to parse the Input correctly for [`TryGet`](primitives::targeting::TryGet).
59+
///
60+
/// This type will be:
61+
/// - Deserialized from a normal `BigNum`
62+
///
63+
/// ```json
64+
/// { "some_big_num_field": "1000" }
65+
/// ```
66+
///
67+
/// - Serialized to:
68+
///
69+
/// ```json
70+
/// { "FUNC": { "bn": "1000" } }
71+
/// ```
72+
pub struct Bn {
73+
#[serde(rename = "bn")]
74+
pub big_num: BigNum,
75+
}
76+
77+
impl From<BigNum> for Bn {
78+
fn from(big_num: BigNum) -> Self {
79+
Self { big_num }
80+
}
81+
}
82+
5383
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
5484
#[serde(rename_all = "camelCase")]
5585
pub enum Function {
@@ -58,8 +88,9 @@ pub enum Function {
5888
Intersects(Box<Rule>, Box<Rule>),
5989
Get(String),
6090
// TODO: set
61-
6291
// TODO: Add: div, mul, mod, add, sub, max, min
92+
/// Bn (BigNum) function.
93+
Bn(Bn),
6394
}
6495

6596
impl From<Function> for Rule {
@@ -99,7 +130,16 @@ impl TryFrom<SerdeValue> for Value {
99130
match serde_value {
100131
SerdeValue::Bool(bool) => Ok(Self::Bool(bool)),
101132
SerdeValue::Number(number) => Ok(Self::Number(number)),
102-
SerdeValue::String(string) => Ok(Self::String(string)),
133+
SerdeValue::String(string) => {
134+
// we need to try and parse the String as a BigNum
135+
// if it fails, then it's just a String
136+
let big_num = BigNum::from_str(&string);
137+
138+
match big_num {
139+
Ok(big_num) => Ok(Value::BigNum(big_num)),
140+
Err(_) => Ok(Value::String(string)),
141+
}
142+
}
103143
SerdeValue::Array(serde_array) => {
104144
let array = serde_array
105145
.into_iter()
@@ -137,7 +177,7 @@ impl Value {
137177
/// - Array
138178
/// - Mutates output
139179
/// - Throws an error
140-
fn eval(input: &Map, output: &mut Map, rule: &Rule) -> Result<Option<Value>, Error> {
180+
fn eval(input: &Input, output: &mut Output, rule: &Rule) -> Result<Option<Value>, Error> {
141181
let function = match rule {
142182
Rule::Value(value) => return Ok(Some(value.clone())),
143183
Rule::Function(function) => function,
@@ -179,11 +219,8 @@ fn eval(input: &Map, output: &mut Map, rule: &Rule) -> Result<Option<Value>, Err
179219

180220
Some(Value::Bool(a.iter().any(|x| b.contains(x))))
181221
}
182-
Function::Get(key) => {
183-
let input_value = input.get(key).ok_or(Error::UnknownVariable)?;
184-
185-
Some(Value::try_from(input_value.clone())?)
186-
}
222+
Function::Get(key) => Some(input.try_get(key)?),
223+
Function::Bn(bn) => Some(Value::BigNum(bn.big_num.clone())),
187224
};
188225

189226
Ok(value)
@@ -192,6 +229,7 @@ fn eval(input: &Map, output: &mut Map, rule: &Rule) -> Result<Option<Value>, Err
192229
#[cfg(test)]
193230
mod test {
194231
use super::*;
232+
use crate::targeting::AdSlot;
195233

196234
#[test]
197235
fn deserialzes_intersects_rule() {
@@ -231,16 +269,18 @@ mod test {
231269
/// ```
232270
#[test]
233271
fn test_intersects_eval() {
234-
let input: Map = vec![(
235-
"adSlot.categories".to_string(),
236-
SerdeValue::Array(vec![
237-
SerdeValue::String("Bitcoin".to_string()),
238-
SerdeValue::String("Ethereum".to_string()),
239-
]),
240-
)]
241-
.into_iter()
242-
.collect();
243-
let mut output = Map::new();
272+
let mut input = Input::default();
273+
input.ad_slot = Some(AdSlot {
274+
categories: vec!["Bitcoin".to_string(), "Ethereum".to_string()],
275+
hostname: Default::default(),
276+
alexa_rank: 0.0,
277+
});
278+
279+
let mut output = Output {
280+
show: true,
281+
boost: 1.0,
282+
price: Default::default(),
283+
};
244284

245285
let categories = vec![Value::new_string("News"), Value::new_string("Bitcoin")];
246286

@@ -256,15 +296,12 @@ mod test {
256296
result.expect("Sould return Non-NULL result!")
257297
);
258298

259-
let input: Map = vec![(
260-
"adSlot.categories".to_string(),
261-
SerdeValue::Array(vec![
262-
SerdeValue::String("Advertisement".to_string()),
263-
SerdeValue::String("Programming".to_string()),
264-
]),
265-
)]
266-
.into_iter()
267-
.collect();
299+
let mut input = Input::default();
300+
input.ad_slot = Some(AdSlot {
301+
categories: vec!["Advertisement".to_string(), "Programming".to_string()],
302+
hostname: Default::default(),
303+
alexa_rank: 0.0,
304+
});
268305

269306
let result = rules.eval(&input, &mut output).expect("Should eval rules");
270307

0 commit comments

Comments
 (0)