promoted-ruby-client 0.1.1 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/Gemfile.lock +2 -4
- data/README.md +206 -169
- data/dev.md +2 -3
- data/lib/promoted/ruby/client.rb +126 -73
- data/lib/promoted/ruby/client/errors.rb +8 -27
- data/lib/promoted/ruby/client/faraday_http_client.rb +8 -3
- data/lib/promoted/ruby/client/request_builder.rb +22 -33
- data/lib/promoted/ruby/client/validator.rb +159 -0
- data/lib/promoted/ruby/client/version.rb +1 -1
- metadata +3 -3
- data/lib/promoted/ruby/client/settings.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30ba0e172809a6aa76284b4b88980fa45dea5b2bf28cdda4fbe8e672b7510a83
|
4
|
+
data.tar.gz: e28586ed23f7db16b959bb9819072e502fc7af3805e68938f2bbf421fdc9e4d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e6b42f73ff30726aa81af1c08b97a0cd0e0307685dec1f90dcce5d0d61d4009c3a2e6179af77a600038531101db187a83dc52f46a7c0ad4a99f7c6c2530b43e
|
7
|
+
data.tar.gz: f3d304f0a2aad7acc15229750f97b021da4c0bfe8deae40c6ac1110bc7c1621ab0db19635464ba835e4e9a9ec07b9cfa0c231a9163fa0e1dcffeeebfdc1e51fc
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
promoted-ruby-client (0.1.
|
4
|
+
promoted-ruby-client (0.1.6)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -9,7 +9,6 @@ GEM
|
|
9
9
|
ast (2.4.2)
|
10
10
|
backport (1.2.0)
|
11
11
|
benchmark (0.1.1)
|
12
|
-
byebug (11.1.3)
|
13
12
|
concurrent-ruby (1.1.9)
|
14
13
|
debase (0.2.5.beta2)
|
15
14
|
debase-ruby_core_source (>= 0.10.12)
|
@@ -111,7 +110,6 @@ PLATFORMS
|
|
111
110
|
|
112
111
|
DEPENDENCIES
|
113
112
|
bundler (~> 1.17)
|
114
|
-
byebug
|
115
113
|
concurrent-ruby
|
116
114
|
debase (>= 0.2.5.beta2)
|
117
115
|
faraday (~> 1.4.1)
|
@@ -126,4 +124,4 @@ DEPENDENCIES
|
|
126
124
|
solargraph
|
127
125
|
|
128
126
|
BUNDLED WITH
|
129
|
-
1.17.
|
127
|
+
1.17.3
|
data/README.md
CHANGED
@@ -2,51 +2,166 @@
|
|
2
2
|
|
3
3
|
Ruby client designed for calling Promoted's Delivery and Metrics API.
|
4
4
|
|
5
|
-
|
5
|
+
More information at [http://www.promoted.ai](http://www.promoted.ai)
|
6
6
|
|
7
|
-
##
|
7
|
+
## Installation
|
8
|
+
```gem 'promoted-ruby-client'```
|
8
9
|
|
9
|
-
|
10
|
+
## Local Development
|
11
|
+
1. Clone or fork the repo on your local machine
|
12
|
+
2. `cd promoted-ruby-client`
|
13
|
+
3. `bundle`
|
14
|
+
4. To test interactively: `irb -Ilib -rpromoted/ruby/client`
|
10
15
|
|
16
|
+
## Dependencies
|
17
|
+
|
18
|
+
### [Faraday](https://github.com/lostisland/faraday)
|
19
|
+
HTTP client for calling Promoted.
|
20
|
+
### [Concurrent Ruby](https://github.com/ruby-concurrency/concurrent-ruby)
|
21
|
+
Provides a thread pool for making shadow traffic requests to Delivery API in the background on a subset of calls to ```prepare_for_logging```
|
22
|
+
## Creating a Client
|
23
|
+
```rb
|
24
|
+
client = Promoted::Ruby::Client::PromotedClient.new
|
11
25
|
```
|
12
|
-
def get_items args
|
13
|
-
items = retrieve_items((args))
|
14
|
-
async_log_request(items)
|
15
|
-
return items
|
16
|
-
end
|
17
26
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
This client will suffice for building log requests. To send actually send traffing to the API, some configuration is required.
|
28
|
+
|
29
|
+
```rb
|
30
|
+
client = Promoted::Ruby::Client::PromotedClient.new({
|
31
|
+
:metrics_endpoint = "https://<get this from Promoted>",
|
32
|
+
:delivery_endpoint = "https://<get this from Promoted>",
|
33
|
+
:metrics_api_key = "<get this from Promoted>",
|
34
|
+
:delivery_api_key = "<get this from Promoted>"
|
35
|
+
})
|
25
36
|
```
|
26
37
|
|
27
|
-
|
38
|
+
### Client Configuration Parameters
|
39
|
+
Name | Type | Description
|
40
|
+
---- | ---- | -----------
|
41
|
+
```:delivery_endpoint``` | String | POST URL for the Promoted Delivery API (get this from Promoted)
|
42
|
+
```:metrics_endpoint``` | String | POST URL for the Promoted Metrics API (get this from Promoted)
|
43
|
+
```:metrics_api_key``` | String | Used as the ```x-api-key``` header on Metrics API requests to Promoted (get this value from Promoted)
|
44
|
+
```:delivery_api_key``` | String | Used as the ```x-api-key``` header on Delivery API requests to Promoted (get this value from Promoted)
|
45
|
+
```:delivery_timeout_millis``` | Number | Timeout on the Delivery API call. Defaults to 3000.
|
46
|
+
```:metrics_timeout_millis``` | Number | Timeout on the Metrics API call. Defaults to 3000.
|
47
|
+
```:perform_checks``` | Boolean | Whether or not to perform detailed input validation, defaults to true but may be disabled for performance
|
48
|
+
```:logger``` | Ruby Logger-compatible logger | Defaults to nil (no logging). Example: ```Logger.new(STDERR, :progname => 'promotedai')```
|
49
|
+
```:shadow_traffic_delivery_percent``` | Number between 0 and 1 | % of ```prepare_for_logging``` traffic that gets directed to Delivery API as "shadow traffic". Defaults to 0 (no shadow traffic).
|
50
|
+
```:default_request_headers``` | Hash | Additional headers to send on the request beyond ```x-api-key```. Defaults to {}
|
51
|
+
```:default_only_log``` | Boolean | If true, the ```deliver``` method will not direct traffic to Delivery API but rather return a request suitable for logging. Defaults to false.
|
52
|
+
```:should_apply_treatment_func``` | Proc | Called during delivery, accepts an experiment and returns a Boolean indicating whether the request should be considered part of the control group (false) or in the experiment (true). If nil, the default behavior of checking the experiement ```:arm``` is applied.
|
28
53
|
|
29
|
-
|
54
|
+
## Data Types
|
30
55
|
|
31
|
-
|
56
|
+
### UserInfo
|
57
|
+
Basic information about the request user.
|
58
|
+
Field Name | Type | Optional? | Description
|
59
|
+
---------- | ---- | --------- | -----------
|
60
|
+
```:user_id``` | String | Yes | The platform user id, cleared from Promoted logs.
|
61
|
+
```:log_user_id``` | String | Yes | A different user id (presumably a UUID) disconnected from the platform user id, good for working with unauthenticated users or implementing right-to-be-forgotten.
|
32
62
|
|
33
|
-
|
63
|
+
---
|
64
|
+
### CohortMembership
|
65
|
+
Useful fields for experimentation during the delivery phase.
|
66
|
+
Field Name | Type | Optional? | Description
|
67
|
+
---------- | ---- | --------- | -----------
|
68
|
+
```:user_info``` | UserInfo | Yes | The user info structure.
|
69
|
+
```:arm``` | String | Yes | 'CONTROL' or one of the TREATMENT values (see [constants.rb](https://github.com/promotedai/promoted-ruby-client/blob/main/lib/promoted/ruby/client/constants.rb)).
|
70
|
+
---
|
71
|
+
### Properties
|
72
|
+
Properties bag. Has the structure:
|
34
73
|
|
35
|
-
|
74
|
+
```rb
|
75
|
+
:struct => {
|
76
|
+
:product => {
|
77
|
+
"id": "product3",
|
78
|
+
"title": "Product 3",
|
79
|
+
"url": "www.mymarket.com/p/3"
|
80
|
+
# other key-value pairs...
|
81
|
+
}
|
82
|
+
}
|
83
|
+
```
|
84
|
+
---
|
85
|
+
### Insertion
|
86
|
+
Content being served at a certain position.
|
87
|
+
Field Name | Type | Optional? | Description
|
88
|
+
---------- | ---- | --------- | -----------
|
89
|
+
```:user_info``` | UserInfo | Yes | The user info structure.
|
90
|
+
```:insertion_id``` | String | Yes | Generated by the SDK (*do not set*)
|
91
|
+
```:request_id``` | String | Yes | Generated by the SDK (*do not set*)
|
92
|
+
```:content_id``` | String | No | Identifier for the content to be shown, must be set.
|
93
|
+
```:properties``` | Properties | Yes | Any additional custom properties to associate. For v1 integrations, it is fine not to fill in all the properties.
|
36
94
|
|
37
|
-
|
38
|
-
|
95
|
+
---
|
96
|
+
### Paging
|
97
|
+
#### TODO
|
98
|
+
---
|
99
|
+
### Request
|
100
|
+
A request for content insertions.
|
101
|
+
Field Name | Type | Optional? | Description
|
102
|
+
---------- | ---- | --------- | -----------
|
103
|
+
```:user_info``` | UserInfo | Yes | The user info structure.
|
104
|
+
```:request_id``` | String | Yes | Generated by the SDK (*do not set*)
|
105
|
+
```:use_case``` | String | Yes | One of the use case values, i.e. 'FEED' (see [constants.rb](https://github.com/promotedai/promoted-ruby-client/blob/main/lib/promoted/ruby/client/constants.rb)).
|
106
|
+
```:properties``` | Properties | Yes | Any additional custom properties to associate.
|
107
|
+
```:paging``` | Paging | Yes | Paging parameters (see TODO)
|
108
|
+
---
|
109
|
+
### MetricsRequest
|
110
|
+
Input to ```prepare_for_logging```
|
111
|
+
Field Name | Type | Optional? | Description
|
112
|
+
---------- | ---- | --------- | -----------
|
113
|
+
```:request``` | Request | No | The underlying request for content.
|
114
|
+
```:full_insertion``` | [] of Insertion | No | The proposed list of insertions.
|
115
|
+
---
|
39
116
|
|
40
|
-
|
117
|
+
### DeliveryRequest
|
118
|
+
Input to ```deliver```
|
119
|
+
Field Name | Type | Optional? | Description
|
120
|
+
---------- | ---- | --------- | -----------
|
121
|
+
```:experiment``` | CohortMembership | Yes | A cohort to evaluation in experimentation.
|
122
|
+
```:request``` | Request | No | The underlying request for content.
|
123
|
+
```:full_insertion``` | [] of Insertion | No | The proposed list of insertions with all metadata, will be compacted before forwarding to Promoted.
|
124
|
+
```:only_log``` | Boolean | Yes | Defaults to false. Set to true to override whether Delivery API is called for this request.
|
125
|
+
---
|
41
126
|
|
42
|
-
|
43
|
-
2. `cd promoted-ruby-client`
|
44
|
-
3. `bundle`
|
45
|
-
4. `irb -Ilib -rpromoted/ruby/client`
|
127
|
+
### LogRequest
|
46
128
|
|
47
|
-
|
129
|
+
Output of ```prepare_for_logging``` as well as an ouput of an SDK call to ```deliver```, input to ```send_log_request``` to log to Promoted
|
130
|
+
Field Name | Type | Optional? | Description
|
131
|
+
---------- | ---- | --------- | -----------
|
132
|
+
```:request``` | Request | No | The underlying request for content to log.
|
133
|
+
```:insertion``` | [] of Insertion | No | The insertions, which are either the original request insertions or the insertions resulting from a call to ```deliver``` if such call occurred.
|
134
|
+
---
|
135
|
+
|
136
|
+
### ClientResponse
|
137
|
+
Output of ```deliver```, includes the insertions as well as a suitable ```LogRequest``` for forwarding to Metrics API.
|
138
|
+
Field Name | Type | Optional? | Description
|
139
|
+
---------- | ---- | --------- | -----------
|
140
|
+
```:insertion``` | [] of Insertion | No | The insertions, which are from Delivery API (when ```deliver``` was called, i.e. we weren't either only-log or part of an experiment) or the input insertions (when the other conditions don't hold).
|
141
|
+
```:log_request``` | LogRequest | Yes | A message suitable for logging to Metrics API via ```send_log_request```. If the call to ```deliver``` was made (i.e. the request was not part of the CONTROL arm of an experiment or marked to only log), ```:log_request``` will not be set, as you can assume logging was performed on the server-side by Promoted.
|
142
|
+
---
|
143
|
+
|
144
|
+
### PromotedClient
|
145
|
+
Method | Input | Output | Description
|
146
|
+
------ | ----- | ------ | -----------
|
147
|
+
```prepare_for_logging``` | MetricsRequest | LogRequest | Builds a request suitable for logging locally and/or to Promoted, either via a subsequent call to ```send_log_request``` in the SDK client or by using this structure to make the call yourself. Optionally, based on client configuration may send a random subset of requests to Delivery API as shadow traffic for integration purposes.
|
148
|
+
```send_log_request``` | LogRequest | n/a | Forwards a LogRequest to Promoted using an HTTP client.
|
149
|
+
```deliver``` | DeliveryRequest | ClientResponse | Makes a request (subject to experimentation) to Delivery API for insertions, which are then returned along with a LogRequest.
|
150
|
+
```close``` | n/a | n/a | Closes down the client at shutdown, currently this is just to drain the thread pool that handles shadow traffic.
|
151
|
+
---
|
152
|
+
|
153
|
+
## Metrics API
|
154
|
+
### Pagination
|
155
|
+
|
156
|
+
The `prepare_for_logging` call assumes the client has already handled pagination. It needs a `Request.paging.offset` to be passed in for the number of items deep that the page is.
|
157
|
+
TODO: Needs more details.
|
158
|
+
|
159
|
+
### Expected flow for Metrics logging
|
160
|
+
|
161
|
+
```rb
|
162
|
+
# Retrieve a list of content (i.e. products)
|
163
|
+
# products = fetch_my_marketplace_products()
|
48
164
|
|
49
|
-
```
|
50
165
|
products = [
|
51
166
|
{
|
52
167
|
id: "123",
|
@@ -68,169 +183,91 @@ products = [
|
|
68
183
|
}
|
69
184
|
]
|
70
185
|
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
}
|
186
|
+
# Transform them into an [] of Insertions.
|
187
|
+
insertions = products.map { |product|
|
188
|
+
{
|
189
|
+
:content_id => product[:id],
|
190
|
+
:properties => {
|
191
|
+
:struct => {
|
192
|
+
:type => product[:type],
|
193
|
+
:name => product[:name]
|
194
|
+
# etc
|
81
195
|
}
|
82
196
|
}
|
83
|
-
|
84
|
-
|
85
|
-
end
|
197
|
+
}
|
198
|
+
}
|
86
199
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
200
|
+
# Form a MetricsRequest
|
201
|
+
metrics_request = {
|
202
|
+
:request => {
|
203
|
+
:user_info => { :user_id => "912", :log_user_id => "912191"},
|
204
|
+
:use_case => "FEED",
|
205
|
+
:paging => {
|
206
|
+
:offset => 0,
|
207
|
+
:size => 5
|
94
208
|
},
|
95
|
-
properties
|
96
|
-
struct
|
97
|
-
active
|
209
|
+
:properties => {
|
210
|
+
:struct => {
|
211
|
+
:active => true
|
98
212
|
}
|
99
213
|
}
|
100
214
|
},
|
101
|
-
full_insertion
|
215
|
+
:full_insertion => insertions
|
102
216
|
}
|
103
217
|
|
104
|
-
|
105
|
-
|
106
|
-
log_request.to_json
|
107
|
-
```
|
108
|
-
|
109
|
-
## Pass a custom function for compact insertion
|
110
|
-
```
|
111
|
-
# define a function using proc and it should use one argument.
|
112
|
-
# Example
|
218
|
+
# OPTIONAL: You can pass a custom function to "compact" insertions before metrics logging.
|
219
|
+
# Note that the PromotedClient has a class method helper, copy_and_remove_properties, that does just this.
|
113
220
|
to_compact_metrics_insertion_func = Proc.new do |insertion|
|
114
221
|
insertion.delete(:properties)
|
115
222
|
insertion
|
116
223
|
end
|
224
|
+
# metrics_request[:to_compact_metrics_insertion_func] = to_compact_metrics_insertion
|
117
225
|
|
118
|
-
|
119
|
-
to_compact_metrics_insertion_func: to_compact_metrics_insertion_func,
|
120
|
-
request: {
|
121
|
-
user_info: { user_id: "912", log_user_id: "912191"},
|
122
|
-
use_case: "FEED",
|
123
|
-
paging: {
|
124
|
-
from: 10,
|
125
|
-
size: 5
|
126
|
-
},
|
127
|
-
properties: {
|
128
|
-
struct: {
|
129
|
-
active: true
|
130
|
-
}
|
131
|
-
}
|
132
|
-
},
|
133
|
-
full_insertion: to_insertions(products)
|
134
|
-
}
|
135
|
-
|
226
|
+
# Create a client
|
136
227
|
client = Promoted::Ruby::Client::PromotedClient.new
|
137
|
-
log_request = client.prepare_for_logging(request_input)
|
138
|
-
log_request.to_json
|
139
|
-
```
|
140
228
|
|
141
|
-
|
142
|
-
|
143
|
-
|
229
|
+
# Build a log request
|
230
|
+
log_request = client.prepare_for_logging(metrics_request)
|
231
|
+
|
232
|
+
# Log (assuming you have configured your client with a :metrics_endpoint)
|
233
|
+
client.send_log_request(log_request)
|
144
234
|
```
|
145
235
|
|
146
|
-
##
|
236
|
+
## Delivery API
|
147
237
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
"id"=>"123",
|
153
|
-
"type"=>"SHOE",
|
154
|
-
"name"=>"Blue shoe",
|
155
|
-
"totalSales"=>1000
|
156
|
-
},
|
157
|
-
{
|
158
|
-
"id"=>"124",
|
159
|
-
"type"=>"SHIRT",
|
160
|
-
"name"=>"Green shirt",
|
161
|
-
"totalSales"=>800
|
162
|
-
},
|
163
|
-
{
|
164
|
-
"id"=>"125",
|
165
|
-
"type"=>"DRESS",
|
166
|
-
"name"=>"Red dress",
|
167
|
-
"totalSales"=>1200
|
168
|
-
}
|
169
|
-
]
|
238
|
+
### Expected flow for Delivery
|
239
|
+
|
240
|
+
```rb
|
241
|
+
# (continuing from the above example for Metrics)
|
170
242
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
"
|
175
|
-
"
|
176
|
-
|
177
|
-
|
243
|
+
# Form a DeliveryRequest
|
244
|
+
delivery_request = {
|
245
|
+
:request => {
|
246
|
+
:user_info => { :user_id => "912", :log_user_id => "912191"},
|
247
|
+
:use_case => "FEED",
|
248
|
+
:paging => {
|
249
|
+
:offset => 0,
|
250
|
+
:size => 5
|
251
|
+
},
|
252
|
+
:properties => {
|
253
|
+
:struct => {
|
254
|
+
:active => true
|
178
255
|
}
|
179
256
|
}
|
180
257
|
},
|
181
|
-
|
258
|
+
:full_insertion => insertions,
|
259
|
+
:only_log => false
|
182
260
|
}
|
183
|
-
```
|
184
261
|
|
185
|
-
|
262
|
+
# Request insertions from Delivery API
|
263
|
+
client_response = client.deliver(delivery_request)
|
264
|
+
|
265
|
+
# Use the resulting insertions
|
266
|
+
client_response[:insertion]
|
267
|
+
|
268
|
+
# Log if a log request was provided (if not, deliver was called successfully
|
269
|
+
# and Promoted logged on the server-side).)
|
270
|
+
client.send_log_request(client_response[:log_request]) if client_response[:log_request]
|
186
271
|
```
|
187
|
-
|
188
|
-
|
189
|
-
"user_info"=>{"user_id"=> "912", "log_user_id"=> "912191"},
|
190
|
-
"use_case"=>"FEED",
|
191
|
-
"properties"=>{
|
192
|
-
"struct"=>{
|
193
|
-
"active"=>true
|
194
|
-
}
|
195
|
-
}
|
196
|
-
},
|
197
|
-
"full_insertion"=>[
|
198
|
-
{
|
199
|
-
"contentId"=>"123",
|
200
|
-
"properties"=>{
|
201
|
-
"struct"=>{
|
202
|
-
"product"=>{
|
203
|
-
"type"=>"SHOE",
|
204
|
-
"name"=>"Blue shoe",
|
205
|
-
"totalSales"=>1000
|
206
|
-
}
|
207
|
-
}
|
208
|
-
}
|
209
|
-
},
|
210
|
-
{
|
211
|
-
"contentId"=>"124",
|
212
|
-
"properties"=>{
|
213
|
-
"struct"=>{
|
214
|
-
"product"=>{
|
215
|
-
"type"=>"SHIRT",
|
216
|
-
"name"=>"Green shirt",
|
217
|
-
"totalSales"=>800
|
218
|
-
}
|
219
|
-
}
|
220
|
-
}
|
221
|
-
},
|
222
|
-
{
|
223
|
-
"contentId"=>"125",
|
224
|
-
"properties"=>{
|
225
|
-
"struct"=>{
|
226
|
-
"product"=>{
|
227
|
-
"type"=>"DRESS",
|
228
|
-
"name"=>"Red dress",
|
229
|
-
"totalSales"=>1200
|
230
|
-
}
|
231
|
-
}
|
232
|
-
}
|
233
|
-
}
|
234
|
-
]
|
235
|
-
}
|
236
|
-
```
|
272
|
+
|
273
|
+
### TODO Experimentation example
|
data/dev.md
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
|
2
|
-
## Deploy
|
1
|
+
# Deploy
|
3
2
|
|
4
3
|
1. Update version number.
|
5
4
|
2. Get credentials for deployment from 1password.
|
6
5
|
3. Modify `promoted-ruby-client.gemspec`'s push block.
|
7
6
|
4. Run `gem build promoted-ruby-client.gemspec` to generate `gem`.
|
8
|
-
5. Run (using new output) `gem push promoted-ruby-client-0.1.
|
7
|
+
5. Run (using new output) `gem push promoted-ruby-client-0.1.6.gem`
|
9
8
|
6. Update README with new version.
|
data/lib/promoted/ruby/client.rb
CHANGED
@@ -6,26 +6,44 @@ module Promoted
|
|
6
6
|
module Ruby
|
7
7
|
module Client
|
8
8
|
|
9
|
-
DEFAULT_DELIVERY_TIMEOUT_MILLIS =
|
9
|
+
DEFAULT_DELIVERY_TIMEOUT_MILLIS = 250
|
10
10
|
DEFAULT_METRICS_TIMEOUT_MILLIS = 3000
|
11
11
|
DEFAULT_DELIVERY_ENDPOINT = "http://delivery.example.com"
|
12
12
|
DEFAULT_METRICS_ENDPOINT = "http://metrics.example.com"
|
13
13
|
|
14
|
+
##
|
15
|
+
# Client for working with Promoted's Metrics and Delivery APIs.
|
16
|
+
# See {Github}[https://github.com/promotedai/promoted-ruby-client] for more info.
|
14
17
|
class PromotedClient
|
15
18
|
|
16
19
|
class Error < StandardError; end
|
17
20
|
|
18
21
|
attr_reader :perform_checks, :default_only_log, :delivery_timeout_millis, :metrics_timeout_millis, :should_apply_treatment_func,
|
19
|
-
:default_request_headers
|
22
|
+
:default_request_headers, :http_client
|
20
23
|
|
24
|
+
##
|
25
|
+
# A common compact method implementation.
|
26
|
+
def self.copy_and_remove_properties
|
27
|
+
Proc.new do |insertion|
|
28
|
+
insertion = Hash[insertion]
|
29
|
+
insertion.delete(:properties)
|
30
|
+
insertion
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Create and configure a new Promoted client.
|
21
36
|
def initialize (params={})
|
22
37
|
@perform_checks = true
|
23
38
|
if params[:perform_checks] != nil
|
24
39
|
@perform_checks = params[:perform_checks]
|
25
40
|
end
|
26
41
|
|
42
|
+
@logger = params[:logger] # Example: Logger.new(STDERR, :progname => "promotedai")
|
43
|
+
|
27
44
|
@default_request_headers = params[:default_request_headers] || {}
|
28
|
-
@
|
45
|
+
@metrics_api_key = params[:metrics_api_key] || ''
|
46
|
+
@delivery_api_key = params[:delivery_api_key] || ''
|
29
47
|
|
30
48
|
@default_only_log = params[:default_only_log] || false
|
31
49
|
@should_apply_treatment_func = params[:should_apply_treatment_func]
|
@@ -46,35 +64,34 @@ module Promoted
|
|
46
64
|
@metrics_timeout_millis = params[:metrics_timeout_millis] || DEFAULT_METRICS_TIMEOUT_MILLIS
|
47
65
|
|
48
66
|
@http_client = FaradayHTTPClient.new
|
49
|
-
@
|
67
|
+
@validator = Promoted::Ruby::Client::Validator.new
|
68
|
+
|
69
|
+
# Thread pool to process delivery of shadow traffic. Will silently drop excess requests beyond the queue
|
70
|
+
# size, and silently eat errors on the background threads.
|
71
|
+
@pool = Concurrent::ThreadPoolExecutor.new(
|
72
|
+
min_threads: 0,
|
73
|
+
max_threads: 10,
|
74
|
+
max_queue: 100,
|
75
|
+
fallback_policy: :discard
|
76
|
+
)
|
50
77
|
end
|
51
78
|
|
79
|
+
##
|
80
|
+
# Politely shut down a Promoted client.
|
52
81
|
def close
|
53
82
|
@pool.shutdown
|
54
83
|
@pool.wait_for_termination
|
55
84
|
end
|
56
85
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
if send_async
|
61
|
-
@pool.post do
|
62
|
-
@http_client.send(endpoint, timeout_millis, payload, use_headers)
|
63
|
-
end
|
64
|
-
else
|
65
|
-
@http_client.send(endpoint, timeout_millis, payload, use_headers)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
86
|
+
##
|
87
|
+
# Make a delivery request.
|
69
88
|
def deliver args, headers={}
|
70
89
|
args = Promoted::Ruby::Client::Util.translate_args(args)
|
71
90
|
|
72
91
|
delivery_request_builder = RequestBuilder.new
|
73
92
|
delivery_request_builder.set_request_params(args)
|
74
93
|
|
75
|
-
if perform_checks
|
76
|
-
Promoted::Ruby::Client::Settings.check_that_log_ids_not_set!(args)
|
77
|
-
end
|
94
|
+
perform_common_checks!(args) if @perform_checks
|
78
95
|
|
79
96
|
pre_delivery_fillin_fields delivery_request_builder
|
80
97
|
|
@@ -83,22 +100,31 @@ module Promoted
|
|
83
100
|
insertions_from_promoted = false
|
84
101
|
|
85
102
|
only_log = delivery_request_builder.only_log != nil ? delivery_request_builder.only_log : @default_only_log
|
103
|
+
deliver_err = false
|
86
104
|
if !only_log
|
87
105
|
cohort_membership_to_log = delivery_request_builder.new_cohort_membership_to_log
|
88
|
-
|
106
|
+
|
107
|
+
if should_apply_treatment(cohort_membership_to_log)
|
108
|
+
delivery_request_params = delivery_request_builder.delivery_request_params
|
89
109
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
110
|
+
# Call Delivery API
|
111
|
+
begin
|
112
|
+
response = send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, @delivery_api_key, headers)
|
113
|
+
rescue StandardError => err
|
114
|
+
# Currently we don't propagate errors to the SDK caller, but rather default to returning
|
115
|
+
# the request insertions.
|
116
|
+
deliver_err = true
|
117
|
+
@logger.error("Error calling delivery: " + err.message) if @logger
|
118
|
+
end
|
119
|
+
|
120
|
+
insertions_from_promoted = (response != nil && !deliver_err);
|
121
|
+
response_insertions = delivery_request_builder.fill_details_from_response(
|
122
|
+
response ? response[:insertion] : [])
|
123
|
+
end
|
98
124
|
end
|
99
125
|
|
100
126
|
request_to_log = nil
|
101
|
-
if !insertions_from_promoted
|
127
|
+
if !insertions_from_promoted then
|
102
128
|
request_to_log = delivery_request_builder.request
|
103
129
|
size = delivery_request_builder.request.dig(:paging, :size)
|
104
130
|
response_insertions = size != nil ? delivery_request_builder.full_insertion[0..size] : delivery_request_builder.full_insertion
|
@@ -141,21 +167,73 @@ module Promoted
|
|
141
167
|
return client_response
|
142
168
|
end
|
143
169
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
170
|
+
##
|
171
|
+
# Generate a log request for a subsequent call to send_log_request
|
172
|
+
# or for logging via alternative means.
|
173
|
+
def prepare_for_logging args, headers={}
|
174
|
+
args = Promoted::Ruby::Client::Util.translate_args(args)
|
175
|
+
|
176
|
+
log_request_builder = RequestBuilder.new
|
177
|
+
|
178
|
+
# Note: This method expects as JSON (string keys) but internally, RequestBuilder
|
179
|
+
# transforms and works with symbol keys.
|
180
|
+
log_request_builder.set_request_params(args)
|
181
|
+
shadow_traffic_err = false
|
182
|
+
if @perform_checks
|
183
|
+
perform_common_checks! args
|
184
|
+
|
185
|
+
if @shadow_traffic_delivery_percent > 0 && args[:insertion_page_type] != Promoted::Ruby::Client::INSERTION_PAGING_TYPE['UNPAGED'] then
|
186
|
+
shadow_traffic_err = true
|
187
|
+
@logger.error(ShadowTrafficInsertionPageType.new) if @logger
|
188
|
+
end
|
149
189
|
end
|
150
|
-
|
190
|
+
|
191
|
+
pre_delivery_fillin_fields log_request_builder
|
151
192
|
|
152
|
-
|
153
|
-
|
193
|
+
if !shadow_traffic_err && should_send_as_shadow_traffic?
|
194
|
+
deliver_shadow_traffic args, headers
|
195
|
+
end
|
196
|
+
|
197
|
+
log_request_builder.log_request_params
|
154
198
|
end
|
155
199
|
|
200
|
+
##
|
156
201
|
# Sends a log request (previously created by a call to prepare_for_logging) to the metrics endpoint.
|
157
202
|
def send_log_request log_request_params, headers={}
|
158
|
-
|
203
|
+
begin
|
204
|
+
send_request(log_request_params, @metrics_endpoint, @metrics_timeout_millis, @metrics_api_key, headers)
|
205
|
+
rescue StandardError => err
|
206
|
+
# Currently we don't propagate errors to the SDK caller.
|
207
|
+
@logger.error("Error from metrics: " + err.message) if @logger
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
def send_request payload, endpoint, timeout_millis, api_key, headers={}, send_async=false
|
214
|
+
headers["x-api-key"] = api_key
|
215
|
+
use_headers = @default_request_headers.merge headers
|
216
|
+
|
217
|
+
if send_async
|
218
|
+
@pool.post do
|
219
|
+
@http_client.send(endpoint, timeout_millis, payload, use_headers)
|
220
|
+
end
|
221
|
+
else
|
222
|
+
begin
|
223
|
+
@http_client.send(endpoint, timeout_millis, payload, use_headers)
|
224
|
+
rescue Faraday::Error => err
|
225
|
+
raise EndpointError.new(err)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
def add_missing_ids_on_insertions! request, insertions
|
232
|
+
insertions.each do |insertion|
|
233
|
+
insertion[:insertion_id] = SecureRandom.uuid if not insertion[:insertion_id]
|
234
|
+
insertion[:session_id] = request[:session_id] if request[:session_id]
|
235
|
+
insertion[:request_id] = request[:request_id] if request[:request_id]
|
236
|
+
end
|
159
237
|
end
|
160
238
|
|
161
239
|
def should_send_as_shadow_traffic?
|
@@ -173,37 +251,22 @@ module Promoted
|
|
173
251
|
delivery_request_params[:client_info][:traffic_type] = Promoted::Ruby::Client::TRAFFIC_TYPE['SHADOW']
|
174
252
|
|
175
253
|
# Call Delivery API async (fire and forget)
|
176
|
-
send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, headers, true)
|
254
|
+
send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, @delivery_api_key, headers, true)
|
177
255
|
end
|
178
256
|
|
179
|
-
def
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
log_request_builder.set_request_params(args)
|
187
|
-
if perform_checks?
|
188
|
-
Promoted::Ruby::Client::Settings.check_that_log_ids_not_set!(args)
|
189
|
-
|
190
|
-
if @shadow_traffic_delivery_percent > 0 && args[:insertion_page_type] != Promoted::Ruby::Client::INSERTION_PAGING_TYPE['UNPAGED'] then
|
191
|
-
raise ShadowTrafficInsertionPageType
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
pre_delivery_fillin_fields log_request_builder
|
196
|
-
|
197
|
-
if should_send_as_shadow_traffic?
|
198
|
-
deliver_shadow_traffic args, headers
|
257
|
+
def perform_common_checks!(req)
|
258
|
+
begin
|
259
|
+
@validator.check_that_log_ids_not_set!(req)
|
260
|
+
@validator.validate_metrics_request!(req)
|
261
|
+
rescue StandardError => err
|
262
|
+
@logger.error(err) if @logger
|
263
|
+
raise
|
199
264
|
end
|
200
|
-
|
201
|
-
log_request_builder.log_request_params
|
202
265
|
end
|
203
266
|
|
204
267
|
def should_apply_treatment(cohort_membership)
|
205
268
|
if @should_apply_treatment_func != nil
|
206
|
-
@should_apply_treatment_func
|
269
|
+
@should_apply_treatment_func.call(cohort_membership)
|
207
270
|
else
|
208
271
|
return true if !cohort_membership
|
209
272
|
return true if !cohort_membership[:arm]
|
@@ -217,15 +280,6 @@ module Promoted
|
|
217
280
|
log_request_builder.timing[:client_log_timestamp] = Time.now.to_i
|
218
281
|
end
|
219
282
|
end
|
220
|
-
|
221
|
-
# A common compact method implementation.
|
222
|
-
def self.copy_and_remove_properties
|
223
|
-
Proc.new do |insertion|
|
224
|
-
insertion = Hash[insertion]
|
225
|
-
insertion.delete(:properties)
|
226
|
-
insertion
|
227
|
-
end
|
228
|
-
end
|
229
283
|
end
|
230
284
|
end
|
231
285
|
end
|
@@ -234,7 +288,6 @@ end
|
|
234
288
|
# dependent /libs
|
235
289
|
require "promoted/ruby/client/request_builder"
|
236
290
|
require "promoted/ruby/client/sampler"
|
237
|
-
require "promoted/ruby/client/settings"
|
238
291
|
require "promoted/ruby/client/util"
|
239
|
-
require
|
292
|
+
require "promoted/ruby/client/validator"
|
240
293
|
require 'securerandom'
|
@@ -1,40 +1,21 @@
|
|
1
1
|
module Promoted
|
2
2
|
module Ruby
|
3
3
|
module Client
|
4
|
-
class
|
5
|
-
def message
|
6
|
-
'Request.requestId should not be set'
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
class RequestInsertionError < StandardError
|
11
|
-
def message
|
12
|
-
'Do not set Request.insertion. Set full_insertion.'
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
class InsertionRequestIdError < StandardError
|
17
|
-
def message
|
18
|
-
'Insertion.requestId should not be set'
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
class InsertionIdError < StandardError
|
4
|
+
class ShadowTrafficInsertionPageType < StandardError
|
23
5
|
def message
|
24
|
-
'
|
6
|
+
'Insertions must be unpaged when shadow traffic is on'
|
25
7
|
end
|
26
8
|
end
|
27
9
|
|
28
|
-
class
|
29
|
-
|
30
|
-
|
10
|
+
class EndpointError < StandardError
|
11
|
+
attr_reader :cause
|
12
|
+
def initialize(cause)
|
13
|
+
@cause = cause
|
14
|
+
super('Error calling Promoted.ai endpoint')
|
31
15
|
end
|
32
16
|
end
|
33
17
|
|
34
|
-
class
|
35
|
-
def message
|
36
|
-
'Insertions must be unpaged when shadow traffic is on'
|
37
|
-
end
|
18
|
+
class ValidationError < StandardError
|
38
19
|
end
|
39
20
|
end
|
40
21
|
end
|
@@ -10,6 +10,7 @@ module Promoted
|
|
10
10
|
@conn = Faraday.new do |f|
|
11
11
|
f.request :json
|
12
12
|
f.request :retry, max: 3
|
13
|
+
f.use Faraday::Response::RaiseError # raises on 4xx and 5xx responses
|
13
14
|
f.adapter :net_http
|
14
15
|
end
|
15
16
|
end
|
@@ -18,12 +19,16 @@ module Promoted
|
|
18
19
|
response = @conn.post(endpoint) do |req|
|
19
20
|
req.headers = req.headers.merge(additional_headers) if additional_headers
|
20
21
|
req.headers['Content-Type'] = req.headers['Content-Type'] || 'application/json'
|
21
|
-
req.options.timeout = timeout_millis / 1000
|
22
|
+
req.options.timeout = timeout_millis.to_f / 1000
|
22
23
|
req.body = request.to_json
|
23
24
|
end
|
24
25
|
|
25
|
-
|
26
|
-
|
26
|
+
norm_headers = response.headers.transform_keys(&:downcase)
|
27
|
+
if norm_headers["content-type"] != nil && norm_headers["content-type"].start_with?("application/json")
|
28
|
+
JSON.parse(response.body, :symbolize_names => true)
|
29
|
+
else
|
30
|
+
response.body
|
31
|
+
end
|
27
32
|
end
|
28
33
|
end
|
29
34
|
end
|
@@ -14,6 +14,7 @@ module Promoted
|
|
14
14
|
def set_request_params args = {}
|
15
15
|
@request = args[:request] || {}
|
16
16
|
@experiment = args[:experiment]
|
17
|
+
@only_log = args[:only_log]
|
17
18
|
@session_id = request[:session_id]
|
18
19
|
@platform_id = request[:platform_id]
|
19
20
|
@client_info = request[:client_info] || {}
|
@@ -23,7 +24,6 @@ module Promoted
|
|
23
24
|
@request_id = SecureRandom.uuid
|
24
25
|
@user_info = request[:user_info] || { :user_id => nil, :log_user_id => nil}
|
25
26
|
@timing = request[:timing] || { :client_log_timestamp => Time.now.to_i }
|
26
|
-
@only_log = request[:only_log]
|
27
27
|
@to_compact_metrics_insertion_func = args[:to_compact_metrics_insertion_func]
|
28
28
|
@to_compact_delivery_insertion_func = args[:to_compact_delivery_insertion_func]
|
29
29
|
end
|
@@ -48,10 +48,16 @@ module Promoted
|
|
48
48
|
params = {
|
49
49
|
user_info: user_info,
|
50
50
|
timing: timing,
|
51
|
-
|
52
|
-
|
51
|
+
client_info: @client_info.merge({ :client_type => Promoted::Ruby::Client::CLIENT_TYPE['PLATFORM_SERVER'] }),
|
52
|
+
platform_id: @platform_id,
|
53
|
+
request_id: @request_id,
|
54
|
+
view_id: @view_id,
|
55
|
+
session_id: @session_id,
|
56
|
+
use_case: @use_case,
|
57
|
+
search_query: request[:search_query],
|
58
|
+
properties: request[:properties],
|
59
|
+
paging: request[:paging]
|
53
60
|
}
|
54
|
-
params[:request] = request
|
55
61
|
params[:insertion] = should_compact ? compact_delivery_insertions : full_insertion
|
56
62
|
|
57
63
|
params.clean!
|
@@ -61,6 +67,10 @@ module Promoted
|
|
61
67
|
# Maps the response insertions to the full insertions and re-insert the properties bag
|
62
68
|
# to the responses.
|
63
69
|
def fill_details_from_response response_insertions
|
70
|
+
if !response_insertions then
|
71
|
+
response_insertions = full_insertion
|
72
|
+
end
|
73
|
+
|
64
74
|
props = @full_insertion.each_with_object({}) do |insertion, hash|
|
65
75
|
hash[insertion[:content_id]] = insertion[:properties]
|
66
76
|
end
|
@@ -77,35 +87,6 @@ module Promoted
|
|
77
87
|
filled_in_copy
|
78
88
|
end
|
79
89
|
|
80
|
-
def validate_request_params
|
81
|
-
# TODO
|
82
|
-
end
|
83
|
-
|
84
|
-
def to_compact_metrics_insertion_func
|
85
|
-
@to_compact_metrics_insertion_func
|
86
|
-
end
|
87
|
-
|
88
|
-
def to_compact_delivery_insertion_func
|
89
|
-
@to_compact_delivery_insertion_func
|
90
|
-
end
|
91
|
-
|
92
|
-
# A list of the response Insertions. This client expects lists to be truncated
|
93
|
-
# already to request.paging.size. If not truncated, this client will truncate
|
94
|
-
# the list.
|
95
|
-
def insertion
|
96
|
-
@insertion
|
97
|
-
end
|
98
|
-
|
99
|
-
# A way to turn off logging. Defaults to true.
|
100
|
-
def enabled?
|
101
|
-
@enabled
|
102
|
-
end
|
103
|
-
|
104
|
-
# Default values to use on DeliveryRequests.
|
105
|
-
def default_request_values
|
106
|
-
@default_request_values
|
107
|
-
end
|
108
|
-
|
109
90
|
def log_request_params(include_insertions: true, include_request: true)
|
110
91
|
params = {
|
111
92
|
user_info: user_info,
|
@@ -154,6 +135,14 @@ module Promoted
|
|
154
135
|
@insertion
|
155
136
|
end
|
156
137
|
|
138
|
+
private
|
139
|
+
|
140
|
+
# A list of the response Insertions. This client expects lists to be truncated
|
141
|
+
# already to request.paging.size. If not truncated, this client will truncate
|
142
|
+
# the list.
|
143
|
+
def insertion
|
144
|
+
@insertion
|
145
|
+
end
|
157
146
|
end
|
158
147
|
end
|
159
148
|
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module Promoted
|
2
|
+
module Ruby
|
3
|
+
module Client
|
4
|
+
class Validator
|
5
|
+
def validate_user_info!(ui)
|
6
|
+
validate_fields!(
|
7
|
+
ui,
|
8
|
+
"user info",
|
9
|
+
[
|
10
|
+
{
|
11
|
+
:name => :user_id,
|
12
|
+
:type => String
|
13
|
+
},
|
14
|
+
{
|
15
|
+
:name => :log_user_id,
|
16
|
+
:type => String
|
17
|
+
}
|
18
|
+
]
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate_insertion!(ins)
|
23
|
+
validate_fields!(
|
24
|
+
ins,
|
25
|
+
"insertion",
|
26
|
+
[
|
27
|
+
{
|
28
|
+
:name => :platform_id,
|
29
|
+
:type => Integer
|
30
|
+
},
|
31
|
+
{
|
32
|
+
:name => :insertion_id,
|
33
|
+
:type => String
|
34
|
+
},
|
35
|
+
{
|
36
|
+
:name => :request_id,
|
37
|
+
:type => String
|
38
|
+
},
|
39
|
+
{
|
40
|
+
:name => :view_id,
|
41
|
+
:type => String
|
42
|
+
},
|
43
|
+
{
|
44
|
+
:name => :session_id,
|
45
|
+
:type => String
|
46
|
+
},
|
47
|
+
{
|
48
|
+
:name => :content_id,
|
49
|
+
:type => String
|
50
|
+
},
|
51
|
+
{
|
52
|
+
:name => :position,
|
53
|
+
:type => Integer
|
54
|
+
},
|
55
|
+
{
|
56
|
+
:name => :delivery_score,
|
57
|
+
:type => Integer
|
58
|
+
}
|
59
|
+
]
|
60
|
+
)
|
61
|
+
|
62
|
+
if ins[:user_info] then
|
63
|
+
self.validate_user_info! ins[:user_info]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_request!(req)
|
68
|
+
validate_fields!(
|
69
|
+
req,
|
70
|
+
"request",
|
71
|
+
[
|
72
|
+
{
|
73
|
+
:name => :platform_id,
|
74
|
+
:type => Integer
|
75
|
+
},
|
76
|
+
{
|
77
|
+
:name => :request_id,
|
78
|
+
:type => String
|
79
|
+
},
|
80
|
+
{
|
81
|
+
:name => :view_id,
|
82
|
+
:type => String
|
83
|
+
},
|
84
|
+
{
|
85
|
+
:name => :session_id,
|
86
|
+
:type => String
|
87
|
+
},
|
88
|
+
{
|
89
|
+
:name => :insertion,
|
90
|
+
:type => Array
|
91
|
+
}
|
92
|
+
]
|
93
|
+
)
|
94
|
+
|
95
|
+
if req[:insertion] then
|
96
|
+
req[:insertion].each {|ins|
|
97
|
+
validate_insertion! ins
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
if req[:user_info] then
|
102
|
+
validate_user_info! req[:user_info]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def validate_metrics_request!(metrics_req)
|
107
|
+
validate_fields!(
|
108
|
+
metrics_req,
|
109
|
+
"metrics request",
|
110
|
+
[
|
111
|
+
{
|
112
|
+
:name => :request,
|
113
|
+
:required => true
|
114
|
+
},
|
115
|
+
{
|
116
|
+
:name => :full_insertion,
|
117
|
+
:required => true,
|
118
|
+
:type => Array
|
119
|
+
}
|
120
|
+
]
|
121
|
+
)
|
122
|
+
|
123
|
+
validate_request!(metrics_req[:request])
|
124
|
+
metrics_req[:full_insertion].each {|ins|
|
125
|
+
validate_insertion! ins
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
def check_that_log_ids_not_set! req
|
130
|
+
raise ValidationError.new("Request.requestId should not be set") if req.dig(:request, :request_id)
|
131
|
+
raise ValidationError.new("Do not set Request.insertion. Set full_insertion.") if req[:insertion]
|
132
|
+
|
133
|
+
req[:full_insertion].each do |insertion_hash|
|
134
|
+
raise ValidationError.new("Insertion.requestId should not be set") if insertion_hash[:request_id]
|
135
|
+
raise ValidationError.new("'Insertion.insertionId should not be set") if insertion_hash[:insertion_id]
|
136
|
+
raise ValidationError.new("Insertion.deliveryScore should not be set") if insertion_hash[:delivery_score]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def validate_fields!(obj, obj_name, fields)
|
143
|
+
fields.each {|field|
|
144
|
+
if field[:required] then
|
145
|
+
raise ValidationError.new(field[:name].to_s + " is required on " + obj_name) if !obj.has_key?(field[:name])
|
146
|
+
end
|
147
|
+
|
148
|
+
# If a field is provided as non-nil, it should be of the correct type.
|
149
|
+
if field[:type] && obj.has_key?(field[:name]) && obj[field[:name]] != nil then
|
150
|
+
raise ValidationError.new(field[:name].to_s + " should be a " + field[:type].to_s) if !obj[field[:name]].is_a?(field[:type])
|
151
|
+
end
|
152
|
+
}
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
require "promoted/ruby/client/errors"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: promoted-ruby-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- scottmcmaster
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -77,8 +77,8 @@ files:
|
|
77
77
|
- lib/promoted/ruby/client/faraday_http_client.rb
|
78
78
|
- lib/promoted/ruby/client/request_builder.rb
|
79
79
|
- lib/promoted/ruby/client/sampler.rb
|
80
|
-
- lib/promoted/ruby/client/settings.rb
|
81
80
|
- lib/promoted/ruby/client/util.rb
|
81
|
+
- lib/promoted/ruby/client/validator.rb
|
82
82
|
- lib/promoted/ruby/client/version.rb
|
83
83
|
- promoted-ruby-client.gemspec
|
84
84
|
homepage: https://github.com/promotedai/promoted-ruby-client
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module Promoted
|
2
|
-
module Ruby
|
3
|
-
module Client
|
4
|
-
class Settings
|
5
|
-
|
6
|
-
def self.check_that_log_ids_not_set! options_hash
|
7
|
-
raise RequestError if options_hash.dig(:request, :request_id)
|
8
|
-
raise RequestInsertionError if options_hash[:insertion]
|
9
|
-
|
10
|
-
options_hash[:full_insertion].each do |insertion_hash|
|
11
|
-
raise InsertionRequestIdError if insertion_hash[:request_id]
|
12
|
-
raise InsertionIdError if insertion_hash[:insertion_id]
|
13
|
-
end
|
14
|
-
true
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
require "promoted/ruby/client/errors"
|