Skip to content

Commit 0fa4c96

Browse files
committed
primitives - impl TryGet & scoped structs
1 parent f4ece77 commit 0fa4c96

File tree

2 files changed

+381
-190
lines changed

2 files changed

+381
-190
lines changed

primitives/src/targeting.rs

Lines changed: 105 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -1,218 +1,133 @@
11
use crate::BigNum;
22
use serde::{Deserialize, Serialize};
3-
use serde_json::{
4-
value::{Map as SerdeMap, Value as SerdeValue},
5-
Number,
6-
};
7-
use std::convert::TryFrom;
8-
use std::fmt;
9-
10-
pub type Map = SerdeMap<String, SerdeValue>;
11-
12-
#[derive(Debug)]
13-
pub enum Error {
14-
TypeError,
15-
UnknownVariable,
3+
use std::{collections::HashMap, convert::TryFrom};
4+
5+
pub use eval::*;
6+
7+
mod eval;
8+
9+
pub trait TryGet {
10+
const PATTERN: &'static str;
11+
12+
fn try_get(&self, key: &str) -> Result<Value, Error>;
1613
}
1714

18-
impl fmt::Display for Error {
19-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20-
match self {
21-
Error::TypeError => write!(f, "TypeError: Wrong type"),
22-
Error::UnknownVariable => write!(f, "UnknownVariable: Unknown varialbe passed"),
15+
impl<T: Serialize> TryGet for T {
16+
const PATTERN: &'static str = ".";
17+
18+
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());
22+
let serde_value = serde_json::json!(self);
23+
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),
2340
}
2441
}
2542
}
2643

27-
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
28-
#[serde(try_from = "SerdeValue")]
29-
pub enum Value {
30-
Bool(bool),
31-
Number(Number),
32-
String(String),
33-
Array(Vec<Value>),
34-
BigNum(BigNum),
35-
}
36-
37-
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
38-
#[serde(untagged)]
39-
pub enum Rule {
40-
Function(Function),
41-
Value(Value),
44+
#[derive(Debug, Serialize, Deserialize)]
45+
#[cfg_attr(test, derive(Default))]
46+
#[serde(rename_all = "camelCase")]
47+
pub struct Input {
48+
/// AdView scope, accessible only on the AdView
49+
#[serde(default)]
50+
pub ad_view: Option<AdView>,
51+
/// Global scope, accessible everywhere
52+
#[serde(flatten)]
53+
pub global: Global,
54+
/// adSlot scope, accessible on Supermarket and AdView
55+
#[serde(default)]
56+
pub ad_slot: Option<AdSlot>,
4257
}
4358

44-
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
59+
#[derive(Debug, Serialize, Deserialize)]
60+
#[cfg_attr(test, derive(Default))]
4561
#[serde(rename_all = "camelCase")]
46-
pub enum Function {
47-
If(Box<Rule>, Box<Rule>),
48-
And(Box<Rule>, Box<Rule>),
49-
Intersects(Box<Rule>, Box<Rule>),
50-
Get(String),
62+
pub struct AdView {
63+
pub seconds_since_show: u64,
64+
pub has_custom_preferences: bool,
5165
}
5266

53-
impl TryFrom<SerdeValue> for Value {
54-
type Error = Error;
55-
56-
fn try_from(serde_value: SerdeValue) -> Result<Self, Self::Error> {
57-
match serde_value {
58-
SerdeValue::Bool(bool) => Ok(Self::Bool(bool)),
59-
SerdeValue::Number(number) => Ok(Self::Number(number)),
60-
SerdeValue::String(string) => Ok(Self::String(string)),
61-
SerdeValue::Array(serde_array) => {
62-
let array = serde_array
63-
.into_iter()
64-
.map(Value::try_from)
65-
.collect::<Result<_, _>>()?;
66-
Ok(Self::Array(array))
67-
}
68-
SerdeValue::Object(_) | SerdeValue::Null => Err(Error::TypeError),
69-
}
70-
}
67+
#[derive(Debug, Serialize, Deserialize)]
68+
#[cfg_attr(test, derive(Default))]
69+
#[serde(rename_all = "camelCase")]
70+
pub struct Global {
71+
pub ad_slot_id: String,
72+
pub ad_unit_id: String,
73+
pub ad_unit_type: String,
74+
pub publisher_id: String,
75+
pub advertiser_id: String,
76+
pub country: String,
77+
pub event_type: String,
78+
pub campaign_total_spent: String,
79+
pub campaign_seconds_active: u64,
80+
pub campaign_seconds_duration: u64,
81+
pub campaign_budget: BigNum,
82+
pub event_min_price: BigNum,
83+
pub event_max_price: BigNum,
84+
pub publisher_earned_from_campaign: BigNum,
85+
pub seconds_since_epoch: u64,
86+
#[serde(rename = "userAgentOS")]
87+
pub user_agent_os: String,
88+
pub user_agent_browser_family: String,
7189
}
7290

73-
impl Value {
74-
pub fn try_bool(self) -> Result<bool, Error> {
75-
match self {
76-
Self::Bool(b) => Ok(b),
77-
_ => Err(Error::TypeError),
78-
}
79-
}
8091

81-
pub fn try_array(self) -> Result<Vec<Value>, Error> {
82-
match self {
83-
Self::Array(array) => Ok(array),
84-
_ => Err(Error::TypeError),
85-
}
86-
}
92+
#[derive(Debug, Serialize, Deserialize)]
93+
#[cfg_attr(test, derive(Default))]
94+
#[serde(rename_all = "camelCase")]
95+
pub struct AdSlot {
96+
pub categories: Vec<String>,
97+
pub hostname: String,
98+
pub alexa_rank: f64,
8799
}
88100

89-
/// Evaluates a Rule to be applied and has 3 outcomes:
90-
/// - Does nothing
91-
/// Rules returned directly:
92-
/// - Bool
93-
/// - Number
94-
/// - String
95-
/// - Array
96-
/// - Mutates output
97-
/// - Throws an error
98-
pub fn eval(input: &Map, output: &mut Map, rule: &Rule) -> Result<Option<Value>, Error> {
99-
let function = match rule {
100-
Rule::Value(value) => return Ok(Some(value.clone())),
101-
Rule::Function(function) => function,
102-
};
103-
104-
// basic operators
105-
let value = match function {
106-
Function::If(first_rule, second_rule) => {
107-
let eval_if = eval(input, output, first_rule)?
108-
.ok_or(Error::TypeError)?
109-
.try_bool()?;
110-
111-
if eval_if {
112-
let bool = eval(input, output, second_rule)?
113-
.ok_or(Error::TypeError)?
114-
.try_bool()?;
115-
Some(Value::Bool(bool))
116-
} else {
117-
None
118-
}
119-
}
120-
Function::And(first_rule, second_rule) => {
121-
let a = eval(input, output, first_rule)?
122-
.ok_or(Error::TypeError)?
123-
.try_bool()?;
124-
let b = eval(input, output, second_rule)?
125-
.ok_or(Error::TypeError)?
126-
.try_bool()?;
127-
128-
Some(Value::Bool(a && b))
129-
}
130-
Function::Intersects(first_rule, second_rule) => {
131-
let a = eval(input, output, first_rule)?
132-
.ok_or(Error::TypeError)?
133-
.try_array()?;
134-
let b = eval(input, output, second_rule)?
135-
.ok_or(Error::TypeError)?
136-
.try_array()?;
137-
138-
Some(Value::Bool(a.iter().any(|x| b.contains(x))))
139-
}
140-
Function::Get(key) => {
141-
let input_value = input.get(key).ok_or(Error::UnknownVariable)?;
142-
143-
Some(Value::try_from(input_value.clone())?)
144-
}
145-
};
146-
147-
Ok(value)
101+
pub struct Output {
102+
/// Whether to show the ad
103+
/// Default: true
104+
pub show: bool,
105+
/// The boost is a number between 0 and 5 that increases the likelyhood for the ad
106+
/// to be chosen if there is random selection applied on the AdView (multiple ad candidates with the same price)
107+
/// Default: 1.0
108+
pub boost: f64,
109+
/// price.{eventType}
110+
/// For example: price.IMPRESSION
111+
/// The default is the min of the bound of event type:
112+
/// Default: pricingBounds.IMPRESSION.min
113+
pub price: HashMap<String, BigNum>,
148114
}
149115

150116
#[cfg(test)]
151117
mod test {
152118
use super::*;
153119

154120
#[test]
155-
fn deserialzes_intersects_rule() {
156-
let json = r#"{"intersects": [{ "get": "adSlot.categories" }, ["News", "Bitcoin"]]}"#;
157-
158-
let parsed_rule = serde_json::from_str::<Rule>(json).expect("Should deserialize");
159-
160-
let mut expected_map = SerdeMap::new();
161-
expected_map.insert(
162-
"get".to_string(),
163-
SerdeValue::String("adSlot.categories".to_string()),
164-
);
165-
166-
let expected = Rule::Function(Function::Intersects(
167-
Box::new(Rule::Function(Function::Get(
168-
"adSlot.categories".to_string(),
169-
))),
170-
Box::new(Rule::Value(Value::Array(vec![
171-
Value::String("News".to_string()),
172-
Value::String("Bitcoin".to_string()),
173-
]))),
174-
));
175-
176-
assert_eq!(expected, parsed_rule)
177-
}
121+
fn test_try_get_of_input() {
122+
let mut input = Input::default();
123+
input.ad_view = Some(AdView {
124+
seconds_since_show: 10, has_custom_preferences: false
125+
});
178126

179-
/// ```json
180-
/// {
181-
/// "intersects": [
182-
/// {
183-
/// "get": "publisherId"
184-
/// },
185-
/// [
186-
/// "0xd5860D6196A4900bf46617cEf088ee6E6b61C9d6",
187-
/// "0xd5860D6196A4900bf46617cEf088ee6E6b61C9d3"
188-
/// ]
189-
/// ]
190-
/// }
191-
/// ```
192-
#[test]
193-
fn test_simple_intersect_eval() {
194-
let input: Map = vec![(
195-
"publisherId".to_string(),
196-
SerdeValue::Array(vec![SerdeValue::String(
197-
"0xd5860D6196A4900bf46617cEf088ee6E6b61C9d6".to_string(),
198-
)]),
199-
)]
200-
.into_iter()
201-
.collect();
202-
let mut output = SerdeMap::new();
203-
204-
let publishers = vec![
205-
Value::String("0xd5860D6196A4900bf46617cEf088ee6E6b61C9d6".to_string()),
206-
Value::String("0xd5860D6196A4900bf46617cEf088ee6E6b61C9d3".to_string()),
207-
];
208-
209-
let rules = Rule::Function(Function::Intersects(
210-
Box::new(Rule::Function(Function::Get("publisherId".to_string()))),
211-
Box::new(Rule::Value(Value::Array(publishers))),
212-
));
213-
214-
let result = eval(&input, &mut output, &rules).expect("Should eval rules");
215-
216-
assert_eq!(Value::Bool(true), result.expect("Sould be Some!"));
127+
let result = input.try_get("adView.secondsSinceShow").expect("Should get the adView field");
128+
129+
let expected = serde_json::from_str::<serde_json::Number>("10").expect("Should create number");
130+
131+
assert_eq!(Value::Number(expected), result)
217132
}
218133
}

0 commit comments

Comments
 (0)