Skip to content

Commit

Permalink
moved xml encoding/decoding + APIs into :xml plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
HoneyryderChuck committed Dec 8, 2024
1 parent d45cae0 commit bf9d847
Show file tree
Hide file tree
Showing 16 changed files with 179 additions and 107 deletions.
4 changes: 4 additions & 0 deletions lib/httpx/plugins/webdav.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ module Plugins
# https://gitlab.com/os85/httpx/wikis/WebDav
#
module WebDav
def self.configure(klass)
klass.plugin(:xml)
end

module InstanceMethods
def copy(src, dest)
request("COPY", src, headers: { "destination" => @options.origin.merge(dest) })
Expand Down
76 changes: 76 additions & 0 deletions lib/httpx/plugins/xml.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

module HTTPX
module Plugins
#
# This plugin supports request XML encoding/response decoding using the nokogiri gem.
#
# https://gitlab.com/os85/httpx/wikis/XML
#
module XML
MIME_TYPES = %r{\b(application|text)/(.+\+)?xml\b}.freeze
module Transcoder
module_function

class Encoder
def initialize(xml)
@raw = xml
end

def content_type
charset = @raw.respond_to?(:encoding) && @raw.encoding ? @raw.encoding.to_s.downcase : "utf-8"
"application/xml; charset=#{charset}"
end

def bytesize
@raw.to_s.bytesize
end

def to_s
@raw.to_s
end
end

def encode(xml)
Encoder.new(xml)
end

def decode(response)
content_type = response.content_type.mime_type

raise HTTPX::Error, "invalid form mime type (#{content_type})" unless MIME_TYPES.match?(content_type)

Nokogiri::XML.method(:parse)
end
end

class << self
def load_dependencies(*)
require "nokogiri"
end
end

module ResponseMethods
# decodes the response payload into a Nokogiri::XML::Node object **if** the payload is valid
# "application/xml" (requires the "nokogiri" gem).
def xml
decode(Transcoder)
end
end

module RequestBodyClassMethods
# ..., xml: Nokogiri::XML::Node #=> xml encoder
def initialize_body(params)
if (xml = params.delete(:xml))
# @type var xml: Nokogiri::XML::Node | String
return Transcoder.encode(xml)
end

super
end
end
end

register_plugin(:xml, XML)
end
end
28 changes: 16 additions & 12 deletions lib/httpx/request/body.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,11 @@ def new(_, options, body: nil, **params)
# ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
# ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
# ..., form: { body: "bla") }) #=> raw data encoder
def initialize(headers, options, body: nil, form: nil, json: nil, xml: nil, **params)
@headers = headers
def initialize(h, options, **params)
@headers = h
@body = self.class.initialize_body(params)
@options = options.merge(params)

@body = if body
Transcoder::Body.encode(body)
elsif form
Transcoder::Form.encode(form)
elsif json
Transcoder::JSON.encode(json)
elsif xml
Transcoder::Xml.encode(xml)
end

if @body
if @options.compress_request_body && @headers.key?("content-encoding")

Expand Down Expand Up @@ -123,6 +114,19 @@ def inspect
# :nocov:

class << self
def initialize_body(params)
if (body = params.delete(:body))
# @type var body: bodyIO
Transcoder::Body.encode(body)
elsif (form = params.delete(:form))
# @type var form: Transcoder::urlencoded_input
Transcoder::Form.encode(form)
elsif (json = params.delete(:json))
# @type var body: _ToJson
Transcoder::JSON.encode(json)
end
end

# returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
def initialize_deflater_body(body, encoding)
case encoding
Expand Down
6 changes: 0 additions & 6 deletions lib/httpx/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,6 @@ def form
decode(Transcoder::Form)
end

# decodes the response payload into a Nokogiri::XML::Node object **if** the payload is valid
# "application/xml" (requires the "nokogiri" gem).
def xml
decode(Transcoder::Xml)
end

private

# decodes the response payload using the given +transcoder+, which implements the decoding logic.
Expand Down
1 change: 0 additions & 1 deletion lib/httpx/transcoder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ def params_hash_has_key?(hash, key)
require "httpx/transcoder/body"
require "httpx/transcoder/form"
require "httpx/transcoder/json"
require "httpx/transcoder/xml"
require "httpx/transcoder/chunker"
require "httpx/transcoder/deflate"
require "httpx/transcoder/gzip"
52 changes: 0 additions & 52 deletions lib/httpx/transcoder/xml.rb

This file was deleted.

1 change: 1 addition & 0 deletions sig/chainable.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module HTTPX
| (:oauth, ?options) -> Plugins::sessionOAuth
| (:callbacks, ?options) -> Plugins::sessionCallbacks
| (:content_digest, ?options) -> Plugins::sessionContentDigest
| (:xml, ?options) -> Plugins::sessionXML
| (Symbol | Module, ?options) { (Class) -> void } -> Session
| (Symbol | Module, ?options) -> Session

Expand Down
2 changes: 1 addition & 1 deletion sig/options.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ module HTTPX

def do_initialize: (?options options) -> void

def access_option: (Hash | Object | nil obj, Symbol k, Hash[Symbol, Symbol]? ivar_map) -> untyped
def access_option: (Hash[Symbol, untyped] | Object | nil obj, Symbol k, Hash[Symbol, Symbol]? ivar_map) -> untyped
end

type options = Options | Hash[Symbol, untyped]
Expand Down
37 changes: 37 additions & 0 deletions sig/plugins/xml.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module HTTPX
module Plugins
module XML
MIME_TYPES: Regexp

module Transcoder
def self?.encode: (Nokogiri::XML::Node | String xml) -> Encoder
def self?.decode: (HTTPX::Response response) -> HTTPX::Transcoder::_Decoder

class Encoder
@raw: Nokogiri::XML::Node | String # can be nokogiri object

def content_type: () -> String

def bytesize: () -> (Integer | Float)

def to_s: () -> String

private

def initialize: (Nokogiri::XML::Node | String xml) -> void
end
end

module InstanceMethods
end

module ResponseMethods
def xml: () -> Nokogiri::XML::Node
end

module RequestBodyClassMethods
end
end
type sessionXML = Session & XML::InstanceMethods
end
end
6 changes: 5 additions & 1 deletion sig/request/body.rbs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
module HTTPX
class Request::Body
attr_reader options: Options

@headers: Headers
@body: body_encoder?
@unbounded_body: bool

def initialize: (Headers headers, Options options, ?body: bodyIO, ?form: Transcoder::urlencoded_input?, ?json: _ToJson?, **untyped) -> void
def initialize: (Headers h, Options options, **untyped) -> void

def each: () { (String) -> void } -> void
| () -> Enumerable[String]
Expand All @@ -25,6 +27,8 @@ module HTTPX

private

def self.initialize_body: (Hash[Symbol, untyped] params) -> body_encoder?

def self.initialize_deflater_body: (body_encoder body, Encoding | String encoding) -> body_encoder
end

Expand Down
22 changes: 0 additions & 22 deletions sig/transcoder/xml.rbs

This file was deleted.

1 change: 1 addition & 0 deletions test/http_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class HTTPTest < Minitest::Test
include Plugins::WebDav
include Plugins::Brotli if RUBY_ENGINE == "ruby"
include Plugins::SsrfFilter
include Plugins::XML

def test_verbose_log
log = StringIO.new
Expand Down
1 change: 1 addition & 0 deletions test/https_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class HTTPSTest < Minitest::Test
include Plugins::WebDav
include Plugins::Brotli if RUBY_ENGINE == "ruby"
include Plugins::SsrfFilter
include Plugins::XML

def test_ssl_session_resumption
uri = build_uri("/get")
Expand Down
7 changes: 0 additions & 7 deletions test/request_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,6 @@ def test_request_body_json
assert req.headers["content-length"] == "13", "content length is wrong"
end

def test_request_body_xml
req = make_request("POST", "http://example.com/", xml: "<xml></xml>")
assert !req.body.empty?, "body should exist"
assert req.headers["content-type"] == "application/xml; charset=utf-8", "content type is wrong"
assert req.headers["content-length"] == "11", "content length is wrong"
end

def test_request_body_deflater_for_anything
body = Request::Body.new(Headers.new({ "content-encoding" => "unknown" }), Options.new, body: "foo")
assert body.to_s == "foo"
Expand Down
5 changes: 0 additions & 5 deletions test/response_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,6 @@ def test_response_decoders
form_response << "богус"
assert_raises(ArgumentError) { form_response.form }

xml_response = Response.new(request, 200, "2.0", { "content-type" => "application/xml; charset=utf-8" })
xml_response << "<xml></xml>"
xml = xml_response.xml
assert xml.is_a?(Nokogiri::XML::Node)

form2_response = Response.new(request, 200, "2.0", { "content-type" => "application/x-www-form-urlencoded" })
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 == {
Expand Down
37 changes: 37 additions & 0 deletions test/support/requests/plugins/xml.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

require "nokogiri"

module Requests
module Plugins
module XML
def test_plugin_xml_request_body_document
uri = build_uri("/post")
response = HTTPX.plugin(:xml).post(uri, xml: Nokogiri::XML("<xml></xml>"))
verify_status(response, 200)
body = json_body(response)
verify_header(body["headers"], "Content-Type", "application/xml; charset=utf-8")
# nokogiri in cruby adds \n trailer, jruby doesn't
assert body["data"].start_with?("<?xml version=\"1.0\"?>\n<xml/>")
end

def test_plugin_xml_request_body_string
uri = build_uri("/post")
response = HTTPX.plugin(:xml).post(uri, xml: "<xml></xml>")
verify_status(response, 200)
body = json_body(response)
verify_header(body["headers"], "Content-Type", "application/xml; charset=utf-8")
assert body["data"] == "<xml></xml>"
end

def test_plugin_xml_response
uri = build_uri("/xml")
response = HTTPX.plugin(:xml).get(uri)
verify_status(response, 200)
verify_body_length(response)
xml = response.xml
assert xml.is_a?(Nokogiri::XML::Node)
end
end
end
end

0 comments on commit bf9d847

Please sign in to comment.