{"record":{"id":"d859198a00c82161","repo":"lostisland/faraday","slug":"expected-value-type-name-got-context-subkey","errorCode":null,"errorMessage":"expected #{value_type.name} (got #{context[subkey].class.name}) for param `#{subkey}'","messagePattern":"expected #(.+?) \\(got #(.+?)\\) for param `#(.+?)'","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"lib/faraday/encoders/nested_params_encoder.rb","lineNumber":134,"sourceCode":"        context = prepare_context(context, subkey, is_array, last_subkey)\n        add_to_context(is_array, context, value, subkey) if last_subkey\n      end\n    end\n\n    def prepare_context(context, subkey, is_array, last_subkey)\n      if !last_subkey || is_array\n        context = new_context(subkey, is_array, context)\n      end\n      if context.is_a?(Array) && !is_array\n        context = match_context(context, subkey)\n      end\n      context\n    end\n\n    def new_context(subkey, is_array, context)\n      value_type = is_array ? Array : Hash\n      if context[subkey] && !context[subkey].is_a?(value_type)\n        raise TypeError, \"expected #{value_type.name} \" \\\n                         \"(got #{context[subkey].class.name}) for param `#{subkey}'\"\n      end\n\n      context[subkey] ||= value_type.new\n    end\n\n    def match_context(context, subkey)\n      context << {} if !context.last.is_a?(Hash) || context.last.key?(subkey)\n      context.last\n    end\n\n    def add_to_context(is_array, context, value, subkey)\n      is_array ? context << value : context[subkey] = value\n    end\n\n    def validate_params_depth!(depth)\n      return unless @param_depth_limit && depth > @param_depth_limit\n","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/lostisland/faraday/blob/b25b1b26ccef34b1460b0267115be238ca758087/lib/faraday/encoders/nested_params_encoder.rb#L116-L152","documentation":"Raised while NestedParamsEncoder#decode parses an incoming query string into a Hash. When a later key nests through a subkey that already holds a scalar (or an Array where a Hash is required), new_context refuses to overwrite the mismatched type: it wants Hash for a[b]-style keys and Array for a[]-style keys, and anything else raises TypeError naming the offending subkey. In short, the query string assigns the same key as both a leaf value and a container.","triggerScenarios":"Decoding 'a=1&a[b]=2' (a is already the String '1', then a[b] needs a Hash); decoding 'a=1&a[]=2' (a is a String where an Array is required); reverse order 'a[b]=1&a[c][d]=2' is fine, but 'a[]=1&a[b]=2' mixes Array and Hash for the same key. Any code path that calls Faraday::Utils.parse_query or the nested decoder on request URLs or response data with hostile input.","commonSituations":"A server or middleware parsing untrusted URLs — security scanners routinely send str=abc&str[x]=y probes; API clients that flatten and re-append params, accidentally emitting both flat and nested forms of one key; log replay or webhook payloads whose query strings were assembled by string concatenation; mixing Faraday's nested encoding with a partner system's flat encoding for the same key.","solutions":["Treat the input as invalid at your trust boundary: rescue TypeError around the decode call and reject with 400 / skip the params, since the string is ambiguous by construction.","If you control the producer, stop emitting mixed forms for one key — never send both a=1 and a[b]=2 for the same prefix.","Decode untrusted query strings with Faraday::FlatParamsEncoder instead (params_encoder option or Faraday::Utils.parse_query with the flat decoder) — flat decoding never builds containers, so the conflict cannot occur.","Sanitize before parsing: reject query strings where a key appears both with and without bracket suffixes."],"exampleFix":"# before\nparams = Faraday::Utils.parse_query('a=1&a[b]=2')\n# => TypeError: expected Hash (got String) for param `a'\n\n# after\nbegin\n  params = Faraday::Utils.parse_query(raw_query)\nrescue TypeError\n  return [400, {}, ['invalid query string']]\nend\n# or decode untrusted input flat, which cannot conflict:\nflat = Faraday::FlatParamsEncoder.decode(raw_query) # {\"a\"=>\"1\", \"a[b]\"=>\"2\"}","handlingStrategy":"try-catch","validationCode":"keys  = raw_query.split('&').map { |kv| kv.split('=', 2).first.to_s }\nflat  = keys.reject { |k| k.include?('[') }\nprefs = keys.select { |k| k.include?('[') }.map { |k| k.split('[', 2).first }\nraise 'ambiguous query string' if (flat.uniq & prefs.uniq).any?\nparams = Faraday::NestedParamsEncoder.decode(raw_query)","typeGuard":"def safely_nestable?(raw_query)\n  keys  = raw_query.split('&').map { |kv| kv.split('=', 2).first.to_s }\n  flat  = keys.reject { |k| k.include?('[') }\n  prefs = keys.select { |k| k.include?('[') }.map { |k| k.split('[', 2).first }\n  (flat.uniq & prefs.uniq).empty?\nend","tryCatchPattern":"begin\n  params = Faraday::NestedParamsEncoder.decode(raw_query)\nrescue TypeError => e\n  raise unless e.message.start_with?('expected')\n  params = Faraday::FlatParamsEncoder.decode(raw_query) # degrade to flat keys like \"a[b]\"\nend","preventionTips":["Never decode untrusted query strings with the nested decoder without a rescue; treat TypeError as a 400-level input rejection.","Keep one param style per key when building query strings by hand — never emit a=1 and a[b]=2 together.","Use FlatParamsEncoder for decoding external input; reserve nested decoding for strings you encoded yourself."],"tags":["ruby","faraday","nested-params","query-string","typeerror","parsing","untrusted-input"],"backgroundTag":"malformed-query-string","analyzedSha":"b25b1b26ccef34b1460b0267115be238ca758087","analyzedAt":"2026-08-21T19:43:20.220Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}