faraday-stack 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Über Stack
2
+
3
+ [Faraday][] is an HTTP client lib that provides a common interface over many adapters (such as Net::HTTP) and embraces the concept of Rack middleware when processing the request/response cycle.
4
+
5
+ *“Faraday Stack”* is an add-on library that implements several middleware (such as JSON and XML parsers) and helps you build an awesome stack that covers most of your API-consuming needs.
6
+
7
+ Boring example:
8
+
9
+ require 'faraday_stack'
10
+
11
+ response = FaradayStack.get 'http://google.com'
12
+
13
+ response.headers['content-type'] #=> "text/html; charset=UTF-8"
14
+ response.headers['location'] #=> "http://www.google.com/"
15
+ puts response.body
16
+
17
+ Awesome example:
18
+
19
+ conn = FaradayStack.build 'http://github.com/api/v2'
20
+
21
+ # JSON resource
22
+ resp = conn.get 'json/repos/show/mislav/faraday-stack'
23
+ resp.body
24
+ #=> {"repository"=>{"language"=>"Ruby", "fork"=>false, ...}}
25
+
26
+ # XML resource
27
+ resp = conn.get 'xml/repos/show/mislav/faraday-stack'
28
+ resp.body.class
29
+ #=> Nokogiri::XML::Document
30
+
31
+ # 404
32
+ conn.get 'zomg/wrong/url'
33
+ #=> raises Faraday::Error::ResourceNotFound
34
+
35
+ ## Features
36
+
37
+ * parses JSON, XML & HTML
38
+ * raises exceptions on 4xx, 5xx responses
39
+ * follows redirects
40
+
41
+ Optional features:
42
+
43
+ * encode POST/PUT bodies as JSON:
44
+
45
+ conn.post(path, payload, :content_type => 'application/json')
46
+
47
+ * add `Instrumentation` middleware to instrument requests with ActiveSupport
48
+
49
+ conn.builder.insert_after Faraday::Response::RaiseError, FaradayStack::Instrumentation
50
+
51
+ * add `Caching` middleware to have GET responses cached
52
+
53
+ conn.builder.insert_before FaradayStack::ResponseJSON, FaradayStack::Caching do
54
+ ActiveSupport::Cache::FileStore.new 'tmp/cache',
55
+ :namespace => 'faraday', :expires_in => 3600
56
+ end
57
+
58
+ To see how the default stack is built, see "[faraday_stack.rb][source]".
59
+
60
+ [faraday]: https://github.com/technoweenie/faraday
61
+ [source]: https://github.com/mislav/faraday-stack/blob/master/lib/faraday_stack.rb
@@ -0,0 +1 @@
1
+ require 'faraday_stack'
@@ -0,0 +1,18 @@
1
+ require 'addressable/uri'
2
+
3
+ # fix `normalized_query` by sorting query key-value pairs
4
+ # (rejected: https://github.com/sporkmonger/addressable/issues/28)
5
+ class Addressable::URI
6
+ class << self
7
+ alias old_normalize_component normalize_component
8
+
9
+ def normalize_component(component, character_class = CharacterClasses::RESERVED + CharacterClasses::UNRESERVED)
10
+ normalized = old_normalize_component(component, character_class)
11
+ if character_class == Addressable::URI::CharacterClasses::QUERY
12
+ pairs = normalized.split('&').sort_by { |pair| pair[0, pair.index('=') || pair.length] }
13
+ normalized = pairs.join('&')
14
+ end
15
+ normalized
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,58 @@
1
+ module FaradayStack
2
+ class Caching < Faraday::Middleware
3
+ attr_reader :cache
4
+
5
+ def initialize(app, cache = nil, options = {})
6
+ super(app)
7
+ @cache = cache || Proc.new.call
8
+ @options = options
9
+ end
10
+
11
+ def call(env)
12
+ if :get == env[:method]
13
+ if env[:parallel_manager]
14
+ # callback mode
15
+ cache_on_complete(env)
16
+ else
17
+ # synchronous mode
18
+ response = cache.fetch(cache_key(env)) { @app.call(env) }
19
+ finalize_response(response, env)
20
+ end
21
+ else
22
+ @app.call(env)
23
+ end
24
+ end
25
+
26
+ def cache_key(env)
27
+ url = env[:url]
28
+ if params_to_strip.any?
29
+ url = url.dup
30
+ url.query_values = url.query_values.reject { |k,| params_to_strip.include? k }
31
+ url.normalize!
32
+ else
33
+ url = url.normalize
34
+ end
35
+ url.request_uri
36
+ end
37
+
38
+ def params_to_strip
39
+ @params_to_strip ||= Array(@options[:strip_params]).map { |p| p.to_s }
40
+ end
41
+
42
+ def cache_on_complete(env)
43
+ key = cache_key(env)
44
+ if cached_response = cache.read(key)
45
+ finalize_response(cached_response, env)
46
+ else
47
+ response = @app.call(env)
48
+ response.on_complete { cache.write(key, response) }
49
+ end
50
+ end
51
+
52
+ def finalize_response(response, env)
53
+ response = env[:response] = response.dup if response.frozen?
54
+ response.apply_request env unless response.env[:method]
55
+ response
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,43 @@
1
+ module FaradayStack
2
+ class RedirectLimitReached < Faraday::Error::ClientError
3
+ attr_reader :response
4
+
5
+ def initialize(response)
6
+ super "too many redirects; last one to: #{response['location']}"
7
+ @response = response
8
+ end
9
+ end
10
+
11
+ class FollowRedirects < Faraday::Middleware
12
+ # TODO: 307
13
+ REDIRECTS = [301, 302, 303]
14
+ # default value for max redirects followed
15
+ FOLLOW_LIMIT = 3
16
+
17
+ def initialize(app, options = {})
18
+ super(app)
19
+ @options = options
20
+ @follow_limit = options[:limit] || FOLLOW_LIMIT
21
+ end
22
+
23
+ def call(env)
24
+ process_response(@app.call(env), @follow_limit)
25
+ end
26
+
27
+ def process_response(response, follows)
28
+ response.on_complete do |env|
29
+ if redirect? response
30
+ raise RedirectLimitReached, response if follows.zero?
31
+ env[:url] += response['location']
32
+ env[:method] = :get
33
+ response = process_response(@app.call(env), follows - 1)
34
+ end
35
+ end
36
+ response
37
+ end
38
+
39
+ def redirect?(response)
40
+ REDIRECTS.include? response.status
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,26 @@
1
+ module FaradayStack
2
+ # Measures request time only in synchronous request mode.
3
+ # Sample subscriber:
4
+ #
5
+ # ActiveSupport::Notifications.subscribe('request.faraday') do |name, start_time, end_time, _, env|
6
+ # url = env[:url]
7
+ # http_method = env[:method].to_s.upcase
8
+ # duration = end_time - start_time
9
+ # $stderr.puts '[%s] %s %s (%.3f s)' % [url.host, http_method, url.request_uri, duration]
10
+ # end
11
+ class Instrumentation < Faraday::Middleware
12
+ dependency 'active_support/notifications'
13
+
14
+ def initialize(app, options = {})
15
+ super(app)
16
+ @options = options
17
+ @name = options[:name] || 'request.faraday'
18
+ end
19
+
20
+ def call(env)
21
+ ActiveSupport::Notifications.instrument(@name, env) do
22
+ @app.call(env)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,76 @@
1
+ require 'stringio'
2
+
3
+ module FaradayStack
4
+ # Wraps a handler originally written for Rack to make it compatible with Faraday.
5
+ #
6
+ # Experimental. Only handles changes in request headers.
7
+ class RackCompatible
8
+ def initialize(app, rack_handler, *args)
9
+ # tiny middleware that decomposes a Faraday::Response to standard Rack
10
+ # array: [status, headers, body]
11
+ compatible_app = lambda do |env|
12
+ restore_env(env)
13
+ response = app.call(env)
14
+ [response.status, response.headers, Array(response.body)]
15
+ end
16
+ @rack = rack_handler.new(compatible_app, *args)
17
+ end
18
+
19
+ def call(env)
20
+ prepare_env(env)
21
+ rack_response = @rack.call(env)
22
+ finalize_response(env, rack_response)
23
+ end
24
+
25
+ NonPrefixedHeaders = %w[CONTENT_LENGTH CONTENT_TYPE]
26
+
27
+ # faraday to rack-compatible
28
+ def prepare_env(env)
29
+ env[:request_headers].each do |name, value|
30
+ name = name.upcase.tr('-', '_')
31
+ name = "HTTP_#{name}" unless NonPrefixedHeaders.include? name
32
+ env[name] = value
33
+ end
34
+
35
+ url = env[:url]
36
+ env['rack.url_scheme'] = url.scheme
37
+ env['PATH_INFO'] = url.path
38
+ env['SERVER_PORT'] = url.inferred_port
39
+ env['QUERY_STRING'] = url.query
40
+ env['REQUEST_METHOD'] = env[:method].to_s.upcase
41
+
42
+ env['rack.errors'] ||= StringIO.new
43
+
44
+ env
45
+ end
46
+
47
+ # rack to faraday-compatible
48
+ def restore_env(env)
49
+ headers = env[:request_headers]
50
+ headers.clear
51
+
52
+ env.each do |name, value|
53
+ next unless String === name
54
+ if NonPrefixedHeaders.include? name or name.index('HTTP_') == 0
55
+ name = name.sub(/^HTTP_/).downcase.tr('_', '-')
56
+ headers[name] = value
57
+ end
58
+ end
59
+
60
+ env[:method] = env['REQUEST_METHOD'].downcase.to_sym
61
+ env
62
+ end
63
+
64
+ def finalize_response(env, rack_response)
65
+ status, headers, body = rack_response
66
+ body = body.inject('') { |str, part| str << part }
67
+ headers = Faraday::Utils::Headers.new(headers) unless Faraday::Utils::Headers === headers
68
+
69
+ response_env = { :status => status, :body => body, :response_headers => headers }
70
+
71
+ env[:response] ||= Faraday::Response.new({})
72
+ env[:response].env.update(response_env)
73
+ env[:response]
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,12 @@
1
+ module FaradayStack
2
+ class ResponseHTML < ResponseMiddleware
3
+ dependency do
4
+ require 'nokogiri'
5
+ Nokogiri::HTML
6
+ end
7
+
8
+ define_parser do |body|
9
+ Nokogiri::HTML body
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ module FaradayStack
2
+ class ResponseJSON < ResponseMiddleware
3
+ adapter_name = nil
4
+
5
+ # loads the JSON decoder either from yajl-ruby or activesupport
6
+ dependency do
7
+ require 'yajl'
8
+ adapter_name = Yajl::Parser.name
9
+ end
10
+
11
+ dependency do
12
+ require 'active_support/json/decoding'
13
+ adapter_name = ActiveSupport::JSON.name
14
+ end unless loaded?
15
+
16
+ # defines a parser block depending on which adapter has loaded
17
+ case adapter_name
18
+ when 'Yajl::Parser'
19
+ define_parser do |body|
20
+ Yajl::Parser.parse(body)
21
+ end
22
+ when 'ActiveSupport::JSON'
23
+ define_parser do |body|
24
+ unless body.nil? or body.empty?
25
+ result = ActiveSupport::JSON.decode(body)
26
+ raise ActiveSupport::JSON.backend::ParseError if String === result
27
+ result
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,64 @@
1
+ module FaradayStack
2
+ # A base class for middleware that parses responses
3
+ class ResponseMiddleware < Faraday::Response::Middleware
4
+ CONTENT_TYPE = 'Content-Type'.freeze
5
+
6
+ class << self
7
+ attr_accessor :parser
8
+ end
9
+
10
+ # Stores a block that receives the body and should return a parsed result
11
+ def self.define_parser(&block)
12
+ @parser = block
13
+ end
14
+
15
+ def self.inherited(subclass)
16
+ super
17
+ subclass.load_error = self.load_error
18
+ subclass.parser = self.parser
19
+ end
20
+
21
+ def initialize(app = nil, options = {})
22
+ super(app)
23
+ @options = options
24
+ @content_types = Array(options[:content_type])
25
+ end
26
+
27
+ # Override this to modify the environment after the response has finished.
28
+ def on_complete(env)
29
+ if process_response_type?(response_type(env)) and parse_response?(env)
30
+ env[:body] = parse(env[:body])
31
+ end
32
+ end
33
+
34
+ # Parses the response body and returns the result.
35
+ # Instead of overriding this method, consider using `define_parser`
36
+ def parse(body)
37
+ if self.class.parser
38
+ begin
39
+ self.class.parser.call(body)
40
+ rescue
41
+ raise Faraday::Error::ParsingError, $!
42
+ end
43
+ else
44
+ body
45
+ end
46
+ end
47
+
48
+ def response_type(env)
49
+ type = env[:response_headers][CONTENT_TYPE].to_s
50
+ type = type.split(';', 2).first if type.index(';')
51
+ type
52
+ end
53
+
54
+ def process_response_type?(type)
55
+ @content_types.empty? or @content_types.any? { |pattern|
56
+ Regexp === pattern ? type =~ pattern : type == pattern
57
+ }
58
+ end
59
+
60
+ def parse_response?(env)
61
+ env[:body].respond_to? :to_str
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,12 @@
1
+ module FaradayStack
2
+ class ResponseXML < ResponseMiddleware
3
+ dependency do
4
+ require 'nokogiri'
5
+ Nokogiri::XML
6
+ end
7
+
8
+ define_parser do |body|
9
+ Nokogiri::XML body
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+ require 'faraday'
3
+ require 'forwardable'
4
+ require 'faraday_stack/addressable_patch'
5
+
6
+ module FaradayStack
7
+ extend Faraday::AutoloadHelper
8
+
9
+ autoload_all 'faraday_stack',
10
+ :ResponseMiddleware => 'response_middleware',
11
+ :ResponseJSON => 'response_json',
12
+ :ResponseXML => 'response_xml',
13
+ :ResponseHTML => 'response_html',
14
+ :Instrumentation => 'instrumentation',
15
+ :Caching => 'caching',
16
+ :FollowRedirects => 'follow_redirects',
17
+ :RackCompatible => 'rack_compatible'
18
+
19
+ # THE ÜBER STACK
20
+ def self.default_connection
21
+ @default_connection ||= self.build
22
+ end
23
+
24
+ class << self
25
+ extend Forwardable
26
+ attr_writer :default_connection
27
+ def_delegators :default_connection, :get, :post, :put, :head, :delete
28
+ end
29
+
30
+ def self.build(url = nil, options = {})
31
+ Faraday::Connection.new(url, options) do |builder|
32
+ builder.request :url_encoded
33
+ builder.request :json
34
+ yield builder if block_given?
35
+ builder.use ResponseXML, :content_type => /[+\/]xml$/
36
+ builder.use ResponseHTML, :content_type => 'text/html'
37
+ builder.use ResponseJSON, :content_type => 'application/json'
38
+ builder.use FollowRedirects
39
+ builder.response :raise_error
40
+ builder.adapter Faraday.default_adapter
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,120 @@
1
+ require 'test_helper'
2
+ require 'forwardable'
3
+ require 'fileutils'
4
+ require 'rack/cache'
5
+
6
+ class CachingTest < Test::Unit::TestCase
7
+ class TestCache < Hash
8
+ def read(key)
9
+ if cached = self[key]
10
+ Marshal.load(cached)
11
+ end
12
+ end
13
+
14
+ def write(key, data)
15
+ self[key] = Marshal.dump(data)
16
+ end
17
+
18
+ def fetch(key)
19
+ read(key) || yield.tap { |data| write(key, data) }
20
+ end
21
+ end
22
+
23
+ def setup
24
+ @cache = TestCache.new
25
+
26
+ request_count = 0
27
+ response = lambda { |env|
28
+ [200, {'Content-Type' => 'text/plain'}, "request:#{request_count+=1}"]
29
+ }
30
+
31
+ @conn = Faraday.new do |b|
32
+ b.use FaradayStack::Caching, @cache
33
+ b.adapter :test do |stub|
34
+ stub.get('/', &response)
35
+ stub.get('/?foo=bar', &response)
36
+ stub.post('/', &response)
37
+ stub.get('/other', &response)
38
+ end
39
+ end
40
+ end
41
+
42
+ extend Forwardable
43
+ def_delegators :@conn, :get, :post
44
+
45
+ def test_cache_get
46
+ assert_equal 'request:1', get('/').body
47
+ assert_equal 'request:1', get('/').body
48
+ assert_equal 'request:2', get('/other').body
49
+ assert_equal 'request:2', get('/other').body
50
+ end
51
+
52
+ def test_response_has_request_params
53
+ get('/') # make cache
54
+ response = get('/')
55
+ assert_equal :get, response.env[:method]
56
+ assert_equal '/', response.env[:url].to_s
57
+ end
58
+
59
+ def test_cache_query_params
60
+ assert_equal 'request:1', get('/').body
61
+ assert_equal 'request:2', get('/?foo=bar').body
62
+ assert_equal 'request:2', get('/?foo=bar').body
63
+ assert_equal 'request:1', get('/').body
64
+ end
65
+
66
+ def test_doesnt_cache_post
67
+ assert_equal 'request:1', post('/').body
68
+ assert_equal 'request:2', post('/').body
69
+ assert_equal 'request:3', post('/').body
70
+ end
71
+ end
72
+
73
+ # RackCompatible + Rack::Cache
74
+ class HttpCachingTest < Test::Unit::TestCase
75
+ include FileUtils
76
+
77
+ CACHE_DIR = File.expand_path('../cache', __FILE__)
78
+
79
+ def setup
80
+ rm_r CACHE_DIR if File.exists? CACHE_DIR
81
+
82
+ request_count = 0
83
+ response = lambda { |env|
84
+ [200, { 'Content-Type' => 'text/plain',
85
+ 'Cache-Control' => 'public, max-age=900',
86
+ },
87
+ "request:#{request_count+=1}"]
88
+ }
89
+
90
+ @conn = Faraday.new do |b|
91
+ b.use FaradayStack::RackCompatible, Rack::Cache::Context,
92
+ :metastore => "file:#{CACHE_DIR}/rack/meta",
93
+ :entitystore => "file:#{CACHE_DIR}/rack/body",
94
+ :verbose => true
95
+
96
+ b.adapter :test do |stub|
97
+ stub.get('/', &response)
98
+ stub.post('/', &response)
99
+ end
100
+ end
101
+ end
102
+
103
+ extend Forwardable
104
+ def_delegators :@conn, :get, :post
105
+
106
+ def test_cache_get
107
+ assert_equal 'request:1', get('/').body
108
+ response = get('/')
109
+ assert_equal 'request:1', response.body
110
+ assert_equal 'text/plain', response['content-type']
111
+
112
+ assert_equal 'request:2', post('/').body
113
+ end
114
+
115
+ def test_doesnt_cache_post
116
+ assert_equal 'request:1', get('/').body
117
+ assert_equal 'request:2', post('/').body
118
+ assert_equal 'request:3', post('/').body
119
+ end
120
+ end
@@ -0,0 +1,33 @@
1
+ require 'test_helper'
2
+ require 'forwardable'
3
+
4
+ class FollowRedirectsTest < Test::Unit::TestCase
5
+ def setup
6
+ @conn = Faraday.new do |b|
7
+ b.use FaradayStack::FollowRedirects
8
+ b.adapter :test do |stub|
9
+ stub.get('/') { [301, {'Location' => '/found'}, ''] }
10
+ stub.post('/create') { [302, {'Location' => '/'}, ''] }
11
+ stub.get('/found') { [200, {'Content-Type' => 'text/plain'}, 'fin'] }
12
+ stub.get('/loop') { [302, {'Location' => '/loop'}, ''] }
13
+ end
14
+ end
15
+ end
16
+
17
+ extend Forwardable
18
+ def_delegators :@conn, :get, :post
19
+
20
+ def test_redirected
21
+ assert_equal 'fin', get('/').body
22
+ end
23
+
24
+ def test_redirected_twice
25
+ assert_equal 'fin', post('/create').body
26
+ end
27
+
28
+ def test_redirect_limit
29
+ assert_raises FaradayStack::RedirectLimitReached do
30
+ get('/loop')
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,72 @@
1
+ require 'test_helper'
2
+
3
+ class ResponseMiddlewareTest < Test::Unit::TestCase
4
+ def setup
5
+ @json_handler = FaradayStack::ResponseJSON
6
+ @conn = Faraday.new do |b|
7
+ b.use @json_handler
8
+ b.adapter :test do |stub|
9
+ stub.get('json') { [200, {'Content-Type' => 'application/json; charset=utf-8'}, "[1,2,3]"] }
10
+ stub.get('blank') { [200, {'Content-Type' => 'application/json'}, ''] }
11
+ stub.get('nil') { [200, {'Content-Type' => 'application/json'}, nil] }
12
+ stub.get('bad_json') { [200, {'Content-Type' => 'application/json'}, '<body></body>']}
13
+ stub.get('non_json') { [200, {'Content-Type' => 'text/html'}, '<body></body>']}
14
+ end
15
+ end
16
+ end
17
+
18
+ def process_only(*types)
19
+ @conn.builder.swap @json_handler, @json_handler, :content_type => types
20
+ end
21
+
22
+ def test_uses_json_to_parse_json_content
23
+ response = @conn.get('json')
24
+ assert response.success?
25
+ assert_equal [1,2,3], response.body
26
+ end
27
+
28
+ def test_uses_json_to_parse_json_content_conditional
29
+ process_only('application/json')
30
+ response = @conn.get('json')
31
+ assert response.success?
32
+ assert_equal [1,2,3], response.body
33
+ end
34
+
35
+ def test_uses_json_to_parse_json_content_conditional_with_regexp
36
+ process_only(%r{/(x-)?json$})
37
+ response = @conn.get('json')
38
+ assert response.success?
39
+ assert_equal [1,2,3], response.body
40
+ end
41
+
42
+ def test_uses_json_to_skip_blank_content
43
+ response = @conn.get('blank')
44
+ assert response.success?
45
+ assert_nil response.body
46
+ end
47
+
48
+ def test_uses_json_to_skip_nil_content
49
+ response = @conn.get('nil')
50
+ assert response.success?
51
+ assert_nil response.body
52
+ end
53
+
54
+ def test_uses_json_to_raise_Faraday_Error_Parsing_with_no_json_content
55
+ assert_raises Faraday::Error::ParsingError do
56
+ @conn.get('bad_json')
57
+ end
58
+ end
59
+
60
+ def test_non_json_response
61
+ assert_raises Faraday::Error::ParsingError do
62
+ @conn.get('non_json')
63
+ end
64
+ end
65
+
66
+ def test_non_json_response_conditional
67
+ process_only('application/json')
68
+ response = @conn.get('non_json')
69
+ assert_equal 'text/html', response.headers['Content-Type']
70
+ assert_equal '<body></body>', response.body
71
+ end
72
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require 'faraday_stack'
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: faraday-stack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mislav Marohnić
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-04-01 00:00:00.000000000 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: faraday
17
+ requirement: &2164745380 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '0.6'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2164745380
26
+ description:
27
+ email: mislav.marohnic@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - lib/faraday-stack.rb
33
+ - lib/faraday_stack/addressable_patch.rb
34
+ - lib/faraday_stack/caching.rb
35
+ - lib/faraday_stack/follow_redirects.rb
36
+ - lib/faraday_stack/instrumentation.rb
37
+ - lib/faraday_stack/rack_compatible.rb
38
+ - lib/faraday_stack/response_html.rb
39
+ - lib/faraday_stack/response_json.rb
40
+ - lib/faraday_stack/response_middleware.rb
41
+ - lib/faraday_stack/response_xml.rb
42
+ - lib/faraday_stack.rb
43
+ - test/caching_test.rb
44
+ - test/follow_redirects_test.rb
45
+ - test/response_middleware_test.rb
46
+ - test/test_helper.rb
47
+ - README.md
48
+ has_rdoc: false
49
+ homepage: https://github.com/mislav/faraday-stack
50
+ licenses: []
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 1.5.3
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Great Faraday stack for consuming all kinds of APIs
73
+ test_files: []