snowly 0.2.0 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|