Module: Itsi::Server::TypedHandlers::ParamParser
- Included in:
- HttpRequest
- Defined in:
- lib/itsi/server/typed_handlers/param_parser.rb
Defined Under Namespace
Classes: ValidationError
Constant Summary collapse
- CONVERSION_MAP =
Conversion map for primitive/base type conversions.
{ String => ->(v){ v.to_s }, Symbol => ->(v){ v.to_sym }, Integer => ->(v){ Integer(v) }, Float => ->(v){ Float(v) }, :Number => ->(v){ Float(v) }, TrueClass => ->(v){ case v when true, 'true', '1', 1 then true when false, 'false', '0', 0 then false else raise ValidationError.new("Cannot cast #{v.inspect} to Boolean") end }, FalseClass => ->(v){ case v when true, 'true', '1', 1 then true when false, 'false', '0', 0 then false else raise ValidationError.new("Cannot cast #{v.inspect} to Boolean") end }, :Boolean => ->(v){ case v when true, 'true', '1', 1 then true when false, 'false', '0', 0 then false else raise ValidationError.new("Cannot cast #{v.inspect} to Boolean") end }, Date => ->(v){ Date.parse(v.to_s) }, Time => ->(v){ Time.parse(v.to_s) }, DateTime => ->(v){ DateTime.parse(v.to_s) } }.compare_by_identity
Instance Method Summary collapse
-
#apply_schema!(params, schema, path = []) ⇒ Object
Applies the schema in place to the given params hash.
-
#cast_value!(container, key, expected_type, path) ⇒ Object
In-place casts the value at container according to expected_type.
-
#format_path(path) ⇒ Object
Helper that converts an array of path segments into a string.
-
#processed_schema(schema) ⇒ Object
Preprocess the schema into fixed keys (as symbols) and regex keys.
Instance Method Details
#apply_schema!(params, schema, path = []) ⇒ Object
Applies the schema in place to the given params hash. Fixed keys are converted to symbols, and regex-matched keys remain as strings. The current location in the params is tracked as an array of path segments.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/itsi/server/typed_handlers/param_parser.rb', line 145 def apply_schema!(params, schema, path = []) # Support top-level array schema: homogeneous arrays. if schema.is_a?(Array) # Only allow homogeneous array types unless schema.size == 1 raise ValidationError.new(["Schema Array must contain exactly one type. Got #{schema.size}"]) end expected_type = schema.first # Expect params to be an Array unless params.is_a?(Array) raise ValidationError.new(["Expected Array at #{format_path(path)}, got #{params.class}"]) end errors = [] params.each_with_index do |_, idx| err = cast_value!(params, idx, expected_type, path + [idx]) errors << err if err end raise ValidationError.new(errors) unless errors.empty? return params end # Ensure schema is a Hash unless schema.is_a?(Hash) raise ValidationError.new(["Unsupported schema type: #{schema.class} at #{format_path(path)}"]) end errors = [] processed = processed_schema(schema) fixed_schema = processed[0] regex_schema = processed[1] # Process fixed keys. fixed_schema.each do |fixed_key, (expected_type, required)| new_path = path + [fixed_key] if params.key?(fixed_key) # Symbol key present. elsif params.key?(fixed_key.to_s) params[fixed_key] = params.delete(fixed_key.to_s) else if required errors << "Missing required key: #{format_path(new_path)}" else params[fixed_key] = nil end next end err = cast_value!(params, fixed_key, expected_type, new_path) errors << err if err end # Process regex keys (only string keys not already handled as fixed keys). params.keys.each do |key| if key == :_required params.delete(key) next end next if fixed_schema.has_key?(key.to_sym) || fixed_schema.has_key?(key) unless regex_schema.find do |regex, (expected_type, _required)| if regex.match(key) new_path = path + [key] err = cast_value!(params, key, expected_type, new_path) errors << err if err true # only use the first matching regex end end params.delete(key) end end raise ValidationError.new(errors) unless errors.empty? params end |
#cast_value!(container, key, expected_type, path) ⇒ Object
85 86 87 88 89 90 91 92 93 94 95 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 |
# File 'lib/itsi/server/typed_handlers/param_parser.rb', line 85 def cast_value!(container, key, expected_type, path) if expected_type.is_a?(Array) # Only allow homogeneous array types. return "Only homogeneous array types are supported at #{format_path(path)}" if expected_type.size != 1 # Expect container[key] to be an Array; process each element in place. unless container[key].is_a?(Array) return "Expected an Array at #{format_path(path)}, got #{container[key].class}" end container[key].each_with_index do |_, idx| err = cast_value!(container[key], idx, expected_type.first, path + [idx]) return err if err end return nil elsif expected_type.is_a?(Hash) # Nested schema: expect container[key] to be a Hash; process it in place. unless container[key].is_a?(Hash) return "Expected a Hash at #{format_path(path)}, got #{container[key].class}" end begin apply_schema!(container[key], expected_type, path) return nil rescue ValidationError => ve return ve.errors.join('; ') end else converter = CONVERSION_MAP[expected_type] if converter begin container[key] = converter.call(container[key]) return nil rescue => e return "Invalid value for #{expected_type} at #{format_path(path)}: #{container[key].inspect} (#{e.})" end end # Fallbacks. if expected_type == Array unless container[key].is_a?(Array) return "Expected Array at #{format_path(path)}, got #{container[key].class}" end return nil elsif expected_type == Hash unless container[key].is_a?(Hash) return "Expected Hash at #{format_path(path)}, got #{container[key].class}" end return nil elsif expected_type == File && container[key].is_a?(Hash) && container[key][:tempfile].is_a?(Tempfile) return nil else return "Unsupported type: #{expected_type.inspect} at #{format_path(path)}" end end end |
#format_path(path) ⇒ Object
Helper that converts an array of path segments into a string. For example, [:user, “addresses”, 0, :street] becomes “user.addresses.street”.
70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/itsi/server/typed_handlers/param_parser.rb', line 70 def format_path(path) result = "".dup path.each do |seg| if seg.is_a?(Integer) result << "[#{seg}]" else result << (result.empty? ? seg.to_s : ".#{seg}") end end result end |
#processed_schema(schema) ⇒ Object
Preprocess the schema into fixed keys (as symbols) and regex keys. Memoizes the result based on the schema.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/itsi/server/typed_handlers/param_parser.rb', line 50 def processed_schema(schema) @@schema_cache ||= {} @@schema_cache[schema] ||= begin fixed = {} regex = [] required_params = schema[:_required] || [] schema.each do |k, schema_def| expected_type, required = schema_def, required_params.include?(k) if k.is_a?(Regexp) regex << [k, [expected_type, required]] else fixed[k.to_sym] = [expected_type, required] end end [fixed, regex] end end |