httprb / http

HTTP (The Gem! a.k.a. http.rb) - a fast Ruby HTTP client with a chainable API, streaming support, and timeouts
MIT License
3.01k stars 321 forks source link

4.x add deprecation warning to response.parse() #665

Closed ronald closed 3 years ago

ronald commented 3 years ago

In Version 5 Http.get(url).parse throws ArgumentError "wrong number of arguments (given 0, expected 1)" https://github.com/httprb/http/blob/53b7b91ac3012dbdfb43fa65947fd8d9c2311c7c/lib/http/response.rb#L159 introduced with #538.

I suggest adding a deprecation warning in Version 4.

tarcieri commented 3 years ago

That's a good idea.

ixti commented 3 years ago

https://github.com/httprb/http/commit/fa542332fd625a26480bb8677be16c66660bd507

DannyBen commented 3 years ago

Actually.... I am not sure I understand why this API was changed at all? This change makes the #parse method much less usable. If I know what the content type is, I do not need the HTTP gem to parse it for me - I can JSON.parse it myself.... I was assuming that the intention of the #parse method is to do "best effort" to parse it.

In addition, the response object already knows what the mime type is, so why not use it to parse?

The below code feels wrong:

require 'http'

url = "https://jsonplaceholder.typicode.com/posts/1"
response = HTTP.get url

# Why do we have to do this...
parsed_response = response.parse(response.mime_type)

# ...instead of this?
# parsed_response = response.parse

p parsed_response

To me, the beauty of the HTTP gem is that it "just works". Can't the #parse method be changed to have its type argument optional?

# lib/http/response.rb
def parse(type = nil)
  type ||= mime_type
  MimeType[type].decode to_s
end

What am I missing?

ixti commented 3 years ago

@DannyBen

Even if you pass Accept: application/json header, server might respond with plain text, or html, etc. So implicit mime type inferring from the response might lead into a confusing experience IMO:

HTTP.headers(:accept => "application/json").get("https://httpbin.org/html").parse
# HTTP::Error: Unknown MIME type: text/html

Let's say you define HTML decoder for that:

module HTTP
  module MimeType
    class HTML < Adapter
      def decode(str)
        ::Nokogiri::HTML str
      end
    end

    register_adapter "text/html", HTML
    register_alias   "text/html", :html
  end
end

And let's say you expect result to be a JSON, but it comes back as HTML (error or such):

payload = HTTP.headers(:accept => "application/json").get("https://httpbin.org/html").parse

Now you have a parsed response. But it's far from what you expect. So, to be honest I consider parse as a simple helper over JSON.parse(response.to_s), thus I think response.parse(:json) (which can be enhanced to pick up the most efficient adapter) is a better choice.


See Also: https://github.com/httprb/http/issues/538

ixti commented 3 years ago

It's easy enough to revert this API change locally:

module HTTP
  class Response
    module ImplicitParse
      def parse(as = mime_type)
        super(as)
      end
    end

    prepend ImplicitParse
  end
end

You can also overload HTTP::Response#parse completely.

DannyBen commented 3 years ago

And let's say you expect result to be a JSON, but it comes back as HTML (error or such):

We can say a lot of things.... 😜

If the response comes back with unexpected content, anything will fail. I don't see the point of this line of questioning. This will also fail with me calling response.parse(:json).

Anyways, that is my opinion. Not a big deal from my own standpoint (I switched to JSON.parse), but I feel it is the wrong direction for this particular feature.

ronald commented 3 years ago

Since we are already off topic, i can contribute my 2 cents.

How about if we let parse() be automatic again, but require an allow list to be provided. So only if the mimetype matches the expected one, automatic parsing takes place.

Http.get(url).parse(:json, :xml)
DannyBen commented 3 years ago

Since we are already off topic

Sorry, this is probably my bad. I thought we are on topic, since opening another topic on the subject would have conflicted with this one. My on-topic thought was "don't deprecate, revert".

Http.get(url).parse(:json, :xml)

Personally, I do not see the point in that. It is not the responsibility of an HTTP library to provide parsing wrappers. It is the responsibility of a non-bare-bones HTTP library (such as the HTTP gem in my understanding), to provide automatic parsing when it can do so easily and reliably.

I do not know what was the past implementation that prompted the authors to change it, but in my view "automatic" does not mean "make a guess". It simply means "use the information available to you". So, if the response object has a mime type, and if it knows how to parse this mime type, then Response#parse can be used to automatically get the parsed output. Otherwise, calling #parse should result in some friendly UnspecifiedMimeType exception or similar.

I do not think that situations where the server does not return a mime type, or when it returns different mime types for the same endpoint should be the used as a guide for the implementation. These are edge cases of badly behaving servers. If some users face this, they should test for the mime type themselves and act accordingly.

Lastly, I do not see an advantage of calling response.parse :json instead of JSON.parse response. This is just masking a native Ruby behavior behind a method that needs maintaining and explaining.

ronald commented 3 years ago

IMHO on-topic would have been there: #538

DannyBen commented 3 years ago

IMHO on-topic would have been there: #538

A closed 2 years old topic? I doubt any reply there would have received any attention, but one thing is certain - discussing whether or not this is on topic, is by itself off topic... :)

Bottom line, your call of course how to handle this. If there is any weight to my opinion, I say either make it parse by mime type (and fail gracefully if it can't), or remove the parse method altogether. Any other route is awkward at best, and bad form at worst.

tarcieri commented 3 years ago

If you really liked parse() with no argument and want it back, I'd suggest opening a PR that reverts #538 and we can discuss it there.

ixti commented 3 years ago

I've reverted the deprecation warning in 4.x.x branch, further discussion of #parse future should go in #670