vert 0.2.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.
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: []