mirror of
https://github.com/HoneyryderChuck/httpx.git
synced 2025-09-14 00:00:44 -04:00
added Response#form (supports only x-www-urlencoded for now)
This commit is contained in:
parent
a85828d0d5
commit
f2d3c1f09b
@ -59,7 +59,7 @@ module HTTPX
|
||||
@registry ||= {}
|
||||
return @registry if tag.nil?
|
||||
|
||||
handler = @registry.fetch(tag)
|
||||
handler = @registry[tag]
|
||||
raise(Error, "#{tag} is not registered in #{self}") unless handler
|
||||
|
||||
handler
|
||||
|
@ -45,7 +45,7 @@ module HTTPX
|
||||
end
|
||||
|
||||
def content_type
|
||||
ContentType.parse(@headers["content-type"])
|
||||
@content_type ||= ContentType.new(@headers["content-type"])
|
||||
end
|
||||
|
||||
def complete?
|
||||
@ -72,17 +72,23 @@ module HTTPX
|
||||
decode("json", options)
|
||||
end
|
||||
|
||||
def form
|
||||
decode("form")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def decode(format, options = nil)
|
||||
# TODO: check if content-type is a valid format, i.e. "application/json" for json parsinng
|
||||
# TODO: check if content-type is a valid format, i.e. "application/json" for json parsing
|
||||
transcoder = Transcoder.registry(format)
|
||||
|
||||
raise Error, "no decoder available for \"#{format}\"" unless transcoder.respond_to?(:decoder)
|
||||
raise Error, "no decoder available for \"#{format}\"" unless transcoder.respond_to?(:decode)
|
||||
|
||||
decoder = transcoder.decoder
|
||||
decoder = transcoder.decode(self)
|
||||
|
||||
decoder.call(@body, options)
|
||||
raise Error, "no decoder available for \"#{format}\"" unless decoder
|
||||
|
||||
decoder.call(self, options)
|
||||
rescue Registry::Error
|
||||
raise Error, "no decoder available for \"#{format}\""
|
||||
end
|
||||
@ -281,30 +287,22 @@ module HTTPX
|
||||
MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}.freeze
|
||||
CHARSET_RE = /;\s*charset=([^;]+)/i.freeze
|
||||
|
||||
attr_reader :mime_type, :charset
|
||||
|
||||
def initialize(mime_type, charset)
|
||||
@mime_type = mime_type
|
||||
@charset = charset
|
||||
def initialize(header_value)
|
||||
@header_value = header_value
|
||||
end
|
||||
|
||||
class << self
|
||||
# Parse string and return ContentType struct
|
||||
def parse(str)
|
||||
new(mime_type(str), charset(str))
|
||||
end
|
||||
def mime_type
|
||||
return @mime_type if defined?(@mime_type)
|
||||
|
||||
private
|
||||
m = @header_value.to_s[MIME_TYPE_RE, 1]
|
||||
m && @mime_type = m.strip.downcase
|
||||
end
|
||||
|
||||
def mime_type(str)
|
||||
m = str.to_s[MIME_TYPE_RE, 1]
|
||||
m && m.strip.downcase
|
||||
end
|
||||
def charset
|
||||
return @charset if defined?(@charset)
|
||||
|
||||
def charset(str)
|
||||
m = str.to_s[CHARSET_RE, 1]
|
||||
m && m.strip.delete('"')
|
||||
end
|
||||
m = @header_value.to_s[CHARSET_RE, 1]
|
||||
m && @charset = m.strip.delete('"')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -4,7 +4,9 @@ module HTTPX
|
||||
module Transcoder
|
||||
extend Registry
|
||||
|
||||
def self.normalize_keys(key, value, cond = nil, &block)
|
||||
module_function
|
||||
|
||||
def normalize_keys(key, value, cond = nil, &block)
|
||||
if (cond && cond.call(value))
|
||||
block.call(key.to_s, value)
|
||||
elsif value.respond_to?(:to_ary)
|
||||
@ -23,6 +25,63 @@ module HTTPX
|
||||
block.call(key.to_s, value)
|
||||
end
|
||||
end
|
||||
|
||||
# based on https://github.com/rack/rack/blob/d15dd728440710cfc35ed155d66a98dc2c07ae42/lib/rack/query_parser.rb#L82
|
||||
def normalize_query(params, name, v, depth)
|
||||
raise Error, "params depth surpasses what's supported" if depth <= 0
|
||||
|
||||
name =~ /\A[\[\]]*([^\[\]]+)\]*/
|
||||
k = Regexp.last_match(1) || ""
|
||||
after = Regexp.last_match ? Regexp.last_match.post_match : ""
|
||||
|
||||
if k.empty?
|
||||
return Array(v) if !v.empty? && name == "[]"
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
case after
|
||||
when ""
|
||||
params[k] = v
|
||||
when "["
|
||||
params[name] = v
|
||||
when "[]"
|
||||
params[k] ||= []
|
||||
raise Error, "expected Array (got #{params[k].class}) for param '#{k}'" unless params[k].is_a?(Array)
|
||||
|
||||
params[k] << v
|
||||
when /^\[\]\[([^\[\]]+)\]$/, /^\[\](.+)$/
|
||||
child_key = Regexp.last_match(1)
|
||||
params[k] ||= []
|
||||
raise Error, "expected Array (got #{params[k].class}) for param '#{k}'" unless params[k].is_a?(Array)
|
||||
|
||||
if params[k].last.is_a?(Hash) && !params_hash_has_key?(params[k].last, child_key)
|
||||
normalize_query(params[k].last, child_key, v, depth - 1)
|
||||
else
|
||||
params[k] << normalize_query({}, child_key, v, depth - 1)
|
||||
end
|
||||
else
|
||||
params[k] ||= {}
|
||||
raise Error, "expected Hash (got #{params[k].class}) for param '#{k}'" unless params[k].is_a?(Hash)
|
||||
|
||||
params[k] = normalize_query(params[k], after, v, depth - 1)
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
|
||||
def params_hash_has_key?(hash, key)
|
||||
return false if /\[\]/.match?(key)
|
||||
|
||||
key.split(/[\[\]]+/).inject(hash) do |h, part|
|
||||
next h if part == ""
|
||||
return false unless h.is_a?(Hash) && h.key?(part)
|
||||
|
||||
h[part]
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -7,6 +7,8 @@ module HTTPX::Transcoder
|
||||
module Form
|
||||
module_function
|
||||
|
||||
PARAM_DEPTH_LIMIT = 32
|
||||
|
||||
class Encoder
|
||||
extend Forwardable
|
||||
|
||||
@ -31,9 +33,23 @@ module HTTPX::Transcoder
|
||||
end
|
||||
end
|
||||
|
||||
module Decoder
|
||||
module_function
|
||||
|
||||
def call(response, _)
|
||||
URI.decode_www_form(response.to_s).each_with_object({}) do |(field, value), params|
|
||||
HTTPX::Transcoder.normalize_query(params, field, value, PARAM_DEPTH_LIMIT)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def encode(form)
|
||||
Encoder.new(form)
|
||||
end
|
||||
|
||||
def decode(_response)
|
||||
Decoder
|
||||
end
|
||||
end
|
||||
register "form", Form
|
||||
end
|
||||
|
@ -28,7 +28,7 @@ module HTTPX::Transcoder
|
||||
Encoder.new(json)
|
||||
end
|
||||
|
||||
def decoder
|
||||
def decode(_response)
|
||||
::JSON.method(:parse)
|
||||
end
|
||||
end
|
||||
|
@ -29,6 +29,8 @@ module HTTPX
|
||||
|
||||
def json: (?json_options opts) -> untyped
|
||||
|
||||
def form: () -> Hash[String, untyped]
|
||||
|
||||
private
|
||||
|
||||
def initialize: (Request request, String | Integer status, String version, headers?) -> untyped
|
||||
|
@ -6,10 +6,12 @@ module HTTPX
|
||||
| () -> Hash[String, _Encode]
|
||||
|
||||
def self?.register: (String tag, _Encode handler) -> void
|
||||
|
||||
|
||||
def self?.normalize_keys: (_ToS key, _ToAry[untyped] | _ToHash[_ToS, untyped] | untyped value) { (String, ?untyped) -> void } -> void
|
||||
| (_ToS key, untyped value, Proc? cond) { (String, untyped) -> void } -> void
|
||||
|
||||
def self?.normalize_query: (Hash[String, untyped] params, String name, String v, Integer depth)
|
||||
|
||||
interface _Encode
|
||||
def encode: (untyped payload) -> (_Encoder | _Each[String])
|
||||
end
|
||||
|
@ -136,8 +136,25 @@ class ResponseTest < Minitest::Test
|
||||
assert form_response.form == { "a" => "b", "c" => "d" }
|
||||
|
||||
form2_response = Response.new(request, 200, "2.0", { "content-type" => "application/x-www-form-urlencoded" })
|
||||
form2_response << "a[]=b&a[]=c&d[e]=f"
|
||||
assert form2_response.form == { "a" => %w[b c], "d" => { "e" => "f" } }
|
||||
form2_response << "a[]=b&a[]=c&d[e]=f&g[h][i][j]=k&l[m][][n]=o&l[m][][p]=q&l[m][][n]=r&s[=t"
|
||||
assert form2_response.form == {
|
||||
"a" => %w[b c],
|
||||
"d" => { "e" => "f" },
|
||||
"g" => { "h" => { "i" => { "j" => "k" } } },
|
||||
"l" => { "m" => [{ "n" => "o", "p" => "q" }, { "n" => "r" }] },
|
||||
"s[" => "t",
|
||||
}
|
||||
|
||||
form3_response = Response.new(request, 200, "2.0", { "content-type" => "application/x-www-form-urlencoded" })
|
||||
form3_response << "a[][]=3"
|
||||
assert form3_response.form == { "a" => [["3"]] }
|
||||
|
||||
form4_response = Response.new(request, 200, "2.0", { "content-type" => "application/x-www-form-urlencoded" })
|
||||
form4_response << "[]"
|
||||
assert form4_response.form == {}
|
||||
|
||||
error = assert_raises(HTTPX::Error) { form2_response.__send__(:decode, "bla") }
|
||||
assert error.message =~ /no decoder available for/, "failed with unexpected error"
|
||||
end
|
||||
|
||||
private
|
||||
|
Loading…
x
Reference in New Issue
Block a user