snowly 0.2.0 → 0.2.2
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/config.ru +2 -1
- data/lib/schemas/snowplow_protocol.json +1 -1
- data/lib/snowly/app/collector.rb +13 -20
- data/lib/snowly/app/views/index.erb +1 -1
- data/lib/snowly/app/views/js.erb +153 -0
- data/lib/snowly/each_validator.rb +109 -0
- data/lib/snowly/schema_cache.rb +16 -19
- data/lib/snowly/transformer.rb +1 -1
- data/lib/snowly/validator.rb +16 -84
- data/lib/snowly/version.rb +1 -1
- data/lib/snowly.rb +1 -1
- data/snowly.gemspec +1 -0
- metadata +18 -3
- data/lib/snowly/multi_validator.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8363b19687de0fa4f4812b7a6209a2caf414149e
|
4
|
+
data.tar.gz: e9b6a30be212eb0f42196f696dc10a6013cad6b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e713e4e7f804f8cf715730a9c157cfb4bf6493f019f59658e83752af4fc0d029af70087d8368d59d1c34b6b72757fd1c57d00f6c898d3f54edca56489edbe37
|
7
|
+
data.tar.gz: 71423a4271c014abdecd4f4e146cf5a8aaa338a482fd417386dd5a1424e150aa512fee2b113544868d3b9fe582bf35d4380b206b427d7d12b4febc379efc95ed
|
data/config.ru
CHANGED
@@ -249,7 +249,7 @@
|
|
249
249
|
"type": "number"
|
250
250
|
}
|
251
251
|
},
|
252
|
-
"required": ["app_id", "platform", "event", "event_id", "v_tracker"
|
252
|
+
"required": ["app_id", "platform", "event", "event_id", "v_tracker"],
|
253
253
|
"custom_dependencies": {
|
254
254
|
"se_category": { "event": "se" },
|
255
255
|
"se_action": {"event": "se" },
|
data/lib/snowly/app/collector.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'thin'
|
1
2
|
require 'erb'
|
2
3
|
require 'base64'
|
3
4
|
require 'sinatra'
|
@@ -6,44 +7,33 @@ require 'sinatra/reloader' if development?
|
|
6
7
|
module Snowly
|
7
8
|
module App
|
8
9
|
class Collector < Sinatra::Base
|
10
|
+
set :server, 'thin'
|
9
11
|
GIF = Base64.decode64('R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==')
|
10
12
|
configure :development do
|
11
13
|
register Sinatra::Reloader
|
12
14
|
end
|
13
15
|
|
14
|
-
def extract_content(validator)
|
15
|
-
multi = validator.respond_to?(:validators)
|
16
|
-
@content ||= if multi
|
17
|
-
validator.validators.each_with_object([]) do |item, memo|
|
18
|
-
item_request = item.request.as_hash
|
19
|
-
memo << { event_id: item_request['event_id'], errors: item.errors, content: item_request }
|
20
|
-
end
|
21
|
-
else
|
22
|
-
[{ event_id: validator.request.as_hash['event_id'], errors: validator.errors, content: validator.request.as_hash }]
|
23
|
-
end.to_json
|
24
|
-
end
|
25
|
-
|
26
16
|
def handle_response(validator)
|
17
|
+
content_type :json
|
27
18
|
if validator.validate
|
28
19
|
status 200
|
29
20
|
if params[:debug] || Snowly.debug_mode
|
30
|
-
content =
|
21
|
+
content = validator.as_hash
|
31
22
|
Snowly.logger.info content
|
32
|
-
body(content)
|
23
|
+
body(content.to_json)
|
33
24
|
else
|
34
25
|
content_type 'image/gif'
|
35
26
|
Snowly::App::Collector::GIF
|
36
27
|
end
|
37
28
|
else
|
38
|
-
status
|
39
|
-
content =
|
29
|
+
status 422
|
30
|
+
content = validator.as_hash
|
40
31
|
Snowly.logger.error content
|
41
|
-
body (content)
|
32
|
+
body (content.to_json)
|
42
33
|
end
|
43
34
|
end
|
44
35
|
|
45
36
|
get '/' do
|
46
|
-
@url = request.url.gsub(/(http|https)\:\/\//,'')[0..-2]
|
47
37
|
@resolved_schemas = if resolver = Snowly.development_iglu_resolver_path
|
48
38
|
Dir[File.join(resolver,"/**/*")].select{ |e| File.file? e }
|
49
39
|
else
|
@@ -53,18 +43,21 @@ module Snowly
|
|
53
43
|
end
|
54
44
|
|
55
45
|
get '/i' do
|
56
|
-
content_type :json
|
57
46
|
validator = Snowly::Validator.new request.query_string
|
58
47
|
handle_response(validator)
|
59
48
|
end
|
60
49
|
|
50
|
+
get '/js' do
|
51
|
+
erb :js
|
52
|
+
end
|
53
|
+
|
61
54
|
post '/com.snowplowanalytics.snowplow/tp2' do
|
62
55
|
response.headers['Allow'] = 'HEAD,GET,PUT,POST,DELETE,OPTIONS'
|
63
56
|
response.headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept'
|
64
57
|
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
65
58
|
response.headers['Access-Control-Allow-Origin'] = env['HTTP_ORIGIN'] || '*'
|
66
59
|
payload = JSON.parse request.body.read
|
67
|
-
validator = Snowly::
|
60
|
+
validator = Snowly::Validator.new payload, batch: true
|
68
61
|
handle_response(validator)
|
69
62
|
end
|
70
63
|
|
@@ -22,7 +22,7 @@
|
|
22
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
23
|
<p>When <strong>Snowly</strong> finds something wrong, it renders the parsed request along with its errors.</p>
|
24
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><%=
|
25
|
+
<p>Point your collector URL to <code><%= request.env['HTTP_HOST'] %></code> and have fun!</p>
|
26
26
|
<p>
|
27
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
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&eid=u2i3&debug=true" role="button">Event with errors!</a>
|
@@ -0,0 +1,153 @@
|
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
2
|
+
"http://www.w3.org/TR/html4/loose.dtd">
|
3
|
+
<!--
|
4
|
+
! Copyright (c) 2012-2013 Snowplow Analytics Ltd. All rights reserved.
|
5
|
+
!
|
6
|
+
! This program is licensed to you under the Apache License Version 2.0,
|
7
|
+
! and you may not use this file except in compliance with the Apache License Version 2.0.
|
8
|
+
! You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
|
9
|
+
!
|
10
|
+
! Unless required by applicable law or agreed to in writing,
|
11
|
+
! software distributed under the Apache License Version 2.0 is distributed on an
|
12
|
+
! "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
! See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
|
14
|
+
-->
|
15
|
+
<html>
|
16
|
+
<head>
|
17
|
+
<title>Small asynchronous website/webapp examples for snowplow.js</title>
|
18
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
19
|
+
|
20
|
+
<!-- Snowplow starts plowing -->
|
21
|
+
<script type="text/javascript">
|
22
|
+
|
23
|
+
;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
|
24
|
+
p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
|
25
|
+
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
|
26
|
+
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","//d1fc8wv8zag5ca.cloudfront.net/2.6.1/sp.js","snowplow"));
|
27
|
+
|
28
|
+
window.snowplow('newTracker', 'cf', '<%= request.env['HTTP_HOST']%>', { // Initialise a tracker
|
29
|
+
encodeBase64: false, // Default is true
|
30
|
+
appId: 'CFe23a', // Site ID can be anything you want. Set it if you're tracking more than one site in this account
|
31
|
+
platform: 'mob',
|
32
|
+
useLocalStorage: false,
|
33
|
+
post: true,
|
34
|
+
forceUnsecureTracker: true,
|
35
|
+
contexts: {
|
36
|
+
webPage: true,
|
37
|
+
gaCookies: true,
|
38
|
+
performanceTiming: true
|
39
|
+
}
|
40
|
+
});
|
41
|
+
|
42
|
+
window.snowplow('setUserId', 'alex 123'); // Business-defined user ID
|
43
|
+
//window.snowplow('setUserIdFromLocation', 'id'); // To test this, reload the page with ?id=xxx
|
44
|
+
//window.snowplow('setUserIdFromCookie', '_sp_id.4209') // Test this using Firefox because Chrome doesn't allow local cookies.
|
45
|
+
window.snowplow('setCustomUrl', '/overridden-url/'); // Override the page URL
|
46
|
+
// window.snowplow('enableActivityTracking', 10, 10); // Ping every 10 seconds after 10 seconds
|
47
|
+
window.snowplow('enableLinkClickTracking', {blacklist: ['barred']}, true); // Track clicks on links whose class is not "barred"
|
48
|
+
window.snowplow('trackPageView', 'Async Test'); // Track the page view with custom title
|
49
|
+
|
50
|
+
</script>
|
51
|
+
<!-- Snowplow stops plowing -->
|
52
|
+
|
53
|
+
<!-- Example events -->
|
54
|
+
<script type="text/javascript">
|
55
|
+
|
56
|
+
function playMix() {
|
57
|
+
alert("Playing a DJ mix");
|
58
|
+
window.snowplow('trackStructEvent', 'Mixes', 'Play', 'MRC/fabric-0503-mix', '', '0.0');
|
59
|
+
}
|
60
|
+
|
61
|
+
function addProduct() {
|
62
|
+
alert("Adding a product to basket");
|
63
|
+
window.snowplow('trackStructEvent', 'Checkout', 'Add', 'ASO01043', 'blue:xxl', '2.0' );
|
64
|
+
}
|
65
|
+
|
66
|
+
function wrongAddProduct() {
|
67
|
+
alert("Adding a product to basket");
|
68
|
+
window.snowplow('trackStructEvent', '', 'Add', 'ASO01043', 'blue:xxl', '2.0' );
|
69
|
+
}
|
70
|
+
|
71
|
+
function viewProduct() {
|
72
|
+
alert("Viewing a product");
|
73
|
+
window.snowplow('trackUnstructEvent', {
|
74
|
+
schema: 'iglu:com.acme_company/viewed_product/jsonschema/5-0-0',
|
75
|
+
data: {
|
76
|
+
productId: 'ASO01043',
|
77
|
+
category: 'Dresses',
|
78
|
+
brand: 'ACME',
|
79
|
+
returning: true,
|
80
|
+
price: 49.95,
|
81
|
+
sizes: ['xs', 's', 'l', 'xl', 'xxl'],
|
82
|
+
availableSince: new Date(2013,3,7)
|
83
|
+
}
|
84
|
+
});
|
85
|
+
}
|
86
|
+
|
87
|
+
function addEcommerceTransaction() {
|
88
|
+
alert('Adding an ecommerce transaction');
|
89
|
+
|
90
|
+
var orderId = 'order-123';
|
91
|
+
|
92
|
+
// addTrans sets up the transaction, should be called first.
|
93
|
+
window.snowplow('addTrans',
|
94
|
+
orderId, // order ID - required
|
95
|
+
'', // affiliation or store name
|
96
|
+
'8000', // total - required
|
97
|
+
'', // tax
|
98
|
+
'', // shipping
|
99
|
+
'', // city
|
100
|
+
'', // state or province
|
101
|
+
'', // country
|
102
|
+
'JPY' // currency
|
103
|
+
);
|
104
|
+
|
105
|
+
// addItem might be called for each item in the shopping cart,
|
106
|
+
// or not at all.
|
107
|
+
window.snowplow('addItem',
|
108
|
+
orderId, // order ID - required
|
109
|
+
'1001', // SKU - required
|
110
|
+
'Blue t-shirt', // product name
|
111
|
+
'', // category
|
112
|
+
'2000', // unit price - required
|
113
|
+
'2', // quantity - required
|
114
|
+
'JPY');
|
115
|
+
|
116
|
+
window.snowplow('addItem',
|
117
|
+
orderId, // order ID - required
|
118
|
+
'1002', // SKU - required
|
119
|
+
'Red shoes', // product name
|
120
|
+
'', // category
|
121
|
+
'4000', // unit price - required
|
122
|
+
'1', // quantity - required
|
123
|
+
'JPY' // currency
|
124
|
+
);
|
125
|
+
|
126
|
+
// trackTrans sends the transaction to Snowplow tracking servers.
|
127
|
+
// Must be called last to commit the transaction.
|
128
|
+
window.snowplow('trackTrans');
|
129
|
+
|
130
|
+
}
|
131
|
+
</script>
|
132
|
+
</head>
|
133
|
+
|
134
|
+
<body>
|
135
|
+
<h1>Small_asynchronous_examples_for_snowplow.js</h1>
|
136
|
+
|
137
|
+
<p>Warning: if your browser's Do Not Track feature is enabled and respectDoNotTrack is enabled, all tracking will be prevented.</p>
|
138
|
+
<p>If you are viewing the page using a file URL, you must edit the script URL in the Snowplow tag to include an http scheme. Otherwise a file scheme will be inferred and the page will attempt to load sp.js from the local filesystem..</p>
|
139
|
+
|
140
|
+
<p>Press the buttons below to trigger individual tracking events:<br>
|
141
|
+
<button type="button" onclick="playMix()">Play a mix</button><br>
|
142
|
+
<button type="button" onclick="addProduct()">Add a product</button><br>
|
143
|
+
<button type="button" onclick="wrongAddProduct()">Add a product missing required field</button><br>
|
144
|
+
<button type="button" onclick="viewProduct()">View a product</button><br>
|
145
|
+
<button type="button" onclick="addEcommerceTransaction()">Add an ecommerce transaction</button>
|
146
|
+
</p>
|
147
|
+
<a href=http://en.wikipedia.org/wiki/Main_Page id=legal target="_blank" class="link out">Link</a>
|
148
|
+
<br>
|
149
|
+
<a href=http://en.wikipedia.org/wiki/Main_Page id=illegal class="class barred">Ignored link</a>
|
150
|
+
|
151
|
+
</body>
|
152
|
+
|
153
|
+
</html>
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# Performs the validation for the root attributes and associated contexts and unstructured events.
|
2
|
+
require 'snowly/request'
|
3
|
+
require 'snowly/protocol_schema_finder'
|
4
|
+
require 'snowly/extensions/custom_dependencies'
|
5
|
+
|
6
|
+
module Snowly
|
7
|
+
class EachValidator
|
8
|
+
attr_reader :request, :errors
|
9
|
+
|
10
|
+
def initialize(payload)
|
11
|
+
@request = Request.new payload
|
12
|
+
@errors = []
|
13
|
+
end
|
14
|
+
|
15
|
+
# If request is valid
|
16
|
+
# @return [true, false] if valid
|
17
|
+
def valid?
|
18
|
+
@errors == []
|
19
|
+
end
|
20
|
+
|
21
|
+
# Entry point for validation.
|
22
|
+
def validate
|
23
|
+
validate_root
|
24
|
+
validate_associated
|
25
|
+
valid?
|
26
|
+
end
|
27
|
+
|
28
|
+
def protocol_schema
|
29
|
+
@protocol_schema ||= ProtocolSchemaFinder.new.schema
|
30
|
+
end
|
31
|
+
|
32
|
+
def as_hash
|
33
|
+
{ event_id: request.as_hash['event_id'], errors: errors, content: request.as_hash }
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# @return [Hash] all contexts content and schema definitions
|
39
|
+
def associated_contexts
|
40
|
+
load_contexts request.as_hash['contexts']
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Hash] all unstructured events content and schema definitions
|
44
|
+
def associated_unstruct_event
|
45
|
+
load_unstruct_event request.as_hash['unstruct_event']
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Array<Hash>] all associated content
|
49
|
+
def associated_elements
|
50
|
+
(Array(associated_contexts) + Array(associated_unstruct_event)).compact
|
51
|
+
end
|
52
|
+
|
53
|
+
# Performs initial validation for associated contexts and loads their contents and definitions.
|
54
|
+
# @return [Array<Hash>]
|
55
|
+
def load_contexts(hash)
|
56
|
+
return unless hash
|
57
|
+
response = []
|
58
|
+
unless hash['data']
|
59
|
+
@errors << "All custom contexts must be contain a `data` element" and return
|
60
|
+
end
|
61
|
+
schema = SchemaCache.instance[hash['schema']]
|
62
|
+
response << { content: hash['data'], definition: schema, schema_name: hash['schema'] }
|
63
|
+
unless hash['data'].is_a? Array
|
64
|
+
@errors << "All custom contexts must be wrapped in an Array" and return
|
65
|
+
end
|
66
|
+
hash['data'].each do |data_item|
|
67
|
+
schema = SchemaCache.instance[data_item['schema']]
|
68
|
+
response << { content: data_item['data'], definition: schema, schema_name: data_item['schema'] }
|
69
|
+
end
|
70
|
+
response
|
71
|
+
end
|
72
|
+
|
73
|
+
def register_missing_schema(name)
|
74
|
+
@errors << "#{ name } wasn't found in any resolvers."
|
75
|
+
end
|
76
|
+
|
77
|
+
# Performs initial validation for associated unstructured events and loads their contents and definitions.
|
78
|
+
# @return [Array<Hash>]
|
79
|
+
def load_unstruct_event(hash)
|
80
|
+
return unless hash
|
81
|
+
response = []
|
82
|
+
unless hash['data']
|
83
|
+
@errors << "All custom unstruct event must be contain a `data` element" and return
|
84
|
+
end
|
85
|
+
outer_data = hash['data']
|
86
|
+
inner_data = outer_data['data']
|
87
|
+
response << { content: outer_data, definition: SchemaCache.instance[hash['schema']], schema_name: hash['schema'] }
|
88
|
+
response << { content: inner_data, definition: SchemaCache.instance[outer_data['schema']], schema_name: outer_data['schema'] }
|
89
|
+
response
|
90
|
+
end
|
91
|
+
|
92
|
+
# Validates associated contexts and unstructured events
|
93
|
+
def validate_associated
|
94
|
+
return unless associated_elements
|
95
|
+
missing_schemas, valid_elements = associated_elements.partition{ |el| el[:definition].blank? }
|
96
|
+
missing_schemas.each { |element| register_missing_schema(element[:schema_name]) }
|
97
|
+
valid_elements.each do |element|
|
98
|
+
this_error = JSON::Validator.fully_validate JSON.parse(element[:definition]), element[:content]
|
99
|
+
@errors += this_error if this_error.count > 0
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Validates root attributes for the events table
|
104
|
+
def validate_root
|
105
|
+
this_error = JSON::Validator.fully_validate protocol_schema, request.as_hash
|
106
|
+
@errors += this_error if this_error.count > 0
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/snowly/schema_cache.rb
CHANGED
@@ -26,13 +26,6 @@ module Snowly
|
|
26
26
|
|
27
27
|
private
|
28
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
29
|
def external?(location)
|
37
30
|
location.match(/^(http|https):\/\//)
|
38
31
|
end
|
@@ -42,25 +35,29 @@ module Snowly
|
|
42
35
|
# @param resolver [String] local or remote path to look for the schema
|
43
36
|
# @return [String] Schema's actual location
|
44
37
|
def resolve(location, resolver)
|
45
|
-
location.sub(/^iglu\:/,
|
46
|
-
|
47
|
-
|
48
|
-
def resolved_path(location)
|
49
|
-
if from_snowplow?(location)
|
50
|
-
resolve(location, SNOWPLOW_IGLU_RESOLVER)
|
51
|
-
else
|
52
|
-
resolve(location, Snowly.development_iglu_resolver_path)
|
53
|
-
end
|
38
|
+
path = location.sub(/^iglu\:/, '')
|
39
|
+
File.join resolver, path
|
54
40
|
end
|
55
41
|
|
56
42
|
# Caches the schema content under its original location name
|
57
43
|
# @param location [String]
|
58
44
|
# @return [String] schema content
|
59
45
|
def save_in_cache(location)
|
60
|
-
|
61
|
-
|
46
|
+
content = begin
|
47
|
+
full_path = resolve(location, (Snowly.development_iglu_resolver_path || SNOWPLOW_IGLU_RESOLVER) )
|
48
|
+
external?(full_path) ? Net::HTTP.get(URI(full_path)) : File.read(full_path)
|
49
|
+
rescue
|
50
|
+
Snowly.logger.warn "Could't locate #{location} in development resolver. Attemping IgluCentral Server..."
|
51
|
+
full_path = resolve(location, SNOWPLOW_IGLU_RESOLVER)
|
52
|
+
begin
|
53
|
+
result = Net::HTTP.get(URI(full_path))
|
54
|
+
JSON.load(result) && result
|
55
|
+
rescue
|
56
|
+
Snowly.logger.error "#{location} schema is not available in any resolver"
|
57
|
+
return nil
|
58
|
+
end
|
59
|
+
end
|
62
60
|
@@schema_cache[location] = content
|
63
61
|
end
|
64
|
-
|
65
62
|
end
|
66
63
|
end
|
data/lib/snowly/transformer.rb
CHANGED
@@ -35,7 +35,7 @@ module Snowly
|
|
35
35
|
"f_ag" => { field: "br_features_silverlight", type: "string" },
|
36
36
|
"cookie" => { field: "br_cookies", type: "string" },
|
37
37
|
"res" => { field: "screen_res_width_x_height", type: "string" },
|
38
|
-
"cd" => { field: "br_colordepth", type: "
|
38
|
+
"cd" => { field: "br_colordepth", type: "integer" },
|
39
39
|
"tz" => { field: "os_timezone", type: "string" },
|
40
40
|
"refr" => { field: "page_referrer", type: "string" },
|
41
41
|
"url" => { field: "page_url", type: "string" },
|
data/lib/snowly/validator.rb
CHANGED
@@ -1,97 +1,29 @@
|
|
1
|
-
|
2
|
-
require 'snowly/request'
|
3
|
-
require 'snowly/protocol_schema_finder'
|
4
|
-
require 'snowly/extensions/custom_dependencies'
|
5
|
-
|
1
|
+
require 'snowly/each_validator'
|
6
2
|
module Snowly
|
7
3
|
class Validator
|
8
|
-
attr_reader :
|
9
|
-
|
10
|
-
def initialize(payload)
|
11
|
-
@
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@errors == []
|
4
|
+
attr_reader :validators
|
5
|
+
|
6
|
+
def initialize(payload, batch: false)
|
7
|
+
@validators = if batch
|
8
|
+
payload['data'].map do |req|
|
9
|
+
EachValidator.new(req)
|
10
|
+
end
|
11
|
+
else
|
12
|
+
[ EachValidator.new(payload) ]
|
13
|
+
end
|
19
14
|
end
|
20
15
|
|
21
|
-
# Entry point for validation.
|
22
16
|
def validate
|
23
|
-
|
24
|
-
validate_associated
|
17
|
+
validators.each(&:validate)
|
25
18
|
valid?
|
26
19
|
end
|
27
20
|
|
28
|
-
def
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
# @return [Hash] all contexts content and schema definitions
|
35
|
-
def associated_contexts
|
36
|
-
load_contexts request.as_hash['contexts']
|
37
|
-
end
|
38
|
-
|
39
|
-
# @return [Hash] all unstructured events content and schema definitions
|
40
|
-
def associated_unstruct_event
|
41
|
-
load_unstruct_event request.as_hash['unstruct_event']
|
42
|
-
end
|
43
|
-
|
44
|
-
# @return [Array<Hash>] all associated content
|
45
|
-
def associated_elements
|
46
|
-
(Array(associated_contexts) + Array(associated_unstruct_event)).compact
|
47
|
-
end
|
48
|
-
|
49
|
-
# Performs initial validation for associated contexts and loads their contents and definitions.
|
50
|
-
# @return [Array<Hash>]
|
51
|
-
def load_contexts(hash)
|
52
|
-
return unless hash
|
53
|
-
response = []
|
54
|
-
unless hash['data']
|
55
|
-
@errors << "All custom contexts must be contain a `data` element" and return
|
56
|
-
end
|
57
|
-
response << { content: hash['data'], definition: SchemaCache.instance[hash['schema']] }
|
58
|
-
unless hash['data'].is_a? Array
|
59
|
-
@errors << "All custom contexts must be wrapped in an Array" and return
|
60
|
-
end
|
61
|
-
hash['data'].each do |data_item|
|
62
|
-
response << { content: data_item['data'], definition: SchemaCache.instance[data_item['schema']] }
|
63
|
-
end
|
64
|
-
response
|
65
|
-
end
|
66
|
-
|
67
|
-
# Performs initial validation for associated unstructured events and loads their contents and definitions.
|
68
|
-
# @return [Array<Hash>]
|
69
|
-
def load_unstruct_event(hash)
|
70
|
-
return unless hash
|
71
|
-
response = []
|
72
|
-
unless hash['data']
|
73
|
-
@errors << "All custom unstruct event must be contain a `data` element" and return
|
74
|
-
end
|
75
|
-
outer_data = hash['data']
|
76
|
-
inner_data = outer_data['data']
|
77
|
-
response << { content: outer_data, definition: SchemaCache.instance[hash['schema']] }
|
78
|
-
response << { content: inner_data, definition: SchemaCache.instance[outer_data['schema']] }
|
79
|
-
response
|
80
|
-
end
|
81
|
-
|
82
|
-
# Validates associated contexts and unstructured events
|
83
|
-
def validate_associated
|
84
|
-
return unless associated_elements
|
85
|
-
associated_elements.each do |schema|
|
86
|
-
this_error = JSON::Validator.fully_validate JSON.parse(schema[:definition]), schema[:content]
|
87
|
-
@errors += this_error if this_error.count > 0
|
88
|
-
end
|
21
|
+
def valid?
|
22
|
+
validators.all? { |v| v.valid? }
|
89
23
|
end
|
90
24
|
|
91
|
-
|
92
|
-
|
93
|
-
this_error = JSON::Validator.fully_validate protocol_schema, request.as_hash
|
94
|
-
@errors += this_error if this_error.count > 0
|
25
|
+
def as_hash
|
26
|
+
validators.map(&:as_hash)
|
95
27
|
end
|
96
28
|
end
|
97
29
|
end
|
data/lib/snowly/version.rb
CHANGED
data/lib/snowly.rb
CHANGED
data/snowly.gemspec
CHANGED
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_dependency 'sinatra', '~> 1.4'
|
27
27
|
spec.add_dependency 'sinatra-contrib', '~> 1.4'
|
28
28
|
spec.add_dependency 'vegas', '~> 0.1'
|
29
|
+
spec.add_dependency 'thin', '~> 1.7'
|
29
30
|
|
30
31
|
spec.add_development_dependency 'bundler', '~> 1.11'
|
31
32
|
spec.add_development_dependency 'rake', '~> 10.0'
|
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.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexandre Angelim
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-09-
|
11
|
+
date: 2016-09-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json-schema
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - ~>
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0.1'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: thin
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.7'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.7'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: bundler
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -215,8 +229,9 @@ files:
|
|
215
229
|
- lib/snowly.rb
|
216
230
|
- lib/snowly/app/collector.rb
|
217
231
|
- lib/snowly/app/views/index.erb
|
232
|
+
- lib/snowly/app/views/js.erb
|
233
|
+
- lib/snowly/each_validator.rb
|
218
234
|
- lib/snowly/extensions/custom_dependencies.rb
|
219
|
-
- lib/snowly/multi_validator.rb
|
220
235
|
- lib/snowly/protocol_schema_finder.rb
|
221
236
|
- lib/snowly/request.rb
|
222
237
|
- lib/snowly/schema_cache.rb
|
@@ -1,21 +0,0 @@
|
|
1
|
-
require 'snowly/validator'
|
2
|
-
module Snowly
|
3
|
-
class MultiValidator
|
4
|
-
attr_reader :validators
|
5
|
-
|
6
|
-
def initialize(payload)
|
7
|
-
@validators = payload['data'].map do |req|
|
8
|
-
Validator.new(req)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def validate
|
13
|
-
validators.each(&:validate)
|
14
|
-
valid?
|
15
|
-
end
|
16
|
-
|
17
|
-
def valid?
|
18
|
-
validators.all? { |v| v.valid? }
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|