Module: ClassyHash
- Defined in:
- lib/classy_hash.rb,
lib/classy_hash/generate.rb
Overview
Classy Hash extended validation generators Copyright ©2014 Deseret Book
Defined Under Namespace
Modules: Generate
Constant Summary collapse
- G =
Shortcut to ClassyHash::Generate
Generate
Class Method Summary collapse
-
.check_multi(key, value, constraints, parent_path = nil) ⇒ Object
Raises an error unless the given
valuematches one of the given multiple choiceconstraints. -
.check_one(key, value, constraint, parent_path = nil) ⇒ Object
Checks a single value against a single constraint.
- .join_path(parent_path, key) ⇒ Object
-
.multiconstraint_string(constraints, value) ⇒ Object
Generates a semi-compact String describing the given
constraints. -
.raise_error(parent_path, key, message) ⇒ Object
Raises an error indicating that the given
keyunder the givenparent_pathfails because the value “is not #<code>message</code>”. -
.validate(hash, schema, parent_path = nil) ⇒ Object
Validates a
hashagainst aschema. -
.validate_strict(hash, schema, verbose = false, parent_path = nil) ⇒ Object
As with #validate, but members not specified in the
schemaare forbidden.
Class Method Details
.check_multi(key, value, constraints, parent_path = nil) ⇒ Object
Raises an error unless the given value matches one of the given multiple choice constraints.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/classy_hash.rb', line 50 def self.check_multi(key, value, constraints, parent_path=nil) if constraints.length == 0 self.raise_error(parent_path, key, "a valid multiple choice constraint (array must not be empty)") end # Optimize the common case of a direct class match return if constraints.include?(value.class) error = nil constraints.each do |c| next if c == :optional begin self.check_one(key, value, c, parent_path) return rescue => e # Throw schema and array errors immediately if (c.is_a?(Hash) && value.is_a?(Hash)) || (c.is_a?(Array) && value.is_a?(Array) && c.length == 1 && c.first.is_a?(Array)) raise e end end end self.raise_error(parent_path, key, "one of #{multiconstraint_string(constraints, value)}") end |
.check_one(key, value, constraint, parent_path = nil) ⇒ Object
Checks a single value against a single constraint.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/classy_hash.rb', line 96 def self.check_one(key, value, constraint, parent_path=nil) case constraint when Class # Constrain value to be a specific class if constraint == TrueClass || constraint == FalseClass unless value == true || value == false self.raise_error(parent_path, key, "true or false") end elsif !value.is_a?(constraint) self.raise_error(parent_path, key, "a/an #{constraint}") end when Hash # Recursively check nested Hashes self.raise_error(parent_path, key, "a Hash") unless value.is_a?(Hash) self.validate(value, constraint, self.join_path(parent_path, key)) when Array # Multiple choice or array validation if constraint.length == 1 && constraint.first.is_a?(Array) # Array validation self.raise_error(parent_path, key, "an Array") unless value.is_a?(Array) constraints = constraint.first value.each_with_index do |v, idx| self.check_multi(idx, v, constraints, self.join_path(parent_path, key)) end else # Multiple choice self.check_multi(key, value, constraint, parent_path) end when Regexp # Constrain value to be a String matching a Regexp unless value.is_a?(String) && value =~ constraint self.raise_error(parent_path, key, "a String matching #{constraint.inspect}") end when Proc # User-specified validator result = constraint.call(value) if result != true self.raise_error(parent_path, key, result.is_a?(String) ? result : "accepted by Proc") end when Range # Range (with type checking for common classes) if constraint.min.is_a?(Integer) && constraint.max.is_a?(Integer) self.raise_error(parent_path, key, "an Integer") unless value.is_a?(Integer) elsif constraint.min.is_a?(Numeric) self.raise_error(parent_path, key, "a Numeric") unless value.is_a?(Numeric) elsif constraint.min.is_a?(String) self.raise_error(parent_path, key, "a String") unless value.is_a?(String) end unless constraint.cover?(value) self.raise_error(parent_path, key, "in range #{constraint.inspect}") end when :optional # Optional key marker in multiple choice validators nil else # Unknown schema constraint self.raise_error(parent_path, key, "a valid schema constraint: #{constraint.inspect}") end nil end |
.join_path(parent_path, key) ⇒ Object
167 168 169 |
# File 'lib/classy_hash.rb', line 167 def self.join_path(parent_path, key) parent_path ? "#{parent_path}[#{key.inspect}]" : key.inspect end |
.multiconstraint_string(constraints, value) ⇒ Object
Generates a semi-compact String describing the given constraints.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/classy_hash.rb', line 77 def self.multiconstraint_string(constraints, value) constraints.map{|c| if c.is_a?(Hash) "{...schema...}" elsif c.is_a?(Array) "[#{self.multiconstraint_string(c, value)}]" elsif c.is_a?(Proc) c.call(value) || "a value accepted by #{c.inspect}" elsif c == :optional nil elsif c == TrueClass || c == FalseClass 'true or false' else c.inspect end }.compact.join(', ') end |
.raise_error(parent_path, key, message) ⇒ Object
Raises an error indicating that the given key under the given parent_path fails because the value “is not #<code>message</code>”.
173 174 175 176 |
# File 'lib/classy_hash.rb', line 173 def self.raise_error(parent_path, key, ) # TODO: Ability to validate all keys raise "#{self.join_path(parent_path, key)} is not #{message}" end |
.validate(hash, schema, parent_path = nil) ⇒ Object
Validates a hash against a schema. The parent_path parameter is used internally to generate error messages.
12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/classy_hash.rb', line 12 def self.validate(hash, schema, parent_path=nil) raise 'Must validate a Hash' unless hash.is_a?(Hash) # TODO: Allow validating other types by passing to #check_one? raise 'Schema must be a Hash' unless schema.is_a?(Hash) # TODO: Allow individual element validations? schema.each do |key, constraint| if hash.include?(key) self.check_one(key, hash[key], constraint, parent_path) elsif !(constraint.is_a?(Array) && constraint.include?(:optional)) self.raise_error(parent_path, key, "present") end end nil end |
.validate_strict(hash, schema, verbose = false, parent_path = nil) ⇒ Object
As with #validate, but members not specified in the schema are forbidden. Only the top-level schema is strictly validated. If verbose is true, the names of unexpected keys will be included in the error message.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/classy_hash.rb', line 30 def self.validate_strict(hash, schema, verbose=false, parent_path=nil) raise 'Must validate a Hash' unless hash.is_a?(Hash) # TODO: Allow validating other types by passing to #check_one? raise 'Schema must be a Hash' unless schema.is_a?(Hash) # TODO: Allow individual element validations? extra_keys = hash.keys - schema.keys unless extra_keys.empty? if verbose raise "Hash contains members (#{extra_keys.map(&:inspect).join(', ')}) not specified in schema" else raise 'Hash contains members not specified in schema' end end # TODO: Strict validation for nested schemas as well self.validate(hash, schema, parent_path) end |