kharon 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +5 -13
  2. data/{.rspec → .rspec.old} +0 -0
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +12 -4
  5. data/dist/kharon-1.1.0.gem +0 -0
  6. data/doc/Kharon.html +384 -6
  7. data/doc/Kharon/Errors.html +1 -1
  8. data/doc/Kharon/Errors/Validation.html +1 -1
  9. data/doc/Kharon/Handlers.html +1 -1
  10. data/doc/Kharon/Handlers/Exceptions.html +1 -1
  11. data/doc/Kharon/Handlers/Messages.html +1 -1
  12. data/doc/Kharon/Processor.html +425 -0
  13. data/doc/Kharon/Processors.html +144 -0
  14. data/doc/Kharon/Processors/ArrayProcessor.html +429 -0
  15. data/doc/Kharon/Processors/BooleanProcessor.html +306 -0
  16. data/doc/Kharon/Processors/BoxProcessor.html +440 -0
  17. data/doc/Kharon/Processors/DateProcessor.html +306 -0
  18. data/doc/Kharon/Processors/DatetimeProcessor.html +306 -0
  19. data/doc/Kharon/Processors/EmailProcessor.html +307 -0
  20. data/doc/Kharon/Processors/HashProcessor.html +431 -0
  21. data/doc/Kharon/Processors/IntegerProcessor.html +441 -0
  22. data/doc/Kharon/Processors/NumericProcessor.html +463 -0
  23. data/doc/Kharon/Processors/SSIDProcessor.html +307 -0
  24. data/doc/Kharon/Processors/TextProcessor.html +430 -0
  25. data/doc/Kharon/Validate.html +17 -5
  26. data/doc/Kharon/Validator.html +198 -1194
  27. data/doc/_index.html +159 -1
  28. data/doc/class_list.html +1 -1
  29. data/doc/file.README.html +15 -4
  30. data/doc/index.html +15 -4
  31. data/doc/method_list.html +135 -39
  32. data/doc/top-level-namespace.html +1 -1
  33. data/kharon.gemspec +4 -2
  34. data/lib/kharon.rb +32 -2
  35. data/lib/kharon/processor.rb +162 -0
  36. data/lib/kharon/processors.rb +6 -0
  37. data/lib/kharon/processors/array_processor.rb +30 -0
  38. data/lib/kharon/processors/boolean_processor.rb +31 -0
  39. data/lib/kharon/processors/box_processor.rb +63 -0
  40. data/lib/kharon/processors/date_processor.rb +21 -0
  41. data/lib/kharon/processors/datetime_processor.rb +21 -0
  42. data/lib/kharon/processors/email_processor.rb +21 -0
  43. data/lib/kharon/processors/hash_processor.rb +31 -0
  44. data/lib/kharon/processors/integer_processor.rb +55 -0
  45. data/lib/kharon/processors/numeric_processor.rb +66 -0
  46. data/lib/kharon/processors/ssid_processor.rb +21 -0
  47. data/lib/kharon/processors/text_processor.rb +30 -0
  48. data/lib/kharon/validate.rb +1 -1
  49. data/lib/kharon/validator.rb +26 -390
  50. data/lib/kharon/version.rb +1 -1
  51. data/spec/results.html +5277 -321
  52. metadata +69 -28
@@ -103,7 +103,7 @@
103
103
  </div>
104
104
 
105
105
  <div id="footer">
106
- Generated on Tue May 17 11:54:24 2016 by
106
+ Generated on Thu Jun 2 16:20:54 2016 by
107
107
  <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
108
108
  0.8.7.6 (ruby-1.9.3).
109
109
  </div>
@@ -2,6 +2,7 @@
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'kharon/version'
5
+ require 'date'
5
6
 
6
7
  Gem::Specification.new do |specification|
7
8
  specification.name = "kharon"
@@ -13,12 +14,12 @@ Gem::Specification.new do |specification|
13
14
  specification.email = "courtois.vincent@outlook.com"
14
15
  specification.files = `git ls-files`.split($/)
15
16
  specification.homepage = "https://rubygems.org/gems/kharon"
16
- specification.license = "Apache License 2"
17
+ specification.license = "Apache-2.0"
17
18
  specification.test_files = ["spec/spec_helper.rb", "spec/lib/kharon/validator_spec.rb"]
18
19
 
19
20
  specification.required_ruby_version = ">= 1.9.3"
20
21
 
21
- specification.add_runtime_dependency "bson", "~> 2.2", ">= 2.2.2"
22
+ specification.add_runtime_dependency "bson", ">= 4.2.1", "< 5.0.0"
22
23
 
23
24
  specification.add_development_dependency "yard", "~> 0.8"
24
25
  specification.add_development_dependency "redcarpet", "3.3.1"
@@ -26,4 +27,5 @@ Gem::Specification.new do |specification|
26
27
  specification.add_development_dependency "rake", "~> 10.0"
27
28
  specification.add_development_dependency "rack-test", "~> 0.6.2"
28
29
  specification.add_development_dependency "rspec", "~> 3.0", ">= 3.0.0"
30
+ specification.add_development_dependency "pry"
29
31
  end
@@ -2,10 +2,10 @@
2
2
  # @author Vincent Courtois <courtois.vincent@outlook.com>
3
3
  module Kharon
4
4
 
5
- [:Validator, :Version, :Errors, :Handlers, :Helpers, :Validate].each { |classname| autoload(classname, "kharon/#{classname.downcase}") }
6
-
7
5
  @@use_exceptions = true
8
6
 
7
+ @@processors = {}
8
+
9
9
  # Configuration method used to tell the module if it uses exceptions or stores error messages.
10
10
  # @param [Boolean] use TRUE if you want to use exceptions, FALSE else.
11
11
  def self.use_exceptions(use = true)
@@ -17,4 +17,34 @@ module Kharon
17
17
  def self.errors_handler
18
18
  @@use_exceptions ? Kharon::Handlers::Exceptions.instance : Kharon::Handlers::Messages.new
19
19
  end
20
+
21
+ # Adds a processor class to the list of processors.
22
+ # @param [Symbol] name the name of the stored processor to retrieve it after. It will be the method you call to process a data with that processor.
23
+ # @param [Class] classname the class object for the processor to be instanciated later.
24
+ def self.add_processor(name, classname)
25
+ @@processors[name] = classname if classname.ancestors.include? Kharon::Processor
26
+ end
27
+
28
+ # Removes a processor from the list of available processors.
29
+ # @param [Symbol] name the name (key) of the processor to delete.
30
+ def self.remove_processor(name)
31
+ @@processors.delete(name) if self.has_processor?(name)
32
+ end
33
+
34
+ # Getter for the list of processors.
35
+ # @return [Hash] the list of processors currently available.
36
+ def self.processors
37
+ @@processors
38
+ end
39
+
40
+ # Checks if a processor currently exists in the system.
41
+ # @param [String] name the name of the processor to check the existence.
42
+ # @return [Boolean] TRUE if the processor exists, FALSE if not.
43
+ def self.has_processor?(name)
44
+ @@processors.keys.include?(name)
45
+ end
46
+
47
+ [:Processor, :Validator, :Version, :Errors, :Handlers, :Helpers, :Validate].each { |classname| autoload(classname, "kharon/#{classname.downcase}") }
48
+
49
+ Dir[File.join(File.dirname(__FILE__), "kharon/processors/*.rb")].each { |filename| require filename}
20
50
  end
@@ -0,0 +1,162 @@
1
+ module Kharon
2
+ # A basic processor used to be subclassed by all different processors.
3
+ # It provides basic informations to process a hash key validation.
4
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
5
+ class Processor
6
+
7
+ Kharon.add_processor(:any, Kharon::Processor)
8
+
9
+ attr_accessor :validator
10
+
11
+ def initialize(validator)
12
+ @validator = validator
13
+ end
14
+
15
+ # Default processing method, simply storing the validated key in the filtered hash.
16
+ # @param [Object] key the key associated with the value currently filteres in the filtered datas.
17
+ # @param [Hash] options the options applied to the initial value.
18
+ def process(key, options = {})
19
+ store(key, ->(item){item}, options)
20
+ end
21
+
22
+ protected
23
+
24
+ # This method is executed before any call to a public method.
25
+ # @param [Object] key the key associated with the value currently filteres in the filtered datas.
26
+ # @param [Hash] options the options applied to the initial value.
27
+ def before_all(key, options)
28
+ required(key) if (options.has_key?(:required) and options[:required] == true)
29
+ if options.has_key?(:dependencies)
30
+ dependencies(key, options[:dependencies])
31
+ elsif options.has_key?(:dependency)
32
+ dependency(key, options[:dependency])
33
+ end
34
+ end
35
+
36
+ # Tries to store the associated key in the filtered key, transforming it with the given process.
37
+ # @param [Object] key the key associated with the value to store in the filtered datas.
38
+ # @param [Proc] process a process (lambda) to execute on the initial value. Must contain strictly one argument.
39
+ # @param [Hash] options the options applied to the initial value.
40
+ def store(key, process, options = {})
41
+ unless (options.has_key?(:extract) and options[:extract] == false)
42
+ if validator.datas.has_key?(key)
43
+ value = ((options.has_key?(:cast) and options[:cast] == false) ? validator.datas[key] : process.call(validator.datas[key]))
44
+ if(options.has_key?(:in))
45
+ in_array?(key, options[:in])
46
+ elsif(options.has_key?(:equals))
47
+ equals_to?(key, options[:equals])
48
+ elsif(options.has_key?(:equals_key))
49
+ equals_key?(key, options[:equals_key])
50
+ end
51
+ options.has_key?(:rename) ? (validator.filtered[options[:rename]] = value) : (validator.filtered[key] = value)
52
+ end
53
+ end
54
+ end
55
+
56
+ # Raises a type error with a generic message.
57
+ # @param [Object] key the key associated from the value triggering the error.
58
+ # @param [Class] type the expected type, not respected by the initial value.
59
+ # @raise [ArgumentError] the chosen type error.
60
+ def raise_type_error(key, type)
61
+ raise_error(type: "type", key: key, supposed: type, found: key.class)
62
+ end
63
+
64
+ # Raises an error giving a message to display.
65
+ # @param [String] message the the message to display with the exception.
66
+ # @raise ArgumentError an error to stop the execution when this method is invoked.
67
+ def raise_error(message)
68
+ validator.handler.report_error(message)
69
+ end
70
+
71
+ # Accessor for the errors, use only if the handler is a Kharon::Handlers::Messages.
72
+ # @return [Array] the errors encountered during validation or an empty array if the handler was a Kharon::Handlers::Exceptions.
73
+ def errors
74
+ validator.handler.respond_to?(:errors) ? validator.handler.errors : []
75
+ end
76
+
77
+ # Checks if a required key is present in provided datas.
78
+ # @param [Object] key the key of which check the presence.
79
+ # @raise [ArgumentError] if the key is not present.
80
+ def required(key)
81
+ raise_error(type: "required", key: key) unless validator.datas.has_key?(key)
82
+ end
83
+
84
+ # Syntaxic sugar used to chack several dependencies at once.
85
+ # @param [Object] key the key needing another key to properly work.
86
+ # @param [Object] dependencies the keys needed by another key for it to properly work.
87
+ # @raise [ArgumentError] if the required dependencies are not present.
88
+ # @see self#check_dependency the associated singular method.
89
+ def dependencies(key, dependencies)
90
+ dependencies.each { |dependency| dependency(key, dependency) }
91
+ end
92
+
93
+ # Checks if a dependency is respected. A dependency is a key openly needed by another key.
94
+ # @param [Object] key the key needing another key to properly work.
95
+ # @param [Object] dependency the key needed by another key for it to properly work.
96
+ # @raise [ArgumentError] if the required dependency is not present.
97
+ def dependency(key, dependency)
98
+ raise_error(type: "dependency", key: "key", needed: dependency) unless validator.datas.has_key?(dependency)
99
+ end
100
+
101
+ # Check if the value associated with the given key is typed with the given type, or with a type inheriting from it.
102
+ # @param [Object] key the key of the value to check the type from.
103
+ # @param [Class] type the type with which check the initial value.
104
+ # @return [Boolean] true if the initial value is from the right type, false if not.
105
+ def is_typed?(key, type)
106
+ return (!validator.datas.has_key?(key) or validator.datas[key].kind_of?(type))
107
+ end
108
+
109
+ # Checks if the value associated with the given key is included in the given array of values.
110
+ # @param [Object] key the key associated with the value to check.
111
+ # @param [Array] values the values in which the initial value should be contained.
112
+ # @raise [ArgumentError] if the initial value is not included in the given possible values.
113
+ def in_array?(key, values)
114
+ raise_error(type: "array.in", key: key, supposed: values, value: validator.datas[key]) unless (values.empty? or values.include?(validator.datas[key]))
115
+ end
116
+
117
+ # Checks if the value associated with the given key is equal to the given value.
118
+ # @param [Object] key the key associated with the value to check.
119
+ # @param [Object] value the values with which the initial value should be compared.
120
+ # @raise [ArgumentError] if the initial value is not equal to the given value.
121
+ def equals_to?(key, value)
122
+ raise_error(type: "equals", key: key, supposed: value, found: validator.datas[key]) unless validator.datas[key] == value
123
+ end
124
+
125
+ # Checks if the value associated with the given key is equal to the given key.
126
+ # @param [Object] key the key associated with the value to check.
127
+ # @param [Object] value the key to compare the currently validated key with.
128
+ # @raise [ArgumentError] if the initial value is not equal to the given value.
129
+ def equals_key?(key, value)
130
+ raise_error(type: "equals", key: key, supposed: validator.datas[value], found: validator.datas[key]) unless validator.datas[key] == validator.datas[value]
131
+ end
132
+
133
+ # Check if the value associated with the given key matches the given regular expression.
134
+ # @param [Object] key the key of the value to compare with the given regexp.
135
+ # @param [Regexp] regex the regex with which match the initial value.
136
+ # @return [Boolean] true if the initial value matches the regex, false if not.
137
+ def match?(key, regex)
138
+ return (!validator.datas.has_key?(key) or validator.datas[key].to_s.match(regex))
139
+ end
140
+
141
+ def match_regex?(key, value, regex)
142
+ regex = Regexp.new(regex) if regex.kind_of?(String)
143
+ raise_error(type: "regex", regex: regex, value: value, key: key) unless regex.match(value)
144
+ end
145
+
146
+ # Checks if the value associated with the given key has the given required values.
147
+ # @param [Object] key the key associated with the value to check.
148
+ # @param [Array] required_values the values that the initial Enumerable typed value should contain.
149
+ # @raise [ArgumentError] if the initial value has not each and every one of the given values.
150
+ def contains?(key, values, required_values)
151
+ raise_error(type: "contains.values", required: required_values, key: key) if (values & required_values) != required_values
152
+ end
153
+
154
+ # Checks if the value associated with the given key has the given required keys.
155
+ # @param [Object] key the key associated with the value to check.
156
+ # @param [Array] required_keys the keys that the initial Hash typed value should contain.
157
+ # @raise [ArgumentError] if the initial value has not each and every one of the given keys.
158
+ def has_keys?(key, required_keys)
159
+ raise_error(type: "contains.keys", required: required_keys, key: key) if (validator.datas[key].keys & required_keys) != required_keys
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,6 @@
1
+ module Kharon
2
+ # Module containing all the processors used to process and validate datas.
3
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
4
+ module Processors
5
+ end
6
+ end
@@ -0,0 +1,30 @@
1
+ module Kharon
2
+ module Processors
3
+
4
+ # Processor to validate arrays. It has the :contains option plus the default ones.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ class ArrayProcessor < Kharon::Processor
7
+
8
+ Kharon.add_processor(:array, Kharon::Processors::ArrayProcessor)
9
+
10
+ # Checks if the given key is a datetime or not.
11
+ # @param [Object] key the key about which verify the type.
12
+ # @param [Hash] options a hash of options passed to this method (see documentation to know which options pass).
13
+ # @example Validates a key so it has to be a datetime, and depends on two other keys.
14
+ # @validator.datetime(:a_datetime, dependencies: [:another_key, :a_third_key])
15
+ def process(key, options = {})
16
+ before_all(key, options)
17
+ is_typed?(key, Array) ? store(key, ->(item){item.to_a}, options) : raise_type_error(key, "Array")
18
+ end
19
+
20
+ # Stores an array after verifying that it contains the values given in the contains? option.
21
+ # @param [Object] key the key associated with the value to store in the filtered datas.
22
+ # @param [Proc] process a process (lambda) to execute on the initial value. Must contain strictly one argument.
23
+ # @param [Hash] options the options applied to the initial value.
24
+ def store(key, process, options)
25
+ contains?(key, validator.datas[key], options[:contains]) if(options.has_key?(:contains))
26
+ super(key, process, options)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ module Kharon
2
+ module Processors
3
+
4
+ # Processor to validate booleans. It only has the default options.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ class BooleanProcessor < Kharon::Processor
7
+
8
+ Kharon.add_processor(:boolean, Kharon::Processors::BooleanProcessor)
9
+
10
+ # Checks if the given key is a boolean or not.
11
+ # @param [Object] key the key about which verify the type.
12
+ # @param [Hash] options a hash of options passed to this method (see documentation to know which options pass).
13
+ # @example Validates a key so it has to be a boolean.
14
+ # @validator.boolean(:a_boolean)
15
+ def process(key, options = {})
16
+ before_all(key, options)
17
+ match?(key, /(true)|(false)/) ? store(key, ->(item){to_boolean(item)}, options) : raise_type_error(key, "Numeric")
18
+ end
19
+
20
+ private
21
+
22
+ # Transforms a given value in a boolean.
23
+ # @param [Object] value the value to transform into a boolean.
24
+ # @return [Boolean] true if the value was true, 1 or yes, false if not.
25
+ def to_boolean(value)
26
+ ["true", "1", "yes"].include?(value.to_s) ? true : false
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,63 @@
1
+ module Kharon
2
+ module Processors
3
+
4
+ # Processor to validate boxes. It has the :at_most and :at_least with the default ones.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ class BoxProcessor < Kharon::Processor
7
+
8
+ Kharon.add_processor(:box, Kharon::Processors::BoxProcessor)
9
+
10
+ # Checks if the given key is a box (geofences) or not. A box is composed of four numbers (positive or negative, decimal or not) separed by commas.
11
+ # @param [Object] key the key about which verify the type.
12
+ # @param [Hash] options a hash of options passed to this method (see documentation to know which options pass).
13
+ # @example Validates a key so it has to be a box.
14
+ # @validator.box(:a_box)
15
+ def process(key, options = {})
16
+ before_all(key, options)
17
+ match?(key, /^(?:[+-]?\d{1,3}(?:\.\d{1,7})?,?){4}$/) ? store(key, nil, options) : raise_type_error(key, "Box")
18
+ end
19
+
20
+ # Tries to store the associated key in the filtered key, transforming it with the given process.
21
+ # @param [Object] key the key associated with the value to store in the filtered datas.
22
+ # @param [Proc] process a process (lambda) to execute on the initial value. Must contain strictly one argument.
23
+ # @param [Hash] options the options applied to the initial value.
24
+ def store(key, process, options)
25
+ if(options.has_key?(:at_least))
26
+ box_contains?(key, validator.datas[key], options[:at_least])
27
+ end
28
+ if(options.has_key?(:at_most))
29
+ box_contains?(key, options[:at_most], validator.datas[key])
30
+ end
31
+ super(key, ->(item){parse_box(key, validator.datas[key])}, options)
32
+ end
33
+
34
+ private
35
+
36
+ # Verify if a box contains another box.
37
+ # @param [Object] container any object that can be treated as a box, container of the other box
38
+ # @param [Object] contained any object that can be treated as a box, contained in the other box
39
+ # @return [Boolean] TRUE if the box is contained in the other one, FALSE if not.
40
+ def box_contains?(key, container, contained)
41
+ container = parse_box(key, container)
42
+ contained = parse_box(key, contained)
43
+ result = ((container[0][0] <= contained[0][0]) and (container[0][1] <= container[0][1]) and (container[1][0] >= container[1][0]) and (container[1][1] >= container[1][1]))
44
+ raise_error(type: "box.containment", contained: contained, container: container, key: key) unless result
45
+ end
46
+
47
+ # Parses a box given as a string of four numbers separated by commas.
48
+ # @param [String] box the string representing the box.
49
+ # @return [Array] an array of size 2, containing two arrays of size 2 (the first being the coordinates of the top-left corner, the second the ones of the bottom-right corner)
50
+ def parse_box(key, box)
51
+ if box.kind_of?(String)
52
+ begin
53
+ raw_box = box.split(",").map(&:to_f)
54
+ box = [[raw_box[0], raw_box[1]], [raw_box[2], raw_box[3]]]
55
+ rescue
56
+ raise_error(type: "box.format", key: "key", value: box)
57
+ end
58
+ end
59
+ return box
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ module Kharon
2
+ module Processors
3
+
4
+ # Processor to validate dates. It only has the default options.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ class DateProcessor < Kharon::Processor
7
+
8
+ Kharon.add_processor(:date, Kharon::Processors::DateProcessor)
9
+
10
+ # Checks if the given key is a datetime or not.
11
+ # @param [Object] key the key about which verify the type.
12
+ # @param [Hash] options a hash of options passed to this method (see documentation to know which options pass).
13
+ # @example Validates a key so it has to be a datetime, and depends on two other keys.
14
+ # @validator.datetime(:a_datetime, dependencies: [:another_key, :a_third_key])
15
+ def process(key, options = {})
16
+ before_all(key, options)
17
+ begin; store(key, ->(item){Date.parse(item.to_s)}, options); rescue; raise_type_error(key, "Date"); end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Kharon
2
+ module Processors
3
+
4
+ # Processor to validate datetimes. It only has the default options.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ class DatetimeProcessor < Kharon::Processor
7
+
8
+ Kharon.add_processor(:datetime, Kharon::Processors::DatetimeProcessor)
9
+
10
+ # Checks if the given key is a datetime or not.
11
+ # @param [Object] key the key about which verify the type.
12
+ # @param [Hash] options a hash of options passed to this method (see documentation to know which options pass).
13
+ # @example Validates a key so it has to be a datetime, and depends on two other keys.
14
+ # @validator.datetime(:a_datetime, dependencies: [:another_key, :a_third_key])
15
+ def process(key, options = {})
16
+ before_all(key, options)
17
+ begin; store(key, ->(item){DateTime.parse(item.to_s)} , options); rescue; raise_type_error(key, "DateTime"); end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Kharon
2
+ module Processors
3
+
4
+ # Processor to validate emails. It only has the default options.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ class EmailProcessor < Kharon::Processor
7
+
8
+ Kharon.add_processor(:email, Kharon::Processors::EmailProcessor)
9
+
10
+ # Checks if the given key is a not-empty string or not.
11
+ # @param [Object] key the key about which verify the type.
12
+ # @param [Hash] options a hash of options passed to this method (see documentation to know which options pass).
13
+ # @example Validates a key so it has to be a string, and seems like and email address (not sure of the regular expression though).
14
+ # @validator.text(:an_email, regex: "[a-zA-Z]+@[a-zA-Z]+\.[a-zA-Z]{2-4}")
15
+ def process(key, options = {})
16
+ before_all(key, options)
17
+ match?(key, /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/) ? store(key, ->(item){item}, options) : raise_type_error(key, "Email")
18
+ end
19
+ end
20
+ end
21
+ end