Skip to content

Commit

Permalink
Merge f0d7bc5 into 47b3979
Browse files Browse the repository at this point in the history
  • Loading branch information
iMacTia committed Feb 16, 2022
2 parents 47b3979 + f0d7bc5 commit 01c9ae4
Show file tree
Hide file tree
Showing 14 changed files with 423 additions and 17 deletions.
4 changes: 4 additions & 0 deletions docs/middleware/list.md
Expand Up @@ -27,6 +27,8 @@ base64 representation.
* [`Multipart`][multipart] converts a `Faraday::Request#body` hash of key/value pairs into a
multipart form request.
* [`UrlEncoded`][url_encoded] converts a `Faraday::Request#body` hash of key/value pairs into a url-encoded request body.
* [`Json Request`][json-request] converts a `Faraday::Request#body` hash of key/value pairs into a JSON request body.
* [`Json Response`][json-response] parses response body into a hash of key/value pairs.
* [`Retry`][retry] automatically retries requests that fail due to intermittent client
or server errors (such as network hiccups).
* [`Instrumentation`][instrumentation] allows to instrument requests using different tools.
Expand All @@ -44,7 +46,9 @@ before returning it.
[authentication]: ./authentication
[multipart]: ./multipart
[url_encoded]: ./url-encoded
[json-request]: ./json-request
[retry]: ./retry
[instrumentation]: ./instrumentation
[json-response]: ./json-response
[logger]: ./logger
[raise_error]: ./raise-error
4 changes: 2 additions & 2 deletions docs/middleware/request/instrumentation.md
Expand Up @@ -5,8 +5,8 @@ permalink: /middleware/instrumentation
hide: true
prev_name: Retry Middleware
prev_link: ./retry
next_name: Logger Middleware
next_link: ./logger
next_name: JSON Response Middleware
next_link: ./json-response
top_name: Back to Middleware
top_link: ./list
---
Expand Down
31 changes: 31 additions & 0 deletions docs/middleware/request/json.md
@@ -0,0 +1,31 @@
---
layout: documentation
title: "JSON Request Middleware"
permalink: /middleware/json-request
hide: true
prev_name: UrlEncoded Middleware
prev_link: ./url-encoded
next_name: Retry Middleware
next_link: ./retry
top_name: Back to Middleware
top_link: ./list
---

The `JSON` request middleware converts a `Faraday::Request#body` hash of key/value pairs into a JSON request body.
The middleware also automatically sets the `Content-Type` header to `application/json`,
processes only requests with matching Content-Type or those without a type and
doesn't try to encode bodies that already are in string form.

### Example Usage

```ruby
conn = Faraday.new(...) do |f|
f.request :json
...
end

conn.post('/', { a: 1, b: 2 })
# POST with
# Content-Type: application/json
# Body: {"a":1,"b":2}
```
4 changes: 2 additions & 2 deletions docs/middleware/request/url_encoded.md
Expand Up @@ -5,8 +5,8 @@ permalink: /middleware/url-encoded
hide: true
prev_name: Multipart Middleware
prev_link: ./multipart
next_name: Retry Middleware
next_link: ./retry
next_name: JSON Request Middleware
next_link: ./json-request
top_name: Back to Middleware
top_link: ./list
---
Expand Down
29 changes: 29 additions & 0 deletions docs/middleware/response/json.md
@@ -0,0 +1,29 @@
---
layout: documentation
title: "JSON Response Middleware"
permalink: /middleware/json-response
hide: true
prev_name: Instrumentation Middleware
prev_link: ./instrumentation
next_name: Logger Middleware
next_link: ./logger
top_name: Back to Middleware
top_link: ./list
---

The `JSON` response middleware parses response body into a hash of key/value pairs.
The behaviour can be customized with the following options:
* **parser_options:** options that will be sent to the JSON.parse method. Defaults to {}.
* **content_type:** Single value or Array of response content-types that should be processed. Can be either strings or Regex. Defaults to `/\bjson$/`.
* **preserve_raw:** If set to true, the original un-parsed response will be stored in the `response.env[:raw_body]` property. Defaults to `false`.

### Example Usage

```ruby
conn = Faraday.new('http://httpbingo.org') do |f|
f.response :json, **options
end

conn.get('json').body
# => {"slideshow"=>{"author"=>"Yours Truly", "date"=>"date of publication", "slides"=>[{"title"=>"Wake up to WonderWidgets!", "type"=>"all"}, {"items"=>["Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets"], "title"=>"Overview", "type"=>"all"}], "title"=>"Sample Slide Show"}}
```
4 changes: 2 additions & 2 deletions docs/middleware/response/logger.md
Expand Up @@ -3,8 +3,8 @@ layout: documentation
title: "Logger Middleware"
permalink: /middleware/logger
hide: true
prev_name: Instrumentation Middleware
prev_link: ./instrumentation
prev_name: JSON Response Middleware
prev_link: ./json-response
next_name: RaiseError Middleware
next_link: ./raise-error
top_name: Back to Middleware
Expand Down
2 changes: 2 additions & 0 deletions lib/faraday.rb
Expand Up @@ -57,6 +57,8 @@
# conn.get '/'
#
module Faraday
CONTENT_TYPE = 'Content-Type'

class << self
# The root path that Faraday is being loaded from.
#
Expand Down
13 changes: 7 additions & 6 deletions lib/faraday/request.rb
Expand Up @@ -44,7 +44,8 @@ class Request < Struct.new(
:TokenAuthentication,
'token_authentication'
],
instrumentation: [:Instrumentation, 'instrumentation']
instrumentation: [:Instrumentation, 'instrumentation'],
json: [:Json, 'json']

# @param request_method [String]
# @yield [request] for block customization, if block given
Expand Down Expand Up @@ -138,11 +139,11 @@ def marshal_dump
# @param serialised [Hash] the serialised object.
def marshal_load(serialised)
self.http_method = serialised[:http_method]
self.body = serialised[:body]
self.headers = serialised[:headers]
self.path = serialised[:path]
self.params = serialised[:params]
self.options = serialised[:options]
self.body = serialised[:body]
self.headers = serialised[:headers]
self.path = serialised[:path]
self.params = serialised[:params]
self.options = serialised[:options]
end

# @return [Env] the Env for this Request
Expand Down
55 changes: 55 additions & 0 deletions lib/faraday/request/json.rb
@@ -0,0 +1,55 @@
# frozen_string_literal: true

require 'json'

module Faraday
class Request
# Request middleware that encodes the body as JSON.
#
# Processes only requests with matching Content-type or those without a type.
# If a request doesn't have a type but has a body, it sets the Content-type
# to JSON MIME-type.
#
# Doesn't try to encode bodies that already are in string form.
class Json < Middleware
MIME_TYPE = 'application/json'
MIME_TYPE_REGEX = %r{^application/(vnd\..+\+)?json$}.freeze

def on_request(env)
match_content_type(env) do |data|
env[:body] = encode(data)
end
end

private

def encode(data)
::JSON.generate(data)
end

def match_content_type(env)
return unless process_request?(env)

env[:request_headers][CONTENT_TYPE] ||= MIME_TYPE
yield env[:body] unless env[:body].respond_to?(:to_str)
end

def process_request?(env)
type = request_type(env)
body?(env) && (type.empty? || type.match?(MIME_TYPE_REGEX))
end

def body?(env)
(body = env[:body]) && !(body.respond_to?(:to_str) && body.empty?)
end

def request_type(env)
type = env[:request_headers][CONTENT_TYPE].to_s
type = type.split(';', 2).first if type.index(';')
type
end
end
end
end

Faraday::Request.register_middleware(json: Faraday::Request::Json)
4 changes: 3 additions & 1 deletion lib/faraday/response.rb
Expand Up @@ -22,7 +22,8 @@ def on_complete(env)

register_middleware File.expand_path('response', __dir__),
raise_error: [:RaiseError, 'raise_error'],
logger: [:Logger, 'logger']
logger: [:Logger, 'logger'],
json: [:Json, 'json']

def initialize(env = nil)
@env = Env.from(env) if env
Expand All @@ -42,6 +43,7 @@ def reason_phrase
def headers
finished? ? env.response_headers : {}
end

def_delegator :headers, :[]

def body
Expand Down
54 changes: 54 additions & 0 deletions lib/faraday/response/json.rb
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require 'json'

module Faraday
class Response
# Parse response bodies as JSON.
class Json < Middleware
def initialize(app = nil, options = {})
super(app)
@parser_options = options[:parser_options]
@content_types = Array(options[:content_type] || /\bjson$/)
@preserve_raw = options[:preserve_raw]
end

def on_complete(env)
process_response(env) if parse_response?(env)
end

private

def process_response(env)
env[:raw_body] = env[:body] if @preserve_raw
env[:body] = parse(env[:body])
rescue StandardError, SyntaxError => e
raise Faraday::ParsingError.new(e, env[:response])
end

def parse(body)
::JSON.parse(body, @parser_options || {}) unless body.strip.empty?
end

def parse_response?(env)
process_response_type?(env) &&
env[:body].respond_to?(:to_str)
end

def process_response_type?(env)
type = response_type(env)
@content_types.empty? || @content_types.any? do |pattern|
pattern.is_a?(Regexp) ? type.match?(pattern) : type == pattern
end
end

def response_type(env)
type = env[:response_headers][CONTENT_TYPE].to_s
type = type.split(';', 2).first if type.index(';')
type
end
end
end
end

Faraday::Response.register_middleware(json: Faraday::Response::Json)
6 changes: 2 additions & 4 deletions lib/faraday/response/logger.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'forwardable'
require 'logger'
require 'faraday/logging/formatter'

module Faraday
Expand All @@ -11,10 +12,7 @@ class Response
class Logger < Middleware
def initialize(app, logger = nil, options = {})
super(app)
logger ||= begin
require 'logger'
::Logger.new($stdout)
end
logger ||= ::Logger.new($stdout)
formatter_class = options.delete(:formatter) || Logging::Formatter
@formatter = formatter_class.new(logger: logger, options: options)
yield @formatter if block_given?
Expand Down

0 comments on commit 01c9ae4

Please sign in to comment.