agiley-faraday_middleware 0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/.gitignore +31 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +7 -0
- data/LICENSE.md +20 -0
- data/README.md +54 -0
- data/Rakefile +17 -0
- data/faraday_middleware.gemspec +24 -0
- data/lib/faraday_middleware.rb +42 -0
- data/lib/faraday_middleware/addressable_patch.rb +20 -0
- data/lib/faraday_middleware/backwards_compatibility.rb +15 -0
- data/lib/faraday_middleware/instrumentation.rb +30 -0
- data/lib/faraday_middleware/rack_compatible.rb +76 -0
- data/lib/faraday_middleware/request/encode_json.rb +50 -0
- data/lib/faraday_middleware/request/oauth.rb +64 -0
- data/lib/faraday_middleware/request/oauth2.rb +62 -0
- data/lib/faraday_middleware/response/caching.rb +76 -0
- data/lib/faraday_middleware/response/follow_redirects.rb +53 -0
- data/lib/faraday_middleware/response/mashify.rb +28 -0
- data/lib/faraday_middleware/response/parse_json.rb +38 -0
- data/lib/faraday_middleware/response/parse_marshal.rb +13 -0
- data/lib/faraday_middleware/response/parse_nokogiri_xml.rb +14 -0
- data/lib/faraday_middleware/response/parse_xml.rb +14 -0
- data/lib/faraday_middleware/response/parse_yaml.rb +13 -0
- data/lib/faraday_middleware/response/rashify.rb +13 -0
- data/lib/faraday_middleware/response_middleware.rb +78 -0
- data/lib/faraday_middleware/version.rb +3 -0
- data/spec/caching_test.rb +122 -0
- data/spec/encode_json_spec.rb +95 -0
- data/spec/follow_redirects_spec.rb +33 -0
- data/spec/helper.rb +33 -0
- data/spec/mashify_spec.rb +79 -0
- data/spec/oauth2_spec.rb +118 -0
- data/spec/oauth_spec.rb +101 -0
- data/spec/parse_json_spec.rb +94 -0
- data/spec/parse_marshal_spec.rb +16 -0
- data/spec/parse_xml_spec.rb +71 -0
- data/spec/parse_yaml_spec.rb +53 -0
- data/spec/rashify_spec.rb +69 -0
- metadata +202 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'faraday_middleware/request/encode_json'
|
3
|
+
|
4
|
+
describe FaradayMiddleware::EncodeJson do
|
5
|
+
let(:middleware) { described_class.new(lambda{|env| env}) }
|
6
|
+
|
7
|
+
def process(body, content_type = nil)
|
8
|
+
env = {:body => body, :request_headers => Faraday::Utils::Headers.new}
|
9
|
+
env[:request_headers]['content-type'] = content_type if content_type
|
10
|
+
middleware.call(env)
|
11
|
+
end
|
12
|
+
|
13
|
+
def result_body() result[:body] end
|
14
|
+
def result_type() result[:request_headers]['content-type'] end
|
15
|
+
|
16
|
+
context "no body" do
|
17
|
+
let(:result) { process(nil) }
|
18
|
+
|
19
|
+
it "doesn't change body" do
|
20
|
+
result_body.should be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "doesn't add content type" do
|
24
|
+
result_type.should be_nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "empty body" do
|
29
|
+
let(:result) { process('') }
|
30
|
+
|
31
|
+
it "doesn't change body" do
|
32
|
+
result_body.should be_empty
|
33
|
+
end
|
34
|
+
|
35
|
+
it "doesn't add content type" do
|
36
|
+
result_type.should be_nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "string body" do
|
41
|
+
let(:result) { process('{"a":1}') }
|
42
|
+
|
43
|
+
it "doesn't change body" do
|
44
|
+
result_body.should eql('{"a":1}')
|
45
|
+
end
|
46
|
+
|
47
|
+
it "adds content type" do
|
48
|
+
result_type.should eql('application/json')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "object body" do
|
53
|
+
let(:result) { process({:a => 1}) }
|
54
|
+
|
55
|
+
it "encodes body" do
|
56
|
+
result_body.should eql('{"a":1}')
|
57
|
+
end
|
58
|
+
|
59
|
+
it "adds content type" do
|
60
|
+
result_type.should eql('application/json')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "empty object body" do
|
65
|
+
let(:result) { process({}) }
|
66
|
+
|
67
|
+
it "encodes body" do
|
68
|
+
result_body.should eql('{}')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "object body with json type" do
|
73
|
+
let(:result) { process({:a => 1}, 'application/json; charset=utf-8') }
|
74
|
+
|
75
|
+
it "encodes body" do
|
76
|
+
result_body.should eql('{"a":1}')
|
77
|
+
end
|
78
|
+
|
79
|
+
it "doesn't change content type" do
|
80
|
+
result_type.should eql('application/json; charset=utf-8')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "object body with incompatible type" do
|
85
|
+
let(:result) { process({:a => 1}, 'application/xml; charset=utf-8') }
|
86
|
+
|
87
|
+
it "doesn't change body" do
|
88
|
+
result_body.should eql({:a => 1})
|
89
|
+
end
|
90
|
+
|
91
|
+
it "doesn't change content type" do
|
92
|
+
result_type.should eql('application/xml; charset=utf-8')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'faraday_middleware/response/follow_redirects'
|
3
|
+
require 'faraday'
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
describe FaradayMiddleware::FollowRedirects do
|
7
|
+
let(:connection) {
|
8
|
+
Faraday.new do |c|
|
9
|
+
c.use described_class
|
10
|
+
c.adapter :test do |stub|
|
11
|
+
stub.get('/') { [301, {'Location' => '/found'}, ''] }
|
12
|
+
stub.post('/create') { [302, {'Location' => '/'}, ''] }
|
13
|
+
stub.get('/found') { [200, {'Content-Type' => 'text/plain'}, 'fin'] }
|
14
|
+
stub.get('/loop') { [302, {'Location' => '/loop'}, ''] }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
}
|
18
|
+
|
19
|
+
extend Forwardable
|
20
|
+
def_delegators :connection, :get, :post
|
21
|
+
|
22
|
+
it "follows redirect" do
|
23
|
+
get('/').body.should eql('fin')
|
24
|
+
end
|
25
|
+
|
26
|
+
it "follows redirect twice" do
|
27
|
+
post('/create').body.should eql('fin')
|
28
|
+
end
|
29
|
+
|
30
|
+
it "raises exception on loop" do
|
31
|
+
expect { get('/loop') }.to raise_error(FaradayMiddleware::RedirectLimitReached)
|
32
|
+
end
|
33
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
if ENV['COVERAGE']
|
2
|
+
require 'simplecov'
|
3
|
+
SimpleCov.start do
|
4
|
+
# add_filter 'faraday_middleware.rb'
|
5
|
+
add_filter 'backwards_compatibility.rb'
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rspec'
|
10
|
+
|
11
|
+
module ResponseMiddlewareExampleGroup
|
12
|
+
def self.included(base)
|
13
|
+
base.let(:options) { Hash.new }
|
14
|
+
base.let(:middleware) {
|
15
|
+
described_class.new(lambda {|env|
|
16
|
+
Faraday::Response.new(env)
|
17
|
+
}, options)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def process(body, content_type = nil, options = {})
|
22
|
+
env = {
|
23
|
+
:body => body, :request => options,
|
24
|
+
:response_headers => Faraday::Utils::Headers.new
|
25
|
+
}
|
26
|
+
env[:response_headers]['content-type'] = content_type if content_type
|
27
|
+
middleware.call(env)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
RSpec.configure do |config|
|
32
|
+
config.include ResponseMiddlewareExampleGroup, :type => :response
|
33
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'faraday_middleware/response/mashify'
|
3
|
+
|
4
|
+
describe FaradayMiddleware::Mashify do
|
5
|
+
context 'during configuration' do
|
6
|
+
it 'should allow for a custom Mash class to be set' do
|
7
|
+
described_class.should respond_to(:mash_class)
|
8
|
+
described_class.should respond_to(:mash_class=)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'when used' do
|
13
|
+
before(:each) { described_class.mash_class = ::Hashie::Mash }
|
14
|
+
let(:mashify) { described_class.new }
|
15
|
+
|
16
|
+
it 'should create a Hashie::Mash from the body' do
|
17
|
+
env = { :body => { "name" => "Erik Michaels-Ober", "username" => "sferik" } }
|
18
|
+
me = mashify.on_complete(env)
|
19
|
+
me.class.should == Hashie::Mash
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should handle strings' do
|
23
|
+
env = { :body => "Most amazing string EVER" }
|
24
|
+
me = mashify.on_complete(env)
|
25
|
+
me.should == "Most amazing string EVER"
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should handle arrays' do
|
29
|
+
env = { :body => [123, 456] }
|
30
|
+
values = mashify.on_complete(env)
|
31
|
+
values.first.should == 123
|
32
|
+
values.last.should == 456
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should handle arrays of hashes' do
|
36
|
+
env = { :body => [{ "username" => "sferik" }, { "username" => "pengwynn" }] }
|
37
|
+
us = mashify.on_complete(env)
|
38
|
+
us.first.username.should == 'sferik'
|
39
|
+
us.last.username.should == 'pengwynn'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should handle mixed arrays' do
|
43
|
+
env = { :body => [123, { "username" => "sferik" }, 456] }
|
44
|
+
values = mashify.on_complete(env)
|
45
|
+
values.first.should == 123
|
46
|
+
values.last.should == 456
|
47
|
+
values[1].username.should == 'sferik'
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should allow for use of custom Mash subclasses' do
|
51
|
+
class MyMash < ::Hashie::Mash; end
|
52
|
+
described_class.mash_class = MyMash
|
53
|
+
|
54
|
+
env = { :body => { "name" => "Erik Michaels-Ober", "username" => "sferik" } }
|
55
|
+
me = mashify.on_complete(env)
|
56
|
+
|
57
|
+
me.class.should == MyMash
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'integration test' do
|
62
|
+
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
|
63
|
+
let(:connection) do
|
64
|
+
Faraday::Connection.new do |builder|
|
65
|
+
builder.adapter :test, stubs
|
66
|
+
builder.use described_class
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# although it is not good practice to pass a hash as the body, if we add ParseJson
|
71
|
+
# to the middleware stack we end up testing two middlewares instead of one
|
72
|
+
it 'should create a Hash from the body' do
|
73
|
+
stubs.get('/hash') {[200, {'content-type' => 'application/json; charset=utf-8'}, { "name" => "Erik Michaels-Ober", "username" => "sferik" }]}
|
74
|
+
me = connection.get('/hash').body
|
75
|
+
me.name.should == 'Erik Michaels-Ober'
|
76
|
+
me.username.should == 'sferik'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/spec/oauth2_spec.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'uri'
|
3
|
+
require 'faraday_middleware/request/oauth2'
|
4
|
+
require 'faraday/utils'
|
5
|
+
|
6
|
+
describe FaradayMiddleware::OAuth2 do
|
7
|
+
|
8
|
+
def query_params(env)
|
9
|
+
Faraday::Utils.parse_query env[:url].query
|
10
|
+
end
|
11
|
+
|
12
|
+
def auth_header(env)
|
13
|
+
env[:request_headers]['Authorization']
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform(params = {}, headers = {})
|
17
|
+
env = {
|
18
|
+
:url => URI('http://example.com/?' + Faraday::Utils.build_query(params)),
|
19
|
+
:request_headers => Faraday::Utils::Headers.new.update(headers)
|
20
|
+
}
|
21
|
+
app = make_app
|
22
|
+
app.call(env)
|
23
|
+
end
|
24
|
+
|
25
|
+
def make_app
|
26
|
+
described_class.new(lambda{|env| env}, *Array(options))
|
27
|
+
end
|
28
|
+
|
29
|
+
context "no token configured" do
|
30
|
+
let(:options) { nil }
|
31
|
+
|
32
|
+
it "doesn't add params" do
|
33
|
+
request = perform(:q => 'hello')
|
34
|
+
query_params(request).should eq('q' => 'hello')
|
35
|
+
end
|
36
|
+
|
37
|
+
it "doesn't add headers" do
|
38
|
+
auth_header(perform).should be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it "creates header for explicit token" do
|
42
|
+
request = perform(:q => 'hello', :access_token => 'abc123')
|
43
|
+
query_params(request).should eq('q' => 'hello', 'access_token' => 'abc123')
|
44
|
+
auth_header(request).should eq(%(Token token="abc123"))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "default token configured" do
|
49
|
+
let(:options) { 'XYZ' }
|
50
|
+
|
51
|
+
it "adds token param" do
|
52
|
+
query_params(perform(:q => 'hello')).should eq('q' => 'hello', 'access_token' => 'XYZ')
|
53
|
+
end
|
54
|
+
|
55
|
+
it "adds token header" do
|
56
|
+
auth_header(perform).should eq(%(Token token="XYZ"))
|
57
|
+
end
|
58
|
+
|
59
|
+
it "overrides default with explicit token" do
|
60
|
+
request = perform(:q => 'hello', :access_token => 'abc123')
|
61
|
+
query_params(request).should eq('q' => 'hello', 'access_token' => 'abc123')
|
62
|
+
auth_header(request).should eq(%(Token token="abc123"))
|
63
|
+
end
|
64
|
+
|
65
|
+
it "clears default with empty explicit token" do
|
66
|
+
request = perform(:q => 'hello', :access_token => nil)
|
67
|
+
query_params(request).should eq('q' => 'hello', 'access_token' => nil)
|
68
|
+
auth_header(request).should be_nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "existing Authorization header" do
|
73
|
+
let(:options) { 'XYZ' }
|
74
|
+
subject { perform({:q => 'hello'}, 'Authorization' => 'custom') }
|
75
|
+
|
76
|
+
it "adds token param" do
|
77
|
+
query_params(subject).should eq('q' => 'hello', 'access_token' => 'XYZ')
|
78
|
+
end
|
79
|
+
|
80
|
+
it "doesn't override existing header" do
|
81
|
+
auth_header(subject).should eq('custom')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "custom param name configured" do
|
86
|
+
let(:options) { ['XYZ', {:param_name => :oauth}] }
|
87
|
+
|
88
|
+
it "adds token param" do
|
89
|
+
query_params(perform).should eq('oauth' => 'XYZ')
|
90
|
+
end
|
91
|
+
|
92
|
+
it "overrides default with explicit token" do
|
93
|
+
request = perform(:oauth => 'abc123')
|
94
|
+
query_params(request).should eq('oauth' => 'abc123')
|
95
|
+
auth_header(request).should eq(%(Token token="abc123"))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "options without token configuration" do
|
100
|
+
let(:options) { [{:param_name => :oauth}] }
|
101
|
+
|
102
|
+
it "doesn't add param" do
|
103
|
+
query_params(perform).should be_empty
|
104
|
+
end
|
105
|
+
|
106
|
+
it "overrides default with explicit token" do
|
107
|
+
query_params(perform(:oauth => 'abc123')).should eq('oauth' => 'abc123')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "invalid param name configured" do
|
112
|
+
let(:options) { ['XYZ', {:param_name => nil}] }
|
113
|
+
|
114
|
+
it "raises error" do
|
115
|
+
expect { make_app }.to raise_error(ArgumentError, ":param_name can't be blank")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/spec/oauth_spec.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'faraday_middleware/request/oauth'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
describe FaradayMiddleware::OAuth do
|
6
|
+
def auth_header(env)
|
7
|
+
env[:request_headers]['Authorization']
|
8
|
+
end
|
9
|
+
|
10
|
+
def auth_values(env)
|
11
|
+
if auth = auth_header(env)
|
12
|
+
raise "invalid header: #{auth.inspect}" unless auth.sub!('OAuth ', '')
|
13
|
+
Hash[*auth.split(/, |=/)]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform(oauth_options = {}, headers = {})
|
18
|
+
env = {
|
19
|
+
:url => URI('http://example.com/'),
|
20
|
+
:request_headers => Faraday::Utils::Headers.new.update(headers),
|
21
|
+
:request => {}
|
22
|
+
}
|
23
|
+
unless oauth_options.is_a? Hash and oauth_options.empty?
|
24
|
+
env[:request][:oauth] = oauth_options
|
25
|
+
end
|
26
|
+
app = make_app
|
27
|
+
app.call(env)
|
28
|
+
end
|
29
|
+
|
30
|
+
def make_app
|
31
|
+
described_class.new(lambda{|env| env}, *Array(options))
|
32
|
+
end
|
33
|
+
|
34
|
+
context "invalid options" do
|
35
|
+
let(:options) { nil }
|
36
|
+
|
37
|
+
it "should error out" do
|
38
|
+
expect { make_app }.to raise_error(ArgumentError)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "empty options" do
|
43
|
+
let(:options) { [{}] }
|
44
|
+
|
45
|
+
it "should sign request" do
|
46
|
+
auth = auth_values(perform)
|
47
|
+
expected_keys = %w[ oauth_nonce
|
48
|
+
oauth_signature oauth_signature_method
|
49
|
+
oauth_timestamp oauth_version ]
|
50
|
+
|
51
|
+
auth.keys.should =~ expected_keys
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "configured with consumer and token" do
|
56
|
+
let(:options) do
|
57
|
+
[{ :consumer_key => 'CKEY', :consumer_secret => 'CSECRET',
|
58
|
+
:token => 'TOKEN', :token_secret => 'TSECRET'
|
59
|
+
}]
|
60
|
+
end
|
61
|
+
|
62
|
+
it "adds auth info to the header" do
|
63
|
+
auth = auth_values(perform)
|
64
|
+
expected_keys = %w[ oauth_consumer_key oauth_nonce
|
65
|
+
oauth_signature oauth_signature_method
|
66
|
+
oauth_timestamp oauth_token oauth_version ]
|
67
|
+
|
68
|
+
auth.keys.should =~ expected_keys
|
69
|
+
auth['oauth_version'].should eq(%("1.0"))
|
70
|
+
auth['oauth_signature_method'].should eq(%("HMAC-SHA1"))
|
71
|
+
auth['oauth_consumer_key'].should eq(%("CKEY"))
|
72
|
+
auth['oauth_token'].should eq(%("TOKEN"))
|
73
|
+
end
|
74
|
+
|
75
|
+
it "doesn't override existing header" do
|
76
|
+
request = perform({}, "Authorization" => "iz me!")
|
77
|
+
auth_header(request).should eq("iz me!")
|
78
|
+
end
|
79
|
+
|
80
|
+
it "can override oauth options per-request" do
|
81
|
+
auth = auth_values(perform(:consumer_key => 'CKEY2'))
|
82
|
+
|
83
|
+
auth['oauth_consumer_key'].should eq(%("CKEY2"))
|
84
|
+
auth['oauth_token'].should eq(%("TOKEN"))
|
85
|
+
end
|
86
|
+
|
87
|
+
it "can turn off oauth signing per-request" do
|
88
|
+
auth_header(perform(false)).should be_nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "configured without token" do
|
93
|
+
let(:options) { [{ :consumer_key => 'CKEY', :consumer_secret => 'CSECRET' }] }
|
94
|
+
|
95
|
+
it "adds auth info to the header" do
|
96
|
+
auth = auth_values(perform)
|
97
|
+
auth.should include('oauth_consumer_key')
|
98
|
+
auth.should_not include('oauth_token')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|