faraday_middleware 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. data/.rspec +0 -1
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +9 -9
  4. data/Gemfile +3 -3
  5. data/README.md +2 -2
  6. data/Rakefile +9 -2
  7. data/faraday_middleware.gemspec +16 -24
  8. data/lib/faraday_middleware.rb +51 -11
  9. data/lib/faraday_middleware/addressable_patch.rb +20 -0
  10. data/lib/faraday_middleware/backwards_compatibility.rb +30 -0
  11. data/lib/faraday_middleware/instrumentation.rb +30 -0
  12. data/lib/faraday_middleware/rack_compatible.rb +76 -0
  13. data/lib/faraday_middleware/request/encode_json.rb +50 -0
  14. data/lib/faraday_middleware/request/oauth.rb +61 -0
  15. data/lib/faraday_middleware/request/oauth2.rb +60 -0
  16. data/lib/faraday_middleware/response/caching.rb +76 -0
  17. data/lib/faraday_middleware/response/follow_redirects.rb +53 -0
  18. data/lib/{faraday → faraday_middleware}/response/mashify.rb +2 -2
  19. data/lib/faraday_middleware/response/parse_json.rb +35 -0
  20. data/lib/faraday_middleware/response/parse_marshal.rb +10 -0
  21. data/lib/faraday_middleware/response/parse_xml.rb +11 -0
  22. data/lib/faraday_middleware/response/parse_yaml.rb +10 -0
  23. data/lib/faraday_middleware/response/rashify.rb +9 -0
  24. data/lib/faraday_middleware/response_middleware.rb +78 -0
  25. data/lib/faraday_middleware/version.rb +1 -1
  26. data/spec/caching_test.rb +122 -0
  27. data/spec/encode_json_spec.rb +95 -0
  28. data/spec/follow_redirects_spec.rb +33 -0
  29. data/spec/helper.rb +27 -12
  30. data/spec/mashify_spec.rb +8 -7
  31. data/spec/oauth2_spec.rb +100 -32
  32. data/spec/oauth_spec.rb +83 -28
  33. data/spec/parse_json_spec.rb +71 -46
  34. data/spec/parse_marshal_spec.rb +9 -26
  35. data/spec/parse_xml_spec.rb +56 -24
  36. data/spec/parse_yaml_spec.rb +40 -20
  37. data/spec/rashify_spec.rb +4 -3
  38. metadata +59 -57
  39. data/lib/faraday/request/oauth.rb +0 -23
  40. data/lib/faraday/request/oauth2.rb +0 -24
  41. data/lib/faraday/response/parse_json.rb +0 -20
  42. data/lib/faraday/response/parse_marshal.rb +0 -10
  43. data/lib/faraday/response/parse_xml.rb +0 -11
  44. data/lib/faraday/response/parse_yaml.rb +0 -11
  45. data/lib/faraday/response/rashify.rb +0 -19
@@ -0,0 +1,60 @@
1
+ require 'faraday'
2
+ require 'forwardable'
3
+
4
+ module FaradayMiddleware
5
+ # Public: A simple middleware that adds an access token to each request.
6
+ #
7
+ # The token is added as both "access_token" query parameter and the
8
+ # "Authorization" HTTP request header. However, an explicit "access_token"
9
+ # parameter or "Authorization" header for the current request are not
10
+ # overriden.
11
+ #
12
+ # Examples
13
+ #
14
+ # # configure default token:
15
+ # OAuth2.new(app, 'abc123')
16
+ #
17
+ # # configure query parameter name:
18
+ # OAuth2.new(app, 'abc123', :param_name => 'my_oauth_token')
19
+ #
20
+ # # default token value is optional:
21
+ # OAuth2.new(app, :param_name => 'my_oauth_token')
22
+ class OAuth2 < Faraday::Middleware
23
+ dependency 'oauth2'
24
+
25
+ PARAM_NAME = 'access_token'.freeze
26
+ AUTH_HEADER = 'Authorization'.freeze
27
+
28
+ attr_reader :param_name
29
+
30
+ extend Forwardable
31
+ def_delegators :'Faraday::Utils', :parse_query, :build_query
32
+
33
+ def call(env)
34
+ params = { param_name => @token }.update query_params(env[:url])
35
+
36
+ if token = params[param_name] and !token.empty?
37
+ env[:url].query = build_query params
38
+ env[:request_headers][AUTH_HEADER] ||= %(Token token="#{token}")
39
+ end
40
+
41
+ @app.call env
42
+ end
43
+
44
+ def initialize(app, token = nil, options = {})
45
+ super(app)
46
+ options, token = token, nil if token.is_a? Hash
47
+ @token = token && token.to_s
48
+ @param_name = options.fetch(:param_name, PARAM_NAME).to_s
49
+ raise ArgumentError, ":param_name can't be blank" if @param_name.empty?
50
+ end
51
+
52
+ def query_params(url)
53
+ if url.query.nil? or url.query.empty?
54
+ {}
55
+ else
56
+ parse_query url.query
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,76 @@
1
+ require 'faraday'
2
+ require 'forwardable'
3
+ # fixes normalizing query strings:
4
+ require 'faraday_middleware/addressable_patch' if defined? ::Addressable::URI
5
+
6
+ module FaradayMiddleware
7
+ # Public: Caches GET responses and pulls subsequent ones from the cache.
8
+ class Caching < Faraday::Middleware
9
+ attr_reader :cache
10
+
11
+ extend Forwardable
12
+ def_delegators :'Faraday::Utils', :parse_query, :build_query
13
+
14
+ # Public: initialize the middleware.
15
+ #
16
+ # cache - An object that responds to read, write and fetch (default: nil).
17
+ # options - An options Hash (default: {}):
18
+ # :ignore_params - String name or Array names of query params
19
+ # that should be ignored when forming the cache
20
+ # key (default: []).
21
+ #
22
+ # Yields if no cache is given. The block should return a cache object.
23
+ def initialize(app, cache = nil, options = {})
24
+ super(app)
25
+ options, cache = cache, nil if cache.is_a? Hash and block_given?
26
+ @cache = cache || yield
27
+ @options = options
28
+ end
29
+
30
+ def call(env)
31
+ if :get == env[:method]
32
+ if env[:parallel_manager]
33
+ # callback mode
34
+ cache_on_complete(env)
35
+ else
36
+ # synchronous mode
37
+ response = cache.fetch(cache_key(env)) { @app.call(env) }
38
+ finalize_response(response, env)
39
+ end
40
+ else
41
+ @app.call(env)
42
+ end
43
+ end
44
+
45
+ def cache_key(env)
46
+ url = env[:url].dup
47
+ if url.query && params_to_ignore.any?
48
+ params = parse_query url.query
49
+ params.reject! {|k,| params_to_ignore.include? k }
50
+ url.query = build_query params
51
+ end
52
+ url.normalize!
53
+ url.request_uri
54
+ end
55
+
56
+ def params_to_ignore
57
+ @params_to_ignore ||= Array(@options[:ignore_params]).map { |p| p.to_s }
58
+ end
59
+
60
+ def cache_on_complete(env)
61
+ key = cache_key(env)
62
+ if cached_response = cache.read(key)
63
+ finalize_response(cached_response, env)
64
+ else
65
+ response = @app.call(env)
66
+ response.on_complete { cache.write(key, response) }
67
+ end
68
+ end
69
+
70
+ def finalize_response(response, env)
71
+ response = env[:response] = response.dup if response.frozen?
72
+ response.apply_request env unless response.env[:method]
73
+ response
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,53 @@
1
+ require 'faraday'
2
+
3
+ module FaradayMiddleware
4
+ class RedirectLimitReached < Faraday::Error::ClientError
5
+ attr_reader :response
6
+
7
+ def initialize(response)
8
+ super "too many redirects; last one to: #{response['location']}"
9
+ @response = response
10
+ end
11
+ end
12
+
13
+ # Public: Follow HTTP 30x redirects.
14
+ class FollowRedirects < Faraday::Middleware
15
+ # TODO: 307 & standards-compliant 302
16
+ REDIRECTS = [301, 302, 303]
17
+ # Default value for max redirects followed
18
+ FOLLOW_LIMIT = 3
19
+
20
+ # Public: Initialize the middleware.
21
+ #
22
+ # options - An options Hash (default: {}):
23
+ # limit - A Numeric redirect limit (default: 3)
24
+ def initialize(app, options = {})
25
+ super(app)
26
+ @options = options
27
+ end
28
+
29
+ def call(env)
30
+ process_response(@app.call(env), follow_limit)
31
+ end
32
+
33
+ def process_response(response, follows)
34
+ response.on_complete do |env|
35
+ if redirect? response
36
+ raise RedirectLimitReached, response if follows.zero?
37
+ env[:url] += response['location']
38
+ env[:method] = :get
39
+ response = process_response(@app.call(env), follows - 1)
40
+ end
41
+ end
42
+ response
43
+ end
44
+
45
+ def redirect?(response)
46
+ REDIRECTS.include? response.status
47
+ end
48
+
49
+ def follow_limit
50
+ @options.fetch(:limit, FOLLOW_LIMIT)
51
+ end
52
+ end
53
+ end
@@ -1,7 +1,7 @@
1
1
  require 'faraday'
2
2
 
3
- module Faraday
4
- class Response::Mashify < Response::Middleware
3
+ module FaradayMiddleware
4
+ class Mashify < Faraday::Response::Middleware
5
5
  dependency 'hashie/mash'
6
6
 
7
7
  class << self
@@ -0,0 +1,35 @@
1
+ require 'faraday_middleware/response_middleware'
2
+
3
+ module FaradayMiddleware
4
+ # Public: Parse response bodies as JSON.
5
+ class ParseJson < ResponseMiddleware
6
+ dependency 'json'
7
+
8
+ define_parser { |body|
9
+ JSON.parse body unless body.empty?
10
+ }
11
+
12
+ # Public: Override the content-type of the response with "application/json"
13
+ # if the response body looks like it might be JSON, i.e. starts with an
14
+ # open bracket.
15
+ #
16
+ # This is to fix responses from certain API providers that insist on serving
17
+ # JSON with wrong MIME-types such as "text/javascript".
18
+ class MimeTypeFix < ResponseMiddleware
19
+ MIME_TYPE = 'application/json'.freeze
20
+
21
+ def process_response(env)
22
+ old_type = env[:response_headers][CONTENT_TYPE].to_s
23
+ new_type = MIME_TYPE.dup
24
+ new_type << ';' << old_type.split(';', 2).last if old_type.index(';')
25
+ env[:response_headers][CONTENT_TYPE] = new_type
26
+ end
27
+
28
+ BRACKETS = %w- [ { -
29
+
30
+ def parse_response?(env)
31
+ super and BRACKETS.include? env[:body][0,1]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,10 @@
1
+ require 'faraday_middleware/response_middleware'
2
+
3
+ module FaradayMiddleware
4
+ # Public: Restore marshalled Ruby objects in response bodies.
5
+ class ParseMarshal < ResponseMiddleware
6
+ define_parser { |body|
7
+ ::Marshal.load body unless body.empty?
8
+ }
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ require 'faraday_middleware/response_middleware'
2
+
3
+ module FaradayMiddleware
4
+ class ParseXml < ResponseMiddleware
5
+ dependency 'multi_xml'
6
+
7
+ define_parser { |body|
8
+ ::MultiXml.parse(body)
9
+ }
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ require 'faraday_middleware/response_middleware'
2
+
3
+ module FaradayMiddleware
4
+ # Public: Parse response bodies as YAML.
5
+ class ParseYaml < ResponseMiddleware
6
+ dependency 'yaml'
7
+
8
+ define_parser { |body| ::YAML.load body }
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ require 'faraday_middleware/response/mashify'
2
+
3
+ module FaradayMiddleware
4
+ class Rashify < Mashify
5
+ dependency 'rash'
6
+
7
+ self.mash_class = ::Hashie::Rash
8
+ end
9
+ end
@@ -0,0 +1,78 @@
1
+ require 'faraday'
2
+
3
+ module FaradayMiddleware
4
+ # Internal: The base class for middleware that parses responses.
5
+ class ResponseMiddleware < Faraday::Middleware
6
+ CONTENT_TYPE = 'Content-Type'.freeze
7
+
8
+ class << self
9
+ attr_accessor :parser
10
+ end
11
+
12
+ # Store a Proc that receives the body and returns the parsed result.
13
+ def self.define_parser(parser = nil)
14
+ @parser = parser || Proc.new
15
+ end
16
+
17
+ def self.inherited(subclass)
18
+ super
19
+ subclass.load_error = self.load_error
20
+ subclass.parser = self.parser
21
+ end
22
+
23
+ def initialize(app = nil, options = {})
24
+ super(app)
25
+ @options = options
26
+ @content_types = Array(options[:content_type])
27
+ end
28
+
29
+ def call(env)
30
+ @app.call(env).on_complete do
31
+ if process_response_type?(response_type(env)) and parse_response?(env)
32
+ process_response(env)
33
+ end
34
+ end
35
+ end
36
+
37
+ def process_response(env)
38
+ env[:raw_body] = env[:body] if preserve_raw?(env)
39
+ env[:body] = parse(env[:body])
40
+ end
41
+
42
+ # Parse the response body.
43
+ #
44
+ # Instead of overriding this method, consider using `define_parser`.
45
+ def parse(body)
46
+ if self.class.parser
47
+ begin
48
+ self.class.parser.call(body)
49
+ rescue StandardError, SyntaxError => err
50
+ raise err if err.is_a? SyntaxError and err.class.name != 'Psych::SyntaxError'
51
+ raise Faraday::Error::ParsingError, err
52
+ end
53
+ else
54
+ body
55
+ end
56
+ end
57
+
58
+ def response_type(env)
59
+ type = env[:response_headers][CONTENT_TYPE].to_s
60
+ type = type.split(';', 2).first if type.index(';')
61
+ type
62
+ end
63
+
64
+ def process_response_type?(type)
65
+ @content_types.empty? or @content_types.any? { |pattern|
66
+ pattern.is_a?(Regexp) ? type =~ pattern : type == pattern
67
+ }
68
+ end
69
+
70
+ def parse_response?(env)
71
+ env[:body].respond_to? :to_str
72
+ end
73
+
74
+ def preserve_raw?(env)
75
+ env[:request].fetch(:preserve_raw, @options[:preserve_raw])
76
+ end
77
+ end
78
+ end
@@ -1,3 +1,3 @@
1
1
  module FaradayMiddleware
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -0,0 +1,122 @@
1
+ require 'test/unit'
2
+ require 'forwardable'
3
+ require 'fileutils'
4
+ require 'rack/cache'
5
+ require 'faraday_middleware/response/caching'
6
+ require 'faraday_middleware/rack_compatible'
7
+
8
+ class CachingTest < Test::Unit::TestCase
9
+ class TestCache < Hash
10
+ def read(key)
11
+ if cached = self[key]
12
+ Marshal.load(cached)
13
+ end
14
+ end
15
+
16
+ def write(key, data)
17
+ self[key] = Marshal.dump(data)
18
+ end
19
+
20
+ def fetch(key)
21
+ read(key) || yield.tap { |data| write(key, data) }
22
+ end
23
+ end
24
+
25
+ def setup
26
+ @cache = TestCache.new
27
+
28
+ request_count = 0
29
+ response = lambda { |env|
30
+ [200, {'Content-Type' => 'text/plain'}, "request:#{request_count+=1}"]
31
+ }
32
+
33
+ @conn = Faraday.new do |b|
34
+ b.use FaradayMiddleware::Caching, @cache
35
+ b.adapter :test do |stub|
36
+ stub.get('/', &response)
37
+ stub.get('/?foo=bar', &response)
38
+ stub.post('/', &response)
39
+ stub.get('/other', &response)
40
+ end
41
+ end
42
+ end
43
+
44
+ extend Forwardable
45
+ def_delegators :@conn, :get, :post
46
+
47
+ def test_cache_get
48
+ assert_equal 'request:1', get('/').body
49
+ assert_equal 'request:1', get('/').body
50
+ assert_equal 'request:2', get('/other').body
51
+ assert_equal 'request:2', get('/other').body
52
+ end
53
+
54
+ def test_response_has_request_params
55
+ get('/') # make cache
56
+ response = get('/')
57
+ assert_equal :get, response.env[:method]
58
+ assert_equal '/', response.env[:url].to_s
59
+ end
60
+
61
+ def test_cache_query_params
62
+ assert_equal 'request:1', get('/').body
63
+ assert_equal 'request:2', get('/?foo=bar').body
64
+ assert_equal 'request:2', get('/?foo=bar').body
65
+ assert_equal 'request:1', get('/').body
66
+ end
67
+
68
+ def test_doesnt_cache_post
69
+ assert_equal 'request:1', post('/').body
70
+ assert_equal 'request:2', post('/').body
71
+ assert_equal 'request:3', post('/').body
72
+ end
73
+ end
74
+
75
+ # RackCompatible + Rack::Cache
76
+ class HttpCachingTest < Test::Unit::TestCase
77
+ include FileUtils
78
+
79
+ CACHE_DIR = File.expand_path('../cache', __FILE__)
80
+
81
+ def setup
82
+ rm_r CACHE_DIR if File.exists? CACHE_DIR
83
+
84
+ request_count = 0
85
+ response = lambda { |env|
86
+ [200, { 'Content-Type' => 'text/plain',
87
+ 'Cache-Control' => 'public, max-age=900',
88
+ },
89
+ "request:#{request_count+=1}"]
90
+ }
91
+
92
+ @conn = Faraday.new do |b|
93
+ b.use FaradayMiddleware::RackCompatible, Rack::Cache::Context,
94
+ :metastore => "file:#{CACHE_DIR}/rack/meta",
95
+ :entitystore => "file:#{CACHE_DIR}/rack/body",
96
+ :verbose => true
97
+
98
+ b.adapter :test do |stub|
99
+ stub.get('/', &response)
100
+ stub.post('/', &response)
101
+ end
102
+ end
103
+ end
104
+
105
+ extend Forwardable
106
+ def_delegators :@conn, :get, :post
107
+
108
+ def test_cache_get
109
+ assert_equal 'request:1', get('/', :user_agent => 'test').body
110
+ response = get('/', :user_agent => 'test')
111
+ assert_equal 'request:1', response.body
112
+ assert_equal 'text/plain', response['content-type']
113
+
114
+ assert_equal 'request:2', post('/').body
115
+ end
116
+
117
+ def test_doesnt_cache_post
118
+ assert_equal 'request:1', get('/').body
119
+ assert_equal 'request:2', post('/').body
120
+ assert_equal 'request:3', post('/').body
121
+ end
122
+ end unless defined? RUBY_ENGINE and "rbx" == RUBY_ENGINE # rbx bug #1522