snowly 0.1.0 → 0.1.1
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/.gitignore +1 -0
- data/bin/snowly +10 -0
- data/config.ru +6 -0
- data/lib/schemas/snowplow_protocol.json +265 -0
- data/lib/snowly/app/collector.rb +42 -0
- data/lib/snowly/app/views/index.erb +103 -0
- data/lib/snowly/extensions/custom_dependencies.rb +66 -0
- data/lib/snowly/request.rb +28 -0
- data/lib/snowly/schema_cache.rb +58 -0
- data/lib/snowly/transformer.rb +117 -0
- data/lib/snowly/validator.rb +95 -0
- data/lib/snowly/validators/self_desc.rb +16 -0
- data/lib/snowly/version.rb +1 -1
- data/lib/snowly.rb +17 -1
- data/snowly.gemspec +16 -7
- metadata +128 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
!binary "U0hBMQ==":
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d41577c932e8b4ba8f150d57bfe887f4be951c6c
|
|
4
|
+
data.tar.gz: 84a1422730f60bb6134ff28a6e6310a699fcd9e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 749b0bb7137ff4782bece6bde1dfdc8fd5ed18db9002b0f9f28e9c4e5f186da137b91c448b8d3b289e638108da3e7d8117eea96487231abb7583ed940a670bbd
|
|
7
|
+
data.tar.gz: 151aff343e3031e30158aebe463fb079b94d2fa05eba04a9f8495659e5d30462417105197a4b17af14d57c9ad9314bf1a9260b395f954b36c66566fde78ab43a
|
data/.gitignore
CHANGED
data/bin/snowly
ADDED
data/config.ru
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
3
|
+
"id": "snowplow_protocol.json",
|
|
4
|
+
"description": "Representation of Snowplow Protocol in JSON Schema format for validation",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"name_tracker": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"maxLength": 128
|
|
10
|
+
},
|
|
11
|
+
"event_vendor": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"maxLength": 1000
|
|
14
|
+
},
|
|
15
|
+
"app_id": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"maxLength": 255
|
|
18
|
+
},
|
|
19
|
+
"platform": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"enum": ["web", "mob", "pc", "srv", "tv", "cnsl", "iot"],
|
|
22
|
+
"maxLength": 255
|
|
23
|
+
},
|
|
24
|
+
"dvce_created_tstamp": {
|
|
25
|
+
"type": "integer"
|
|
26
|
+
},
|
|
27
|
+
"dvce_sent_tstamp": {
|
|
28
|
+
"type": "integer"
|
|
29
|
+
},
|
|
30
|
+
"true_tstamp": {
|
|
31
|
+
"type": "integer"
|
|
32
|
+
},
|
|
33
|
+
"os_timezone": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"maxLength": 255
|
|
36
|
+
},
|
|
37
|
+
"event": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"enum": ["se", "ev", "ue", "ad", "tr", "ti", "pv", "pp"]
|
|
40
|
+
},
|
|
41
|
+
"txn_id": {
|
|
42
|
+
"type": "integer"
|
|
43
|
+
},
|
|
44
|
+
"event_id": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"maxLength": 36
|
|
47
|
+
},
|
|
48
|
+
"v_tracker": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"maxLength": 100
|
|
51
|
+
},
|
|
52
|
+
"domain_userid": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"maxLength": 36
|
|
55
|
+
},
|
|
56
|
+
"network_userid": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"maxLength": 38
|
|
59
|
+
},
|
|
60
|
+
"user_id": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"maxLength": 255
|
|
63
|
+
},
|
|
64
|
+
"domain_sessionidx": {
|
|
65
|
+
"type": "integer"
|
|
66
|
+
},
|
|
67
|
+
"domain_sessionid": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"maxLength": 36
|
|
70
|
+
},
|
|
71
|
+
"user_ipaddress": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"maxLength": 45
|
|
74
|
+
},
|
|
75
|
+
"screen_res_width_x_height": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"pattern": "^[0-9]+x[0-9]+$"
|
|
78
|
+
},
|
|
79
|
+
"page_url": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"maxLength": 4096
|
|
82
|
+
},
|
|
83
|
+
"useragent": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"maxLength": 1000
|
|
86
|
+
},
|
|
87
|
+
"page_title": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"maxLength": 2000
|
|
90
|
+
},
|
|
91
|
+
"page_referer": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"maxLength": 4096
|
|
94
|
+
},
|
|
95
|
+
"user_fingerprint": {
|
|
96
|
+
"type": "integer"
|
|
97
|
+
},
|
|
98
|
+
"br_cookies": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"enum": ["1", "0"]
|
|
101
|
+
},
|
|
102
|
+
"br_lang": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"maxLength": 255
|
|
105
|
+
},
|
|
106
|
+
"br_features_pdf": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"enum": ["1", "0"]
|
|
109
|
+
},
|
|
110
|
+
"br_features_quicktime": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"enum": ["1", "0"]
|
|
113
|
+
},
|
|
114
|
+
"br_features_realplayer": {
|
|
115
|
+
"type": "string",
|
|
116
|
+
"enum": ["1", "0"]
|
|
117
|
+
},
|
|
118
|
+
"br_features_windowsmedia": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"enum": ["1", "0"]
|
|
121
|
+
},
|
|
122
|
+
"br_features_director": {
|
|
123
|
+
"type": "string",
|
|
124
|
+
"enum": ["1", "0"]
|
|
125
|
+
},
|
|
126
|
+
"br_features_flash": {
|
|
127
|
+
"type": "string",
|
|
128
|
+
"enum": ["1", "0"]
|
|
129
|
+
},
|
|
130
|
+
"br_features_java": {
|
|
131
|
+
"type": "string",
|
|
132
|
+
"enum": ["1", "0"]
|
|
133
|
+
},
|
|
134
|
+
"br_features_gears": {
|
|
135
|
+
"type": "string",
|
|
136
|
+
"enum": ["1", "0"]
|
|
137
|
+
},
|
|
138
|
+
"br_features_silverlight": {
|
|
139
|
+
"type": "string",
|
|
140
|
+
"enum": ["1", "0"]
|
|
141
|
+
},
|
|
142
|
+
"br_colordepth": {
|
|
143
|
+
"type": "integer"
|
|
144
|
+
},
|
|
145
|
+
"doc_width_x_height": {
|
|
146
|
+
"type": "string",
|
|
147
|
+
"pattern": "^[0-9]+x[0-9]+$"
|
|
148
|
+
},
|
|
149
|
+
"doc_charset": {
|
|
150
|
+
"type": "string",
|
|
151
|
+
"maxLength": 128
|
|
152
|
+
},
|
|
153
|
+
"browser_viewport_width_x_height": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"pattern": "^[0-9]+x[0-9]+$"
|
|
156
|
+
},
|
|
157
|
+
"mac_address": {
|
|
158
|
+
"type": "string",
|
|
159
|
+
"maxLength": 36
|
|
160
|
+
},
|
|
161
|
+
"pp_xoffset_min": {
|
|
162
|
+
"type": "integer"
|
|
163
|
+
},
|
|
164
|
+
"pp_xoffset_max": {
|
|
165
|
+
"type": "integer"
|
|
166
|
+
},
|
|
167
|
+
"pp_yoffset_min": {
|
|
168
|
+
"type": "integer"
|
|
169
|
+
},
|
|
170
|
+
"pp_yoffset_max": {
|
|
171
|
+
"type": "integer"
|
|
172
|
+
},
|
|
173
|
+
"tr_orderid": {
|
|
174
|
+
"type": "string",
|
|
175
|
+
"maxLength": 255
|
|
176
|
+
},
|
|
177
|
+
"tr_affiliation": {
|
|
178
|
+
"type": "string",
|
|
179
|
+
"maxLength": 255
|
|
180
|
+
},
|
|
181
|
+
"tr_total": {
|
|
182
|
+
"type": "number"
|
|
183
|
+
},
|
|
184
|
+
"tr_tax": {
|
|
185
|
+
"type": "number"
|
|
186
|
+
},
|
|
187
|
+
"tr_shipping": {
|
|
188
|
+
"type": "number"
|
|
189
|
+
},
|
|
190
|
+
"tr_city": {
|
|
191
|
+
"type": "string",
|
|
192
|
+
"maxLength": 255
|
|
193
|
+
},
|
|
194
|
+
"tr_state": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"maxLength": 255
|
|
197
|
+
},
|
|
198
|
+
"tr_country": {
|
|
199
|
+
"type": "string",
|
|
200
|
+
"maxLength": 255
|
|
201
|
+
},
|
|
202
|
+
"tr_currency": {
|
|
203
|
+
"type": "string",
|
|
204
|
+
"maxLength": 255
|
|
205
|
+
},
|
|
206
|
+
"ti_orderid": {
|
|
207
|
+
"type": "string",
|
|
208
|
+
"maxLength": 255
|
|
209
|
+
},
|
|
210
|
+
"ti_sku": {
|
|
211
|
+
"type": "string",
|
|
212
|
+
"maxLength": 255
|
|
213
|
+
},
|
|
214
|
+
"ti_name": {
|
|
215
|
+
"type": "string",
|
|
216
|
+
"maxLength": 255
|
|
217
|
+
},
|
|
218
|
+
"ti_category": {
|
|
219
|
+
"type": "string",
|
|
220
|
+
"maxLength": 255
|
|
221
|
+
},
|
|
222
|
+
"ti_price": {
|
|
223
|
+
"type": "number"
|
|
224
|
+
},
|
|
225
|
+
"ti_quantity": {
|
|
226
|
+
"type": "integer"
|
|
227
|
+
},
|
|
228
|
+
"ti_currency": {
|
|
229
|
+
"type": "string",
|
|
230
|
+
"maxLength": 255
|
|
231
|
+
},
|
|
232
|
+
"se_category": {
|
|
233
|
+
"type": "string",
|
|
234
|
+
"maxLength": 255
|
|
235
|
+
},
|
|
236
|
+
"se_action": {
|
|
237
|
+
"type": "string",
|
|
238
|
+
"maxLength": 255
|
|
239
|
+
},
|
|
240
|
+
"se_label": {
|
|
241
|
+
"type": "string",
|
|
242
|
+
"maxLength": 255
|
|
243
|
+
},
|
|
244
|
+
"se_property": {
|
|
245
|
+
"type": "string",
|
|
246
|
+
"maxLength": 255
|
|
247
|
+
},
|
|
248
|
+
"se_value": {
|
|
249
|
+
"type": "number"
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
"required": ["app_id", "platform", "event", "event_id", "v_tracker", "useragent"],
|
|
253
|
+
"custom_dependencies": {
|
|
254
|
+
"se_category": { "event": "se" },
|
|
255
|
+
"se_action": {"event": "se" },
|
|
256
|
+
"tr_orderid": { "event": "tr" },
|
|
257
|
+
"tr_total": { "event": "tr" },
|
|
258
|
+
"ti_orderid": { "event": "ti" },
|
|
259
|
+
"ti_sku": { "event": "ti" },
|
|
260
|
+
"ti_quantity": { "event": "ti" },
|
|
261
|
+
"ti_price": { "event": "ti" },
|
|
262
|
+
"unstruct_event": { "event": "ue"},
|
|
263
|
+
"page_url": { "platform": "web" }
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
require 'base64'
|
|
3
|
+
require 'sinatra'
|
|
4
|
+
require "sinatra/reloader" if development?
|
|
5
|
+
|
|
6
|
+
module Snowly
|
|
7
|
+
module App
|
|
8
|
+
class Collector < Sinatra::Base
|
|
9
|
+
GIF = Base64.decode64("R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==")
|
|
10
|
+
configure :development do
|
|
11
|
+
register Sinatra::Reloader
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
get '/' do
|
|
15
|
+
@url = request.url.gsub(/(http|https)\:\/\//,'')[0..-2]
|
|
16
|
+
@resolved_schemas = if resolver = Snowly.local_iglu_resolver_path
|
|
17
|
+
Dir[File.join(resolver,"/**/*")].select{ |e| File.file? e }
|
|
18
|
+
else
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
erb :index
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
get '/i' do
|
|
25
|
+
content_type :json
|
|
26
|
+
validator = Snowly::Validator.new request.query_string
|
|
27
|
+
if validator.validate
|
|
28
|
+
status 200
|
|
29
|
+
if params[:debug] || Snowly.debug_mode
|
|
30
|
+
body({ content: validator.request.as_hash }.to_json)
|
|
31
|
+
else
|
|
32
|
+
content_type 'image/gif'
|
|
33
|
+
Snowly::App::Collector::GIF
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
status 500
|
|
37
|
+
body ({ errors: validator.errors, content: validator.request.as_hash }.to_json)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Snowly - Snowplow Request Validator</title>
|
|
5
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
|
|
6
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div class="container">
|
|
10
|
+
<div class="header clearfix">
|
|
11
|
+
<nav>
|
|
12
|
+
<ul class="nav nav-pills pull-right">
|
|
13
|
+
<li role="presentation"><a href="https://github.com/angelim/snowly">Github</a></li>
|
|
14
|
+
<li role="presentation"><a href="https://github.com/snowplow/snowplow">Snowplow</a></li>
|
|
15
|
+
</ul>
|
|
16
|
+
</nav>
|
|
17
|
+
<h3 class="text-muted">Snowly - Snowplow Request Validator</h3>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="jumbotron">
|
|
21
|
+
<p class="lead">Test your snowplow implementation locally!</p>
|
|
22
|
+
<p><strong>Snowly</strong> is a minimal collector implementation intended to validate your event tracking requests before emitting them to cloudfront or a closure collector.</p>
|
|
23
|
+
<p>When <strong>Snowly</strong> finds something wrong, it renders the parsed request along with its errors.</p>
|
|
24
|
+
<p>If everything is ok, Snowly delivers the default Snowplow pixel, unless you're using the debug mode.</p>
|
|
25
|
+
<p>Point your collector URL to <code><%= url %>i</code> and have fun!</p>
|
|
26
|
+
<p>
|
|
27
|
+
<a class="btn btn-lg btn-success" href="/i?&e=pv&page=Root%20README&url=http%3A%2F%2Fgithub.com%2Fsnowplow%2Fsnowplow&aid=snowplow&p=web&tv=no-js-0.1.0&ua=firefox&&eid=u2i3&debug=true" role="button">See it working!</a>
|
|
28
|
+
<a class="btn btn-lg btn-warning" href="/i?&e=pv&page=Root%20README&url=http%3A%2F%2Fgithub.com%2Fsnowplow%2Fsnowplow&aid=snowplow&p=i&tv=no-js-0.1.0&debug=true" role="button">Event with errors!</a>
|
|
29
|
+
</p>
|
|
30
|
+
<% unless Snowly.local_iglu_resolver_path %>
|
|
31
|
+
<div class="alert alert-danger" role="alert">The Local Iglu Resolver Path is missing.</div>
|
|
32
|
+
<% end %>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="row marketing">
|
|
36
|
+
<div class="col-lg-12">
|
|
37
|
+
<div class="panel panel-default">
|
|
38
|
+
<div class="panel-heading">Current Configuration</div>
|
|
39
|
+
<table class="table">
|
|
40
|
+
<thead>
|
|
41
|
+
<tr>
|
|
42
|
+
<th>Environment Variable</th>
|
|
43
|
+
<th>Value</th>
|
|
44
|
+
<th>Description</th>
|
|
45
|
+
</tr>
|
|
46
|
+
</thead>
|
|
47
|
+
<tbody>
|
|
48
|
+
<tr>
|
|
49
|
+
<td>SNOWLY_DEBUG_MODE</td>
|
|
50
|
+
<td><%= Snowly.debug_mode %></td>
|
|
51
|
+
<td>Renders parsed request instead of a pixel. Defaults to false</td>
|
|
52
|
+
</tr>
|
|
53
|
+
<tr>
|
|
54
|
+
<td>LOCAL_IGLU_RESOLVER_PATH</td>
|
|
55
|
+
<td><%= Snowly.local_iglu_resolver_path %></td>
|
|
56
|
+
<td>Local path for contexts and unstructured event schemas.</td>
|
|
57
|
+
</tr>
|
|
58
|
+
</tbody>
|
|
59
|
+
|
|
60
|
+
</table>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="row marketing">
|
|
65
|
+
<div class="col-lg-6">
|
|
66
|
+
<div class="panel panel-default">
|
|
67
|
+
<div class="panel-heading">Resolved Schemas</div>
|
|
68
|
+
<div class="panel-body">
|
|
69
|
+
<% if @resolved_schemas and not @resolved_schemas == [] %>
|
|
70
|
+
<ul>
|
|
71
|
+
<% @resolved_schemas.each do |r| %>
|
|
72
|
+
<li><%= r %></li>
|
|
73
|
+
<% end %>
|
|
74
|
+
</ul>
|
|
75
|
+
<% else %>
|
|
76
|
+
<p>No resolved schemas</p>
|
|
77
|
+
<% end %>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="col-lg-6">
|
|
82
|
+
<h4>Local Iglu Resolver</h4>
|
|
83
|
+
<p>
|
|
84
|
+
Snowly must be able to find your custom context and unstructured event schemas.
|
|
85
|
+
Just like the Resolver you may have already configured for the official ETL tools, Snowly needs a
|
|
86
|
+
local path to find your custom schemas. You can store them under any path(eg: ~/schemas)
|
|
87
|
+
Inside that folder you must create a resolver compatible structure:
|
|
88
|
+
<code>~/schemas/com.yoursite/schema/my_context/1-0-0</code><br>
|
|
89
|
+
<code>~/schemas/com.yoursite/schema/my_event/1-0-0</code><br>
|
|
90
|
+
1-0-0 is the file holding the schema.
|
|
91
|
+
</p>
|
|
92
|
+
<p>
|
|
93
|
+
When you emmit events, use the schema path from the <code>Resolver path</code><br/>
|
|
94
|
+
<code>{ schema: 'iglu:com.yoursite/schema/my_context/1-0-0', data: !some_schema_data! }</code>
|
|
95
|
+
</p>
|
|
96
|
+
<p>
|
|
97
|
+
Be sure to give your schemas an <a href="http://spacetelescope.github.io/understanding-json-schema/structuring.html#the-id-property">id</a>, so Snowly can output more helpful validation error messages.
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</body>
|
|
103
|
+
</html>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Extended validation to require custom dependencies
|
|
2
|
+
# This validation allows the schema designer to define requirements based on other attribute content.
|
|
3
|
+
# @example Require apartment number if address type is `apartment`
|
|
4
|
+
# {
|
|
5
|
+
# 'address': { 'type': string' },
|
|
6
|
+
# 'adress_type': { 'type': 'string', 'enum': ['house', 'apartment'] },
|
|
7
|
+
# 'apartment_number': { 'type': 'number'},
|
|
8
|
+
# 'custom_dependencies': {
|
|
9
|
+
# apartment_number: { 'address_type': 'apartment' }
|
|
10
|
+
# }
|
|
11
|
+
# }
|
|
12
|
+
# In this example if the address type is 'house', apartment_number if not required.
|
|
13
|
+
# It only becomes a requirement if address type is set to 'apartment'.
|
|
14
|
+
require 'json-schema/attribute'
|
|
15
|
+
|
|
16
|
+
class CustomDependenciesAttribute < JSON::Schema::Attribute
|
|
17
|
+
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
|
18
|
+
return unless data.is_a?(Hash)
|
|
19
|
+
current_schema.schema['custom_dependencies'].each do |property, dependency_value|
|
|
20
|
+
next unless accept_value?(dependency_value)
|
|
21
|
+
case dependency_value
|
|
22
|
+
when Array
|
|
23
|
+
dependency_value.each do |dependency_hash|
|
|
24
|
+
validate_dependency(current_schema, data, property, dependency_hash, fragments, processor, self, options)
|
|
25
|
+
end
|
|
26
|
+
when Hash
|
|
27
|
+
validate_dependency(current_schema, data, property, dependency_value, fragments, processor, self, options)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.validate_dependency(schema, data, property, dependency_hash, fragments, processor, attribute, options)
|
|
33
|
+
key, value = Array(dependency_hash).flatten
|
|
34
|
+
return unless data[key.to_s] == value.to_s
|
|
35
|
+
return if data.has_key?(property.to_s)
|
|
36
|
+
message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}' when property '#{key}' is '#{value}'"
|
|
37
|
+
validation_error(processor, message, fragments, schema, attribute, options[:record_errors])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.accept_value?(value)
|
|
41
|
+
value.is_a?(Array) || value.is_a?(Hash)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Registers custom dependencies for Draft-4. This used when evaluating the protocol schema
|
|
46
|
+
# snowly/schemas/snowplow_protocol.json
|
|
47
|
+
class RootExtendedSchema < JSON::Schema::Validator
|
|
48
|
+
def initialize
|
|
49
|
+
super
|
|
50
|
+
extend_schema_definition("http://json-schema.org/draft-04/schema#")
|
|
51
|
+
@attributes["custom_dependencies"] = CustomDependenciesAttribute
|
|
52
|
+
@uri = URI.parse("http://json-schema.org/draft-04/schema")
|
|
53
|
+
end
|
|
54
|
+
JSON::Validator.register_validator(self.new)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Registers custom dependencies for Snowplot Self-Describing schema. Contexts and Unstructure events derive from it.
|
|
58
|
+
class DescExtendedSchema < JSON::Schema::Validator
|
|
59
|
+
def initialize
|
|
60
|
+
super
|
|
61
|
+
extend_schema_definition("http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#")
|
|
62
|
+
@attributes["custom_dependencies"] = CustomDependenciesAttribute
|
|
63
|
+
@uri = URI.parse("http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#")
|
|
64
|
+
end
|
|
65
|
+
JSON::Validator.register_validator(self.new)
|
|
66
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'snowly/transformer'
|
|
2
|
+
module Snowly
|
|
3
|
+
class Request
|
|
4
|
+
attr_reader :query_string
|
|
5
|
+
def initialize(query_string)
|
|
6
|
+
@query_string = query_string
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Retuns request as json, after transforming parameters into column names
|
|
10
|
+
# @return [String] encoded JSON
|
|
11
|
+
def as_json
|
|
12
|
+
@json ||= as_hash.to_json
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Retuns request as hash, after transforming parameters into column names
|
|
16
|
+
# @return [Hash]
|
|
17
|
+
def as_hash
|
|
18
|
+
@hash ||= Transformer.transform(parsed_query)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns query parameters as hash
|
|
22
|
+
# @return [Hash]
|
|
23
|
+
def parsed_query
|
|
24
|
+
@parsed_query ||= Rack::Utils.parse_nested_query(query_string)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Caches schemas found during validation so they don't have to be
|
|
2
|
+
# retrieved a second time. Also uses the resolvers convert the iglu: location to an actual address (local or remote)/
|
|
3
|
+
require 'singleton'
|
|
4
|
+
module Snowly
|
|
5
|
+
class SchemaCache
|
|
6
|
+
include Singleton
|
|
7
|
+
SNOWPLOW_IGLU_RESOLVER = 'http://iglucentral.com/schemas/'
|
|
8
|
+
@@schema_cache = {}
|
|
9
|
+
|
|
10
|
+
# Provides easy access to the schema cache based on its registered key
|
|
11
|
+
# @param location [String] Location provided in the schema
|
|
12
|
+
# @return [String] Json for schema
|
|
13
|
+
def [](location)
|
|
14
|
+
@@schema_cache[location] || save_in_cache(location)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Resets the schema cache
|
|
18
|
+
def reset_cache
|
|
19
|
+
@@schema_cache = {}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Accessor to the global cache
|
|
23
|
+
def cache
|
|
24
|
+
@@schema_cache
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# If schema should be resolved to snowplow iglu server
|
|
30
|
+
# @param location [String]
|
|
31
|
+
# @return [true, false]
|
|
32
|
+
def from_snowplow?(location)
|
|
33
|
+
location['iglu:com.snowplowanalytics.snowplow']
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Translate an iglu address to an actual local or remote location
|
|
37
|
+
# @param location [String]
|
|
38
|
+
# @param resolver [String] local or remote path to look for the schema
|
|
39
|
+
# @return [String] Schema's actual location
|
|
40
|
+
def resolve(location, resolver)
|
|
41
|
+
location.sub(/^iglu\:/, resolver)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Caches the schema content under its original location name
|
|
45
|
+
# @param location [String]
|
|
46
|
+
# @return [String] schema content
|
|
47
|
+
def save_in_cache(location)
|
|
48
|
+
content = if from_snowplow?(location)
|
|
49
|
+
uri = URI(resolve(location, SNOWPLOW_IGLU_RESOLVER))
|
|
50
|
+
Net::HTTP.get(uri)
|
|
51
|
+
else
|
|
52
|
+
File.read(resolve(location, Snowly.local_iglu_resolver_path))
|
|
53
|
+
end
|
|
54
|
+
@@schema_cache[location] = content
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Maps query string parameters to column names to provide more helpful references on validations.
|
|
2
|
+
module Snowly
|
|
3
|
+
module Transformer
|
|
4
|
+
module_function
|
|
5
|
+
# Boolean fields are mapped to string because the tracker sends '1' or '0' in the query string.
|
|
6
|
+
# The actual conversion to boolean happens during the enrichment phase.
|
|
7
|
+
MAP = {
|
|
8
|
+
"e" => { field: "event", type: 'string' },
|
|
9
|
+
"ip" => { field: "user_ipaddress", type: "string" },
|
|
10
|
+
"aid" => { field: "app_id", type: "string" },
|
|
11
|
+
"p" => { field: "platform", type: "string" },
|
|
12
|
+
"tid" => { field: "txn_id", type: "integer" },
|
|
13
|
+
"uid" => { field: "user_id", type: "string" },
|
|
14
|
+
"duid" => { field: "domain_userid", type: "string" },
|
|
15
|
+
"nuid" => { field: "network_userid", type: "string" },
|
|
16
|
+
"ua" => { field: "useragent", type: "string" },
|
|
17
|
+
"fp" => { field: "user_fingerprint", type: "integer" },
|
|
18
|
+
"vid" => { field: "domain_sessionidx", type: "integer" },
|
|
19
|
+
"sid" => { field: "domain_sessionid", type: "string" },
|
|
20
|
+
"dtm" => { field: "dvce_created_tstamp", type: "integer" },
|
|
21
|
+
"ttm" => { field: "true_tstamp", type: "integer" },
|
|
22
|
+
"stm" => { field: "dvce_sent_tstamp", type: "integer" },
|
|
23
|
+
"tna" => { field: "name_tracker", type: "string" },
|
|
24
|
+
"tv" => { field: "v_tracker", type: "string" },
|
|
25
|
+
"cv" => { field: "v_collector", type: "string" },
|
|
26
|
+
"lang" => { field: "br_lang", type: "string" },
|
|
27
|
+
"f_pdf" => { field: "br_features_pdf", type: "string" },
|
|
28
|
+
"f_fla" => { field: "br_features_flash", type: "string" },
|
|
29
|
+
"f_java" => { field: "br_features_java", type: "string" },
|
|
30
|
+
"f_dir" => { field: "br_features_director", type: "string" },
|
|
31
|
+
"f_qt" => { field: "br_features_quicktime", type: "string" },
|
|
32
|
+
"f_realp" => { field: "br_features_realplayer", type: "string" },
|
|
33
|
+
"f_wma" => { field: "br_features_windowsmedia", type: "string" },
|
|
34
|
+
"f_gears" => { field: "br_features_gears", type: "string" },
|
|
35
|
+
"f_ag" => { field: "br_features_silverlight", type: "string" },
|
|
36
|
+
"cookie" => { field: "br_cookies", type: "string" },
|
|
37
|
+
"res" => { field: "screen_res_width_x_height", type: "string" },
|
|
38
|
+
"cd" => { field: "br_colordepth", type: "string" },
|
|
39
|
+
"tz" => { field: "os_timezone", type: "string" },
|
|
40
|
+
"refr" => { field: "page_referrer", type: "string" },
|
|
41
|
+
"url" => { field: "page_url", type: "string" },
|
|
42
|
+
"page" => { field: "page_title", type: "string" },
|
|
43
|
+
"cs" => { field: "doc_charset", type: "string" },
|
|
44
|
+
"ds" => { field: "doc_width_x_height", type: "string" },
|
|
45
|
+
"vp" => { field: "browser_viewport_width_x_height", type: "string" },
|
|
46
|
+
"eid" => { field: "event_id", type: "string" },
|
|
47
|
+
"co" => { field: "contexts", type: "json" },
|
|
48
|
+
"cx" => { field: "contexts", type: "base64" },
|
|
49
|
+
"ev_ca" => { field: "se_category", type: "string" },
|
|
50
|
+
"ev_ac" => { field: "se_action", type: "string" },
|
|
51
|
+
"ev_la" => { field: "se_label", type: "string" },
|
|
52
|
+
"ev_pr" => { field: "se_property", type: "string" },
|
|
53
|
+
"ev_va" => { field: "se_value", type: "string" },
|
|
54
|
+
"se_ca" => { field: "se_category", type: "string" },
|
|
55
|
+
"se_ac" => { field: "se_action", type: "string" },
|
|
56
|
+
"se_la" => { field: "se_label", type: "string" },
|
|
57
|
+
"se_pr" => { field: "se_property", type: "string" },
|
|
58
|
+
"se_va" => { field: "se_value", type: "number" },
|
|
59
|
+
"ue_pr" => { field: "unstruct_event", type: "json" },
|
|
60
|
+
"ue_px" => { field: "unstruct_event", type: "base64" },
|
|
61
|
+
"tr_id" => { field: "tr_orderid", type: "string" },
|
|
62
|
+
"tr_af" => { field: "tr_affiliation", type: "string" },
|
|
63
|
+
"tr_tt" => { field: "tr_total", type: "number" },
|
|
64
|
+
"tr_tx" => { field: "tr_tax", type: "number" },
|
|
65
|
+
"tr_sh" => { field: "tr_shipping", type: "number" },
|
|
66
|
+
"tr_ci" => { field: "tr_city", type: "string" },
|
|
67
|
+
"tr_st" => { field: "tr_state", type: "string" },
|
|
68
|
+
"tr_co" => { field: "tr_country", type: "string" },
|
|
69
|
+
"ti_id" => { field: "ti_orderid", type: "string" },
|
|
70
|
+
"ti_sk" => { field: "ti_sku", type: "string" },
|
|
71
|
+
"ti_na" => { field: "ti_name", type: "string" },
|
|
72
|
+
"ti_nm" => { field: "ti_name", type: "string" },
|
|
73
|
+
"ti_ca" => { field: "ti_category", type: "string" },
|
|
74
|
+
"ti_pr" => { field: "ti_price", type: "number" },
|
|
75
|
+
"ti_qu" => { field: "ti_quantity", type: "integer" },
|
|
76
|
+
"pp_mix" => { field: "pp_xoffset_min", type: "integer" },
|
|
77
|
+
"pp_max" => { field: "pp_xoffset_max", type: "integer" },
|
|
78
|
+
"pp_miy" => { field: "pp_yoffset_min", type: "integer" },
|
|
79
|
+
"pp_may" => { field: "pp_yoffset_max", type: "integer" },
|
|
80
|
+
"tr_cu" => { field: "tr_currency", type: "string" },
|
|
81
|
+
"ti_cu" => { field: "ti_currency", type: "integer" }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Transforms the request params into column names
|
|
85
|
+
# @param parsed_query [Hash] hash using parameter names for keys
|
|
86
|
+
# @return [Hash] hash using column names for keys
|
|
87
|
+
def transform(parsed_query)
|
|
88
|
+
parsed_query.inject({}) do |all, (key, value)|
|
|
89
|
+
if node = MAP[key]
|
|
90
|
+
field = node[:field]
|
|
91
|
+
all[field] = convert(value, node[:type])
|
|
92
|
+
end
|
|
93
|
+
all
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Tries to cast or parse each value so they can be properly validated by json-schema
|
|
98
|
+
# If the casting fails, leaves the value as string and it will be caught by the valication
|
|
99
|
+
# @param value [String]
|
|
100
|
+
# @param type [String] the intended param type
|
|
101
|
+
def convert(value, type)
|
|
102
|
+
begin
|
|
103
|
+
case type
|
|
104
|
+
when 'json' then JSON.parse(value)
|
|
105
|
+
when 'base64' then JSON.parse(Base64.urlsafe_decode64(value))
|
|
106
|
+
when 'integer' then Integer(value)
|
|
107
|
+
when 'number' then Float(value)
|
|
108
|
+
else
|
|
109
|
+
value.to_s
|
|
110
|
+
end
|
|
111
|
+
rescue ArgumentError
|
|
112
|
+
value.to_s
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Performs the validation for the root attributes and associated contexts and unstructured events.
|
|
2
|
+
require 'snowly/request'
|
|
3
|
+
require 'snowly/extensions/custom_dependencies'
|
|
4
|
+
module Snowly
|
|
5
|
+
class Validator
|
|
6
|
+
attr_reader :request, :errors
|
|
7
|
+
|
|
8
|
+
def initialize(query_string)
|
|
9
|
+
@request = Request.new query_string
|
|
10
|
+
@errors = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Loads the protocol schema created to describe snowplow events table attributes
|
|
14
|
+
# @return [Hash] parsed schema
|
|
15
|
+
def protocol_schema
|
|
16
|
+
@protocol_schema ||= JSON.parse File.read("lib/schemas/snowplow_protocol.json")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Hash] all contexts content and schema definitions
|
|
20
|
+
def associated_contexts
|
|
21
|
+
load_contexts request.as_hash['contexts']
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [Hash] all unstructured events content and schema definitions
|
|
25
|
+
def associated_unstruct_event
|
|
26
|
+
load_unstruct_event request.as_hash['unstruct_event']
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [Array<Hash>] all associated content
|
|
30
|
+
def associated_elements
|
|
31
|
+
(Array(associated_contexts) + Array(associated_unstruct_event)).compact
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Performs initial validation for associated contexts and loads their contents and definitions.
|
|
35
|
+
# @return [Array<Hash>]
|
|
36
|
+
def load_contexts(hash)
|
|
37
|
+
return unless hash
|
|
38
|
+
response = []
|
|
39
|
+
unless hash['data']
|
|
40
|
+
@errors << "All custom contexts must be contain a `data` element" and return
|
|
41
|
+
end
|
|
42
|
+
response << { content: hash['data'], definition: SchemaCache.instance[hash['schema']] }
|
|
43
|
+
unless hash['data'].is_a? Array
|
|
44
|
+
@errors << "All custom contexts must be wrapped in an Array" and return
|
|
45
|
+
end
|
|
46
|
+
hash['data'].each do |data_item|
|
|
47
|
+
response << { content: data_item['data'], definition: SchemaCache.instance[data_item['schema']] }
|
|
48
|
+
end
|
|
49
|
+
response
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Performs initial validation for associated unstructured events and loads their contents and definitions.
|
|
53
|
+
# @return [Array<Hash>]
|
|
54
|
+
def load_unstruct_event(hash)
|
|
55
|
+
return unless hash
|
|
56
|
+
response = []
|
|
57
|
+
unless hash['data']
|
|
58
|
+
@errors << "All custom unstruct event must be contain a `data` element" and return
|
|
59
|
+
end
|
|
60
|
+
outer_data = hash['data']
|
|
61
|
+
inner_data = outer_data['data']
|
|
62
|
+
response << { content: outer_data, definition: SchemaCache.instance[hash['schema']] }
|
|
63
|
+
response << { content: inner_data, definition: SchemaCache.instance[outer_data['schema']] }
|
|
64
|
+
response
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Validates associated contexts and unstructured events
|
|
68
|
+
def validate_associated
|
|
69
|
+
return unless associated_elements
|
|
70
|
+
associated_elements.each do |schema|
|
|
71
|
+
this_error = JSON::Validator.fully_validate JSON.parse(schema[:definition]), schema[:content]
|
|
72
|
+
@errors += this_error if this_error.count > 0
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Validates root attributes for the events table
|
|
77
|
+
def validate_root
|
|
78
|
+
this_error = JSON::Validator.fully_validate protocol_schema, request.as_hash
|
|
79
|
+
@errors += this_error if this_error.count > 0
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# If request is valid
|
|
83
|
+
# @return [true, false] if valid
|
|
84
|
+
def valid?
|
|
85
|
+
@errors == []
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Entry point for validation.
|
|
89
|
+
def validate
|
|
90
|
+
validate_root
|
|
91
|
+
validate_associated
|
|
92
|
+
valid?
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Register a validator for the self-describing schema.
|
|
2
|
+
# This is required to allow extended validations being attached to it.
|
|
3
|
+
require 'json-schema/validators/draft4'
|
|
4
|
+
module JSON
|
|
5
|
+
class Schema
|
|
6
|
+
class SelfDesc < Draft4
|
|
7
|
+
URL = "http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#"
|
|
8
|
+
def initialize
|
|
9
|
+
super
|
|
10
|
+
@uri = JSON::Util::URI.parse(URL)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
JSON::Validator.register_validator(self.new)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/snowly/version.rb
CHANGED
data/lib/snowly.rb
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
|
+
require 'json-schema'
|
|
2
|
+
require 'snowly/validators/self_desc'
|
|
3
|
+
require "pry"
|
|
1
4
|
require "snowly/version"
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'rack'
|
|
7
|
+
require 'snowly/validator'
|
|
8
|
+
require 'snowly/schema_cache'
|
|
9
|
+
require 'active_support'
|
|
2
10
|
|
|
3
11
|
module Snowly
|
|
4
|
-
|
|
12
|
+
mattr_accessor :local_iglu_resolver_path, :debug_mode
|
|
13
|
+
|
|
14
|
+
@@local_iglu_resolver_path = ENV['LOCAL_IGLU_RESOLVER_PATH']
|
|
15
|
+
@@debug_mode = ENV['SNOWLY_DEBUG_MODE'] || false
|
|
16
|
+
|
|
17
|
+
def self.config
|
|
18
|
+
yield self
|
|
19
|
+
end
|
|
5
20
|
end
|
|
21
|
+
|
data/snowly.gemspec
CHANGED
|
@@ -9,20 +9,29 @@ Gem::Specification.new do |spec|
|
|
|
9
9
|
spec.authors = ["Alexandre Angelim"]
|
|
10
10
|
spec.email = ["angelim@angelim.com.br"]
|
|
11
11
|
|
|
12
|
-
spec.summary = %q{Snowplow
|
|
13
|
-
spec.description = %q{
|
|
12
|
+
spec.summary = %q{Snowplow Request Validator}
|
|
13
|
+
spec.description = %q{Snowly is a minimal collector implementation intended to validate your event tracking requests before emitting them to cloudfront or a closure collector.}
|
|
14
14
|
spec.homepage = "https://github.com/angelim/snowly"
|
|
15
|
+
spec.license = "MIT"
|
|
15
16
|
|
|
16
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
17
|
-
spec.bindir = "
|
|
18
|
-
spec.executables
|
|
18
|
+
spec.bindir = "bin"
|
|
19
|
+
spec.executables << 'snowly'
|
|
19
20
|
spec.require_paths = ["lib"]
|
|
21
|
+
|
|
20
22
|
|
|
21
|
-
spec.add_dependency 'json-schema', '~> 2.6
|
|
23
|
+
spec.add_dependency 'json-schema', '~> 2.6'
|
|
24
|
+
spec.add_dependency 'rack', '~> 1.6'
|
|
25
|
+
spec.add_dependency 'activesupport', "~> 3.0"
|
|
26
|
+
spec.add_dependency 'sinatra', '~> 1.4'
|
|
27
|
+
spec.add_dependency 'sinatra-contrib', '~> 1.4'
|
|
28
|
+
spec.add_dependency 'vegas', '~> 0.1'
|
|
22
29
|
|
|
23
30
|
spec.add_development_dependency 'bundler', '~> 1.11'
|
|
24
31
|
spec.add_development_dependency 'rake', '~> 10.0'
|
|
25
32
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
26
|
-
spec.add_development_dependency 'pry'
|
|
27
|
-
spec.add_development_dependency 'snowplow-tracker', '~> 0.5
|
|
33
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.3'
|
|
34
|
+
spec.add_development_dependency 'snowplow-tracker', '~> 0.5'
|
|
35
|
+
spec.add_development_dependency 'webmock', '~> 2.0'
|
|
36
|
+
spec.add_development_dependency "shotgun", '~> 0.9'
|
|
28
37
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: snowly
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexandre Angelim
|
|
8
8
|
autorequire:
|
|
9
|
-
bindir:
|
|
9
|
+
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2016-
|
|
11
|
+
date: 2016-06-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json-schema
|
|
@@ -16,14 +16,84 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ~>
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 2.6
|
|
19
|
+
version: '2.6'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ~>
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 2.6
|
|
26
|
+
version: '2.6'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rack
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ~>
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.6'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ~>
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.6'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: activesupport
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ~>
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ~>
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: sinatra
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ~>
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.4'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ~>
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.4'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: sinatra-contrib
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ~>
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '1.4'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ~>
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.4'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: vegas
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ~>
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0.1'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ~>
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0.1'
|
|
27
97
|
- !ruby/object:Gem::Dependency
|
|
28
98
|
name: bundler
|
|
29
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -67,38 +137,67 @@ dependencies:
|
|
|
67
137
|
- !ruby/object:Gem::Version
|
|
68
138
|
version: '3.0'
|
|
69
139
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: pry
|
|
140
|
+
name: pry-byebug
|
|
71
141
|
requirement: !ruby/object:Gem::Requirement
|
|
72
142
|
requirements:
|
|
73
|
-
- -
|
|
143
|
+
- - ~>
|
|
74
144
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
145
|
+
version: '3.3'
|
|
76
146
|
type: :development
|
|
77
147
|
prerelease: false
|
|
78
148
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
149
|
requirements:
|
|
80
|
-
- -
|
|
150
|
+
- - ~>
|
|
81
151
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
152
|
+
version: '3.3'
|
|
83
153
|
- !ruby/object:Gem::Dependency
|
|
84
154
|
name: snowplow-tracker
|
|
85
155
|
requirement: !ruby/object:Gem::Requirement
|
|
86
156
|
requirements:
|
|
87
157
|
- - ~>
|
|
88
158
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: 0.5
|
|
159
|
+
version: '0.5'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - ~>
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '0.5'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: webmock
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - ~>
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '2.0'
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - ~>
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '2.0'
|
|
181
|
+
- !ruby/object:Gem::Dependency
|
|
182
|
+
name: shotgun
|
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
|
184
|
+
requirements:
|
|
185
|
+
- - ~>
|
|
186
|
+
- !ruby/object:Gem::Version
|
|
187
|
+
version: '0.9'
|
|
90
188
|
type: :development
|
|
91
189
|
prerelease: false
|
|
92
190
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
191
|
requirements:
|
|
94
192
|
- - ~>
|
|
95
193
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: 0.
|
|
97
|
-
description:
|
|
98
|
-
|
|
194
|
+
version: '0.9'
|
|
195
|
+
description: Snowly is a minimal collector implementation intended to validate your
|
|
196
|
+
event tracking requests before emitting them to cloudfront or a closure collector.
|
|
99
197
|
email:
|
|
100
198
|
- angelim@angelim.com.br
|
|
101
|
-
executables:
|
|
199
|
+
executables:
|
|
200
|
+
- snowly
|
|
102
201
|
extensions: []
|
|
103
202
|
extra_rdoc_files: []
|
|
104
203
|
files:
|
|
@@ -110,11 +209,23 @@ files:
|
|
|
110
209
|
- Rakefile
|
|
111
210
|
- bin/console
|
|
112
211
|
- bin/setup
|
|
212
|
+
- bin/snowly
|
|
213
|
+
- config.ru
|
|
214
|
+
- lib/schemas/snowplow_protocol.json
|
|
113
215
|
- lib/snowly.rb
|
|
216
|
+
- lib/snowly/app/collector.rb
|
|
217
|
+
- lib/snowly/app/views/index.erb
|
|
218
|
+
- lib/snowly/extensions/custom_dependencies.rb
|
|
219
|
+
- lib/snowly/request.rb
|
|
220
|
+
- lib/snowly/schema_cache.rb
|
|
221
|
+
- lib/snowly/transformer.rb
|
|
222
|
+
- lib/snowly/validator.rb
|
|
223
|
+
- lib/snowly/validators/self_desc.rb
|
|
114
224
|
- lib/snowly/version.rb
|
|
115
225
|
- snowly.gemspec
|
|
116
226
|
homepage: https://github.com/angelim/snowly
|
|
117
|
-
licenses:
|
|
227
|
+
licenses:
|
|
228
|
+
- MIT
|
|
118
229
|
metadata: {}
|
|
119
230
|
post_install_message:
|
|
120
231
|
rdoc_options: []
|
|
@@ -135,6 +246,6 @@ rubyforge_project:
|
|
|
135
246
|
rubygems_version: 2.2.2
|
|
136
247
|
signing_key:
|
|
137
248
|
specification_version: 4
|
|
138
|
-
summary: Snowplow
|
|
249
|
+
summary: Snowplow Request Validator
|
|
139
250
|
test_files: []
|
|
140
251
|
has_rdoc:
|