vert 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/vert.rb +369 -0
  3. metadata +72 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cc6d3bfb12ff30eaad91ca1aaa5d1cb3b22e9f57
4
+ data.tar.gz: 6ab62d9c55248d41c3fa360714b585e54d87924a
5
+ SHA512:
6
+ metadata.gz: 516fe980cc3e286f8f08fed032e79dd2f0b2a98c9770bee92e416e3f93cd82295aa2b9ac98a4e72d918950b5da8282b5ff3e6c4a91558605b10e51a77b0524a4
7
+ data.tar.gz: 97164fdcc94904f226b4a3a2a33bc1756a62cb7992bbfb352ebe5a2560e29b4eef3ed09415b2fabb1956f6f8410321b75045df12f3082263dff58e4a78b266dc
@@ -0,0 +1,369 @@
1
+ require 'oj'
2
+ require 'avro'
3
+
4
+ module Vert
5
+
6
+ extend self
7
+
8
+ InputError = Class.new(StandardError)
9
+ ValidationError = Class.new(StandardError)
10
+
11
+ #error messages for hash validation
12
+ NOT_A_HASH_ERROR = "Not a hash."
13
+ NOT_A_HASH_ERROR_KEY = :not_a_hash
14
+
15
+ EMPTY_ERROR = "The hash is empty."
16
+ EMPTY_ERROR_KEY = :empty
17
+
18
+ ABSENT_KEY_ERROR = "The data does not contain the following key/s"
19
+ ABSENT_KEY_ERROR_KEY = :absent_key
20
+
21
+ ARRAY_TYPE_ERROR = "The following key/s do not have Array type values."
22
+ ARRAY_TYPE_ERROR_KEY = :array_type
23
+
24
+ HASH_TYPE_ERROR = "The following key/s do not have Hash type values."
25
+ HASH_TYPE_ERROR_KEY = :hash_type
26
+
27
+ ARRAY_EMPTY_ERROR = "The following array key/s are empty."
28
+ ARRAY_EMPTY_ERROR_KEY = :array_empty
29
+
30
+ HASH_EMPTY_ERROR = "The following hash key/s are empty."
31
+ HASH_EMPTY_ERROR_KEY = :hash_empty
32
+
33
+ #error messages for json validation
34
+ NOT_A_STRING_ERROR = "Not a JSON string."
35
+ NOT_A_STRING_ERROR_KEY = :not_a_string
36
+
37
+ EMPTY_JSON_ERROR = "The JSON string is empty."
38
+ EMPTY_JSON_ERROR_KEY = :empty_json
39
+
40
+ EMPTY_JSON_OBJECT_ERROR = "The JSON object is empty."
41
+ EMPTY_JSON_OBJECT_ERROR_KEY = :empty_json_object
42
+
43
+ MALFORMED_JSON_ERROR = "The JSON string is malformed."
44
+ MALFORMED_JSON_ERROR_KEY = :malformed_json
45
+
46
+ #error messages for avro verification
47
+ INVALID_AVRO_SCHEMA_ERROR = "The avro schema is invalid."
48
+ INVALID_AVRO_SCHEMA_ERROR_KEY = :invalid_avro_schema
49
+
50
+ INVALID_AVRO_DATUM_ERROR = "The JSON provided is not an instance of the schema:"
51
+ INVALID_AVRO_DATUM_ERROR_KEY = :invalid_avro_datum
52
+
53
+ #Enums
54
+ TYPE_ENUM = {array_keys: Array, hash_keys: Hash}
55
+ OPTIONS_HASH_ENUM = [:keys, :custom_errors]
56
+ OPTIONS_JSON_HASH_ENUM = [:schema, :custom_errors]
57
+ KEYS_ENUM = [:value_keys, :array_keys, :hash_keys]
58
+ ERROR_KEY_ENUM = {
59
+ NOT_A_HASH_ERROR_KEY => NOT_A_HASH_ERROR,
60
+ EMPTY_ERROR_KEY => EMPTY_ERROR,
61
+ ABSENT_KEY_ERROR_KEY => ABSENT_KEY_ERROR,
62
+ ARRAY_TYPE_ERROR_KEY => ARRAY_TYPE_ERROR,
63
+ HASH_TYPE_ERROR_KEY => HASH_TYPE_ERROR,
64
+ ARRAY_EMPTY_ERROR_KEY => ARRAY_EMPTY_ERROR,
65
+ HASH_EMPTY_ERROR_KEY => HASH_EMPTY_ERROR,
66
+ NOT_A_STRING_ERROR_KEY => NOT_A_STRING_ERROR,
67
+ EMPTY_JSON_ERROR_KEY => EMPTY_JSON_ERROR,
68
+ EMPTY_JSON_OBJECT_ERROR_KEY => EMPTY_JSON_OBJECT_ERROR,
69
+ MALFORMED_JSON_ERROR_KEY => MALFORMED_JSON_ERROR,
70
+ INVALID_AVRO_SCHEMA_ERROR_KEY => INVALID_AVRO_SCHEMA_ERROR,
71
+ INVALID_AVRO_DATUM_ERROR_KEY => INVALID_AVRO_DATUM_ERROR
72
+ }
73
+
74
+ #input validation
75
+ OPTIONS_HASH_EMPTY = "The options hash must contain keys"
76
+ OPTIONS_NOT_A_HASH = "The options parameter must be a hash"
77
+ OPTIONS_HASH_MISSING_VALID_KEYS = "The options hash contains no valid keys. The valid symbol keys are - "
78
+ KEYS_HASH_MISSING_VALID_KEYS = "The options hash contains no valid keys for the :keys hash. The valid symbol keys are - "
79
+ CUSTOM_ERRORS_HASH_MISSING_VALID_KEYS = "The options hash contains no valid keys for the :custom_errors hash. The valid symbol keys are - "
80
+ SCHEMA_NOT_A_STRING = "The options hash contains a :schema value which is not a string."
81
+ SCHEMA_NOT_JSON = "The options hash contains a :schema value which is not a JSON string"
82
+
83
+ def validate(hash, options = nil)
84
+ unless options.nil?
85
+ check_options_format(options)
86
+ check_options(options)
87
+ end
88
+ test_validations(hash, options)
89
+ rescue InputError => exception
90
+ build_error_output(exception)
91
+ end
92
+
93
+ def validate?(hash, options = nil)
94
+ check_options(options) unless options.nil?
95
+ test_validations(hash, options).nil? ? true : false
96
+ rescue InputError => exception
97
+ false
98
+ end
99
+
100
+ def validate_json?(json, options = nil)
101
+ check_json_options(options) unless options.nil?
102
+ validate_json(json, options).nil? ? true : false
103
+ rescue InputError => exception
104
+ false
105
+ end
106
+
107
+ def validate_json(json, options = nil)
108
+ unless options.nil?
109
+ check_options_format(options)
110
+ check_json_options(options)
111
+ end
112
+ test_validations_json(json, options)
113
+ rescue InputError => exception
114
+ build_error_output(exception)
115
+ end
116
+
117
+ def get_error_keys
118
+ pp ERROR_KEY_ENUM
119
+ end
120
+
121
+ private
122
+
123
+ def check_options(options)
124
+ raise_when_all_keys_missing(options, OPTIONS_HASH_MISSING_VALID_KEYS, OPTIONS_HASH_ENUM)
125
+ if options.keys.include?(:keys)
126
+ raise_when_all_keys_missing(options[:keys], KEYS_HASH_MISSING_VALID_KEYS, KEYS_ENUM)
127
+ end
128
+ end
129
+
130
+ def raise_when_all_keys_missing(hash, message, key_enum_array)
131
+ test_result = any_keys_present?(hash, key_enum_array)
132
+ raise InputError, "#{message}#{key_enum_array*", "}" unless test_result
133
+ end
134
+
135
+ def any_keys_present?(hash, key_array)
136
+ test_result = key_array.inject(false) do |memo, entry|
137
+ memo || hash.keys.include?(entry)
138
+ end
139
+ end
140
+
141
+ def check_options_format(options)
142
+ raise InputError, OPTIONS_NOT_A_HASH unless options.is_a?(Hash)
143
+ raise InputError, OPTIONS_HASH_EMPTY if options.empty?
144
+ if options.keys.include?(:custom_errors)
145
+ raise_when_all_keys_missing(options[:custom_errors], CUSTOM_ERRORS_HASH_MISSING_VALID_KEYS, ERROR_KEY_ENUM.keys)
146
+ end
147
+ end
148
+
149
+ def test_validations(hash, options)
150
+ test_for_default_hash_errors(hash, options)
151
+ if options_key_types_present?(options, :value_keys)
152
+ raise_when_keys_absent(hash, options, :value_keys)
153
+ elsif options_key_types_present?(options, :array_keys)
154
+ test_for_array_key_errors(hash, options)
155
+ elsif options_key_types_present?(options, :hash_keys)
156
+ test_for_hash_key_errors(hash, options)
157
+ end
158
+ rescue ValidationError => exception
159
+ build_error_output(exception)
160
+ else
161
+ nil
162
+ end
163
+
164
+ def check_json_options(options)
165
+ raise_when_all_keys_missing(options, OPTIONS_HASH_MISSING_VALID_KEYS, OPTIONS_JSON_HASH_ENUM)
166
+ if options.keys.include?(:schema)
167
+ test_options = {custom_errors: {not_a_string: SCHEMA_NOT_A_STRING, malformed_json: SCHEMA_NOT_JSON}}
168
+ unless Vert.validate_json?(options[:schema], test_options)
169
+ test = Vert.validate_json(options[:schema], test_options)
170
+ raise InputError, test if test.include?(SCHEMA_NOT_JSON)
171
+ raise InputError, test if test.include?(SCHEMA_NOT_A_STRING)
172
+ end
173
+ end
174
+ end
175
+
176
+ def test_validations_json(json, options)
177
+ test_for_default_json_errors(json, options)
178
+ if options_schema_present?(options)
179
+ validate_with_avro(json, options)
180
+ end
181
+ rescue ValidationError => exception
182
+ build_error_output(exception)
183
+ else
184
+ nil
185
+ end
186
+
187
+ def test_for_default_hash_errors(hash, options)
188
+ raise_when_not_hash(hash, options)
189
+ raise_when_empty(hash, options)
190
+ end
191
+
192
+ def raise_when_not_hash(hash, options)
193
+ raise_custom_error(hash, options, NOT_A_HASH_ERROR_KEY) {|hash| !hash.is_a?(Hash)}
194
+ end
195
+
196
+ def raise_custom_error(data, options, error_key)
197
+ test = yield data
198
+ message = options_errors_present?(options, error_key) ? get_options_custom_error(options, error_key) : ERROR_KEY_ENUM[error_key]
199
+ raise ValidationError, message if test
200
+ end
201
+
202
+ def options_errors_present?(options, error_key)
203
+ if options.nil?
204
+ false
205
+ elsif options.include?(:custom_errors)
206
+ options[:custom_errors].include?(error_key) ? true : false
207
+ else
208
+ false
209
+ end
210
+ end
211
+
212
+ def get_options_custom_error(options, error_key)
213
+ options[:custom_errors][error_key]
214
+ end
215
+
216
+ def raise_when_empty(hash, options)
217
+ raise_custom_error(hash, options, EMPTY_ERROR_KEY) {|hash| hash.empty?}
218
+ end
219
+
220
+ def options_key_types_present?(options, key_type)
221
+ if options.nil?
222
+ false
223
+ elsif options.include?(:keys)
224
+ options[:keys].include?(key_type) ? true : false
225
+ else
226
+ false
227
+ end
228
+ end
229
+
230
+ def test_for_array_key_errors(hash, options)
231
+ test_for_collection_key_errors(hash, options, :array_keys)
232
+ end
233
+
234
+ def test_for_hash_key_errors(hash, options)
235
+ test_for_collection_key_errors(hash, options, :hash_keys)
236
+ end
237
+
238
+ def test_for_collection_key_errors(hash, options, key_type)
239
+ raise_when_keys_absent(hash, options, key_type)
240
+ raise_when_keys_do_not_match_type(hash, options, key_type)
241
+ raise_when_collection_keys_empty(hash, options, key_type)
242
+ end
243
+
244
+ def raise_when_keys_absent(hash, options, key_type)
245
+ missing_keys = get_missing_keys(hash, options, key_type)
246
+ raise_error(nil, options, build_missing_key_error(options, missing_keys)) {!missing_keys.empty?}
247
+ end
248
+
249
+ def get_missing_keys(hash, options, key_type)
250
+ get_options_keys_array(options, key_type) - hash.keys
251
+ end
252
+
253
+ def raise_error(data, options, message)
254
+ test = yield data
255
+ raise ValidationError, message if test
256
+ end
257
+
258
+ def build_missing_key_error(options, missing_keys_array)
259
+ "#{build_error_message(options, ABSENT_KEY_ERROR_KEY)} Missing keys:- #{missing_keys_array*", "}"
260
+ end
261
+
262
+ def build_error_message(options, error_key)
263
+ options_errors_present?(options, error_key) ? get_options_custom_error(options, error_key) : ERROR_KEY_ENUM[error_key]
264
+ end
265
+
266
+ def test_criteria_met?(key_array)
267
+ key_array.empty?
268
+ end
269
+
270
+ def raise_when_keys_do_not_match_type(hash, options, key_type)
271
+ non_matched_type_keys = get_non_matched_type_keys(hash, options, key_type)
272
+ raise_error(nil, options, build_non_matched_type_error_message(options, non_matched_type_keys, key_type)) {!test_criteria_met?(non_matched_type_keys)}
273
+ end
274
+
275
+ def get_non_matched_type_keys(hash, options, key_type)
276
+ missing_keys = get_missing_keys(hash, options, key_type)
277
+ non_matched_type_keys = (get_options_keys_array(options, key_type) - missing_keys).find_all do
278
+ |key| hash[key].is_a?(TYPE_ENUM[key_type]) == false
279
+ end
280
+ end
281
+
282
+ def get_options_keys_array(options, key_type)
283
+ options[:keys][key_type]
284
+ end
285
+
286
+ def build_non_matched_type_error_message(options, non_matched_type_keys_array, key_type)
287
+ case key_type
288
+ when :array_keys
289
+ "#{build_error_message(options, ARRAY_TYPE_ERROR_KEY)} Not Array type keys:- #{non_matched_type_keys_array*","}"
290
+ when :hash_keys
291
+ "#{build_error_message(options, HASH_TYPE_ERROR_KEY)} Not Hash type keys:- #{non_matched_type_keys_array*","}"
292
+ end
293
+ end
294
+
295
+ def raise_when_collection_keys_empty(hash, options, key_type)
296
+ if get_non_matched_type_keys(hash, options, key_type).empty?
297
+ empty_collection_keys = get_empty_collection_keys(hash, options, key_type)
298
+ raise_error(nil, options, build_empty_collection_error_message(options, empty_collection_keys, key_type)) {!test_criteria_met?(empty_collection_keys)}
299
+ end
300
+ end
301
+
302
+ def get_empty_collection_keys(hash, options, key_type)
303
+ get_options_keys_array(options, key_type).find_all do |item|
304
+ hash[item].empty?
305
+ end
306
+ end
307
+
308
+ def build_empty_collection_error_message(options, empty_collection_keys_array, key_type)
309
+ case key_type
310
+ when :array_keys
311
+ "#{build_error_message(options, ARRAY_EMPTY_ERROR_KEY)} Empty array keys:- #{empty_collection_keys_array*","}"
312
+ when :hash_keys
313
+ "#{build_error_message(options, HASH_EMPTY_ERROR_KEY)} Empty hash keys:- #{empty_collection_keys_array*","}"
314
+ end
315
+ end
316
+
317
+ def build_error_output(custom_exception)
318
+ custom_exception.message
319
+ end
320
+
321
+ def test_for_default_json_errors(json, options)
322
+ raise_when_not_string(json, options)
323
+ hash = try_parse_json(json, options)
324
+ end
325
+
326
+ def options_schema_present?(options)
327
+ if options.nil?
328
+ false
329
+ elsif options.include?(:schema)
330
+ true
331
+ else
332
+ false
333
+ end
334
+ end
335
+
336
+ def get_options_schema(options)
337
+ options[:schema]
338
+ end
339
+
340
+ def raise_when_not_string(json, options)
341
+ raise_custom_error(json, options, NOT_A_STRING_ERROR_KEY){|json| !json.is_a?(String)}
342
+ end
343
+
344
+ def try_parse_json(json, options)
345
+ hash = Oj.load(json)
346
+ raise_custom_error(hash, options, EMPTY_JSON_ERROR_KEY){|hash| hash.nil?}
347
+ raise_custom_error(hash, options, EMPTY_JSON_OBJECT_ERROR_KEY){|hash| hash.empty?}
348
+ rescue Oj::ParseError => exception
349
+ detail = "#{exception.message.gsub( /\[.+\]/, "").rstrip}."
350
+ raise ValidationError, "#{build_error_message(options, MALFORMED_JSON_ERROR_KEY)}. #{detail}"
351
+ else
352
+ hash
353
+ end
354
+
355
+ def validate_with_avro(json, options)
356
+ schema_object = parse_avro_schema(options)
357
+ message = build_error_message(options, INVALID_AVRO_DATUM_ERROR_KEY)
358
+ raise_error(json, options, message){|json| !Avro::Schema.validate(schema_object, Oj.load(json))}
359
+ end
360
+
361
+ def parse_avro_schema(options)
362
+ schema = Avro::Schema.parse(get_options_schema(options))
363
+ rescue Avro::SchemaParseError => exception
364
+ raise ValidationError, "#{build_error_message(options, INVALID_AVRO_SCHEMA_ERROR_KEY)}. #{exception.to_s}"
365
+ else
366
+ schema
367
+ end
368
+
369
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vert
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Eskimo Bear
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: avro
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.7.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.7.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: oj
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.10.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.10.2
41
+ description: Vert is a library for verifying and validating data.
42
+ email: dev@eskimobear.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - lib/vert.rb
48
+ homepage: https://github.com/EskimoBear/Vert/
49
+ licenses:
50
+ - MIT
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 2.2.2
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: Keep your data clean
72
+ test_files: []