|
1 | 1 | use crate::BigNum;
|
2 | 2 | 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>; |
16 | 13 | }
|
17 | 14 |
|
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), |
23 | 40 | }
|
24 | 41 | }
|
25 | 42 | }
|
26 | 43 |
|
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>, |
42 | 57 | }
|
43 | 58 |
|
44 |
| -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] |
| 59 | +#[derive(Debug, Serialize, Deserialize)] |
| 60 | +#[cfg_attr(test, derive(Default))] |
45 | 61 | #[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, |
51 | 65 | }
|
52 | 66 |
|
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, |
71 | 89 | }
|
72 | 90 |
|
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 |
| - } |
80 | 91 |
|
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, |
87 | 99 | }
|
88 | 100 |
|
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>, |
148 | 114 | }
|
149 | 115 |
|
150 | 116 | #[cfg(test)]
|
151 | 117 | mod test {
|
152 | 118 | use super::*;
|
153 | 119 |
|
154 | 120 | #[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 | + }); |
178 | 126 |
|
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) |
217 | 132 | }
|
218 | 133 | }
|
0 commit comments