faraday 0.1.2 → 0.2.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 (39) hide show
  1. data/LICENSE +1 -1
  2. data/README.rdoc +48 -61
  3. data/Rakefile +3 -1
  4. data/VERSION +1 -1
  5. data/faraday.gemspec +31 -14
  6. data/lib/faraday.rb +33 -26
  7. data/lib/faraday/adapter/net_http.rb +17 -31
  8. data/lib/faraday/adapter/patron.rb +33 -0
  9. data/lib/faraday/adapter/test.rb +96 -0
  10. data/lib/faraday/adapter/typhoeus.rb +43 -59
  11. data/lib/faraday/builder.rb +57 -0
  12. data/lib/faraday/connection.rb +112 -105
  13. data/lib/faraday/middleware.rb +54 -0
  14. data/lib/faraday/request.rb +77 -0
  15. data/lib/faraday/request/active_support_json.rb +20 -0
  16. data/lib/faraday/request/yajl.rb +18 -0
  17. data/lib/faraday/response.rb +41 -26
  18. data/lib/faraday/response/active_support_json.rb +22 -0
  19. data/lib/faraday/response/yajl.rb +20 -0
  20. data/test/adapters/live_test.rb +157 -0
  21. data/test/adapters/test_middleware_test.rb +28 -0
  22. data/test/adapters/typhoeus_test.rb +28 -0
  23. data/test/connection_app_test.rb +49 -0
  24. data/test/connection_test.rb +47 -18
  25. data/test/env_test.rb +35 -0
  26. data/test/helper.rb +5 -2
  27. data/test/live_server.rb +2 -15
  28. data/test/request_middleware_test.rb +19 -0
  29. data/test/response_middleware_test.rb +21 -0
  30. metadata +46 -16
  31. data/lib/faraday/adapter/mock_request.rb +0 -120
  32. data/lib/faraday/loadable.rb +0 -13
  33. data/lib/faraday/request/post_request.rb +0 -30
  34. data/lib/faraday/request/yajl_request.rb +0 -27
  35. data/lib/faraday/response/yajl_response.rb +0 -35
  36. data/lib/faraday/test_connection.rb +0 -5
  37. data/test/adapter/typhoeus_test.rb +0 -26
  38. data/test/adapter_test.rb +0 -118
  39. data/test/response_test.rb +0 -34
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 rick
1
+ Copyright (c) 2009-* rick olson, zack hobson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -1,79 +1,66 @@
1
1
  = faraday
2
2
 
3
- Experiments in a REST API lib
4
-
5
- Super alpha! Don't use it if you mind throwing away all the code when I change
6
- the API on a whim.
3
+ Modular HTTP client library using middleware heavily inspired by Rack.
7
4
 
8
5
  This mess is gonna get raw, like sushi. So, haters to the left.
9
6
 
10
7
  == Usage
11
8
 
12
- # uses Net/HTTP, no response parsing
13
- conn = Faraday::Connection.new("http://sushi.com")
14
- conn.extend Faraday::Adapter::NetHttp
15
- resp = conn.get("/sake.json")
16
- resp.body # => %({"name":"Sake"})
17
-
18
- # uses Net/HTTP, Yajl parsing
19
- conn = Faraday::Connection.new("http://sushi.com")
20
- conn.extend Faraday::Adapter::NetHttp
21
- conn.response_class = Faraday::Response::YajlResponse
22
- resp = conn.get("/sake.json")
23
- resp.body # => {"name": "Sake"}
24
-
25
- # uses Typhoeus, no response parsing
26
- conn = Faraday::Connection.new("http://sushi.com")
27
- conn.extend Faraday::Adapter::Typhoeus
28
- resp = conn.get("/sake.json")
29
- resp.body # => %({"name":"Sake"})
30
-
31
- # uses Typhoeus, Yajl parsing, performs requests in parallel
32
- conn = Faraday::Connection.new("http://sushi.com")
33
- conn.extend Faraday::Adapter::Typhoeus
34
- conn.response_class = Faraday::Response::YajlResponse
35
- resp1, resp2 = nil, nil
36
- conn.in_parallel do
37
- resp1 = conn.get("/sake.json")
38
- resp2 = conn.get("/unagi.json")
39
-
40
- # requests have not been made yet
41
- resp1.body # => nil
42
- resp2.body # => nil
43
- end
44
- resp1.body # => {"name": "Sake"}
45
- resp2.body # => {"name": "Unagi"}
9
+ conn = Alice::Connection.new(:url => 'http://sushi.com') do |builder|
10
+ builder.use Alice::Request::Yajl # convert body to json with Yajl lib
11
+ builder.use Alice::Adapter::Logger # log the request somewhere?
12
+ builder.use Alice::Adapter::Typhoeus # make http request with typhoeus
13
+ builder.use Alice::Response::Yajl # # parse body with yajl
14
+
15
+ # or use shortcuts
16
+ builder.request :yajl # Alice::Request::Yajl
17
+ builder.adapter :logger # Alice::Adapter::Logger
18
+ builder.adapter :typhoeus # Alice::Adapter::Typhoeus
19
+ builder.response :yajl # Alice::Response::Yajl
20
+ end
21
+
22
+ resp1 = conn.get '/nigiri/sake.json'
23
+ resp2 = conn.post do |req|
24
+ req.url "/nigiri.json", :page => 2
25
+ req[:content_type] = 'application/json'
26
+ req.body = {:name => 'Unagi'}
27
+ end
46
28
 
47
29
  == Testing
48
30
 
49
- * Yajl is needed for tests :(
50
- * Live Sinatra server is required for tests: `ruby test/live_server.rb` to start it.
51
-
52
- === Writing tests based on faraday
31
+ # It's possible to define stubbed request outside a test adapter block.
32
+ stubs = Alice::Test::Stubs.new do |stub|
33
+ stub.get('/tamago') { [200, 'egg', {} }
34
+ end
53
35
 
54
- Using the MockRequest connection adapter you can implement your own test
55
- connection class:
56
-
57
- # customize your own TestConnection or just use Faraday::TestConnection
58
- class TestConnection < Faraday::Connection
59
- include Faraday::Adapter::MockRequest
36
+ # You can pass stubbed request to the test adapter or define them in a block
37
+ # or a combination of the two.
38
+ test = Alice::Connection.new do |builder|
39
+ builder.adapter :test, stubs do |stub|
40
+ stub.get('/ebi') {[ 200, 'shrimp', {} ]}
60
41
  end
42
+ end
61
43
 
62
- conn = TestConnection.new do |stub|
63
- # response mimics a rack response
64
- stub.get('/hello.json') { [200, {}, 'hi world'] }
65
- end
66
- resp = conn.get '/hello.json'
67
- resp.body # => 'hi world'
68
- resp = conn.get '/whatever' # => <not stubbed, raises connection error>
44
+ # It's also possible to stub additional requests after the connection has
45
+ # been initialized. This is useful for testing.
46
+ stubs.get('/uni') {[ 200, 'urchin', {} ]}
47
+
48
+ resp = test.get '/tamago'
49
+ resp.body # => 'egg'
50
+ resp = test.get '/ebi'
51
+ resp.body # => 'shrimp'
52
+ resp = test.get '/uni'
53
+ resp.body # => 'urchin'
54
+ resp = test.get '/else' #=> raises "no such stub" error
69
55
 
70
56
  == TODO
71
57
 
72
- * other HTTP methods besides just GET
73
- * gracefully skip tests for Yajl and other optional libraries if they don't exist.
74
- * gracefully skip live http server tests if the sinatra server is not running.
75
- * use Typhoeus' request mocking facilities in the Typhoeus adapter test
76
- * lots of other crap
58
+ * Add curb/em-http support
59
+ * Add xml parsing
60
+ * Support timeouts, proxy servers, ssl options
61
+ * Add streaming requests and responses
62
+ * Add default middleware load out for common cases
63
+ * Add symbol => string index for mime types (:json => 'application/json')
77
64
 
78
65
  == Note on Patches/Pull Requests
79
66
 
@@ -87,4 +74,4 @@ connection class:
87
74
 
88
75
  == Copyright
89
76
 
90
- Copyright (c) 2009 rick. See LICENSE for details.
77
+ Copyright (c) 2009-2010 rick, hobson. See LICENSE for details.
data/Rakefile CHANGED
@@ -10,6 +10,8 @@ begin
10
10
  gem.email = "technoweenie@gmail.com"
11
11
  gem.homepage = "http://github.com/technoweenie/faraday"
12
12
  gem.authors = ["rick"]
13
+ gem.add_dependency "rack"
14
+ gem.add_dependency "addressable"
13
15
  end
14
16
  Jeweler::GemcutterTasks.new
15
17
  rescue LoadError
@@ -27,7 +29,7 @@ begin
27
29
  require 'rcov/rcovtask'
28
30
  Rcov::RcovTask.new do |test|
29
31
  test.libs << 'test'
30
- test.pattern = 'test/**/test_*.rb'
32
+ test.pattern = 'test/**/*_test.rb'
31
33
  test.verbose = true
32
34
  end
33
35
  rescue LoadError
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
data/faraday.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{faraday}
8
- s.version = "0.1.2"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["rick"]
12
- s.date = %q{2010-01-05}
12
+ s.date = %q{2010-01-12}
13
13
  s.description = %q{HTTP/REST API client library with pluggable components}
14
14
  s.email = %q{technoweenie@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -25,23 +25,30 @@ Gem::Specification.new do |s|
25
25
  "VERSION",
26
26
  "faraday.gemspec",
27
27
  "lib/faraday.rb",
28
- "lib/faraday/adapter/mock_request.rb",
29
28
  "lib/faraday/adapter/net_http.rb",
29
+ "lib/faraday/adapter/patron.rb",
30
+ "lib/faraday/adapter/test.rb",
30
31
  "lib/faraday/adapter/typhoeus.rb",
32
+ "lib/faraday/builder.rb",
31
33
  "lib/faraday/connection.rb",
32
34
  "lib/faraday/error.rb",
33
- "lib/faraday/loadable.rb",
34
- "lib/faraday/request/post_request.rb",
35
- "lib/faraday/request/yajl_request.rb",
35
+ "lib/faraday/middleware.rb",
36
+ "lib/faraday/request.rb",
37
+ "lib/faraday/request/active_support_json.rb",
38
+ "lib/faraday/request/yajl.rb",
36
39
  "lib/faraday/response.rb",
37
- "lib/faraday/response/yajl_response.rb",
38
- "lib/faraday/test_connection.rb",
39
- "test/adapter/typhoeus_test.rb",
40
- "test/adapter_test.rb",
40
+ "lib/faraday/response/active_support_json.rb",
41
+ "lib/faraday/response/yajl.rb",
42
+ "test/adapters/live_test.rb",
43
+ "test/adapters/test_middleware_test.rb",
44
+ "test/adapters/typhoeus_test.rb",
45
+ "test/connection_app_test.rb",
41
46
  "test/connection_test.rb",
47
+ "test/env_test.rb",
42
48
  "test/helper.rb",
43
49
  "test/live_server.rb",
44
- "test/response_test.rb"
50
+ "test/request_middleware_test.rb",
51
+ "test/response_middleware_test.rb"
45
52
  ]
46
53
  s.homepage = %q{http://github.com/technoweenie/faraday}
47
54
  s.rdoc_options = ["--charset=UTF-8"]
@@ -49,12 +56,16 @@ Gem::Specification.new do |s|
49
56
  s.rubygems_version = %q{1.3.5}
50
57
  s.summary = %q{HTTP/REST API client library}
51
58
  s.test_files = [
52
- "test/adapter/typhoeus_test.rb",
53
- "test/adapter_test.rb",
59
+ "test/adapters/live_test.rb",
60
+ "test/adapters/test_middleware_test.rb",
61
+ "test/adapters/typhoeus_test.rb",
62
+ "test/connection_app_test.rb",
54
63
  "test/connection_test.rb",
64
+ "test/env_test.rb",
55
65
  "test/helper.rb",
56
66
  "test/live_server.rb",
57
- "test/response_test.rb"
67
+ "test/request_middleware_test.rb",
68
+ "test/response_middleware_test.rb"
58
69
  ]
59
70
 
60
71
  if s.respond_to? :specification_version then
@@ -62,9 +73,15 @@ Gem::Specification.new do |s|
62
73
  s.specification_version = 3
63
74
 
64
75
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
76
+ s.add_runtime_dependency(%q<rack>, [">= 0"])
77
+ s.add_runtime_dependency(%q<addressable>, [">= 0"])
65
78
  else
79
+ s.add_dependency(%q<rack>, [">= 0"])
80
+ s.add_dependency(%q<addressable>, [">= 0"])
66
81
  end
67
82
  else
83
+ s.add_dependency(%q<rack>, [">= 0"])
84
+ s.add_dependency(%q<addressable>, [">= 0"])
68
85
  end
69
86
  end
70
87
 
data/lib/faraday.rb CHANGED
@@ -1,5 +1,16 @@
1
+ require 'rack/utils'
2
+
1
3
  module Faraday
2
4
  module AutoloadHelper
5
+ def register_lookup_modules(mods)
6
+ (@lookup_module_index ||= {}).update(mods)
7
+ end
8
+
9
+ def lookup_module(key)
10
+ return if !@lookup_module_index
11
+ const_get @lookup_module_index[key] || key
12
+ end
13
+
3
14
  def autoload_all(prefix, options)
4
15
  options.each do |const_name, path|
5
16
  autoload const_name, File.join(prefix, path)
@@ -8,45 +19,41 @@ module Faraday
8
19
 
9
20
  # Loads each autoloaded constant. If thread safety is a concern, wrap
10
21
  # this in a Mutex.
11
- def load
22
+ def load_autoloaded_constants
12
23
  constants.each do |const|
13
24
  const_get(const) if autoload?(const)
14
25
  end
15
26
  end
27
+
28
+ def all_loaded_constants
29
+ constants.map { |c| const_get(c) }.select { |a| a.loaded? }
30
+ end
16
31
  end
17
32
 
18
33
  extend AutoloadHelper
19
34
 
20
35
  autoload_all 'faraday',
21
- :Connection => 'connection',
22
- :TestConnection => 'test_connection',
23
- :Response => 'response',
24
- :Error => 'error',
25
- :Loadable => 'loadable'
26
-
27
- module Request
28
- extend AutoloadHelper
29
- autoload_all 'faraday/request',
30
- :YajlRequest => 'yajl_request',
31
- :PostRequest => 'post_request'
32
- end
36
+ :Connection => 'connection',
37
+ :Middleware => 'middleware',
38
+ :Builder => 'builder',
39
+ :Request => 'request',
40
+ :Response => 'response',
41
+ :Error => 'error'
33
42
 
34
43
  module Adapter
35
44
  extend AutoloadHelper
36
- autoload_all 'faraday/adapter',
37
- :NetHttp => 'net_http',
38
- :Typhoeus => 'typhoeus',
39
- :MockRequest => 'mock_request'
45
+ autoload_all 'faraday/adapter',
46
+ :NetHttp => 'net_http',
47
+ :Typhoeus => 'typhoeus',
48
+ :Patron => 'patron',
49
+ :Test => 'test'
40
50
 
41
- # Names of available adapters. Should not actually load them.
42
- def self.adapters
43
- constants
44
- end
45
-
46
- # Array of Adapters. These have been loaded and confirmed to work (right gems, etc).
47
- def self.loaded_adapters
48
- adapters.map { |c| const_get(c) }.select { |a| a.loaded? }
49
- end
51
+ register_lookup_modules \
52
+ :test => :Test,
53
+ :net_http => :NetHttp,
54
+ :typhoeus => :Typhoeus,
55
+ :patron => :patron,
56
+ :net_http => :NetHttp
50
57
  end
51
58
  end
52
59
 
@@ -1,42 +1,28 @@
1
1
  require 'net/http'
2
- require 'cgi'
3
2
  module Faraday
4
3
  module Adapter
5
- module NetHttp
6
- extend Faraday::Connection::Options
4
+ class NetHttp < Middleware
5
+ def call(env)
6
+ process_body_for_request(env)
7
7
 
8
- def _perform(method, uri, data, request_headers)
9
- http = Net::HTTP.new(uri.host, uri.port)
10
- response_class.new do |resp|
11
- http_resp = http.send_request(method, path_for(uri), data, request_headers)
12
- raise Faraday::Error::ResourceNotFound if http_resp.code == '404'
13
- resp.process http_resp.body
14
- http_resp.each_header do |key, value|
15
- resp.headers[key] = value
16
- end
17
- end
18
- rescue Errno::ECONNREFUSED
19
- raise Faraday::Error::ConnectionFailed, "connection refused"
20
- end
21
-
22
- def _put(uri, data, request_headers)
23
- request = request_class.new(data, request_headers)
24
- _perform('PUT', uri, request.body, request.headers)
25
- end
8
+ http = Net::HTTP.new(env[:url].host, env[:url].port)
9
+ full_path = full_path_for(env[:url].path, env[:url].query, env[:url].fragment)
10
+ http_resp = http.send_request(env[:method].to_s.upcase, full_path, env[:body], env[:request_headers])
26
11
 
27
- def _post(uri, data, request_headers)
28
- request = request_class.new(data, request_headers)
29
- _perform('POST', uri, request.body, request.headers)
30
- end
12
+ resp_headers = {}
13
+ http_resp.each_header do |key, value|
14
+ resp_headers[key] = value
15
+ end
31
16
 
32
- def _get(uri, request_headers)
33
- _perform('GET', uri, uri.query, request_headers)
34
- end
17
+ env.update \
18
+ :status => http_resp.code.to_i,
19
+ :response_headers => resp_headers,
20
+ :body => http_resp.body
35
21
 
36
- def _delete(uri, request_headers)
37
- _perform('DELETE', uri, uri.query, request_headers)
22
+ @app.call env
23
+ rescue Errno::ECONNREFUSED
24
+ raise Error::ConnectionFailed, "connection refused"
38
25
  end
39
-
40
26
  end
41
27
  end
42
28
  end
@@ -0,0 +1,33 @@
1
+ module Faraday
2
+ module Adapter
3
+ class Patron < Middleware
4
+ begin
5
+ require 'patron'
6
+ rescue LoadError => e
7
+ self.load_error = e
8
+ end
9
+
10
+ def call(env)
11
+ process_body_for_request(env)
12
+
13
+ sess = ::Patron::Session.new
14
+ args = [env[:method], env[:url].to_s, env[:request_headers]]
15
+ if Faraday::Connection::METHODS_WITH_BODIES.include?(env[:method])
16
+ args.insert(2, env[:body].to_s)
17
+ end
18
+ resp = sess.send *args
19
+
20
+ env.update \
21
+ :status => resp.status,
22
+ :response_headers => resp.headers.
23
+ inject({}) { |memo, (k, v)| memo.update(k.downcase => v) },
24
+ :body => resp.body
25
+ env[:response].finish(env)
26
+
27
+ @app.call env
28
+ rescue Errno::ECONNREFUSED
29
+ raise Error::ConnectionFailed, "connection refused"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,96 @@
1
+ module Faraday
2
+ module Adapter
3
+ # test = Faraday::Connection.new do
4
+ # use Faraday::Adapter::Test do |stub|
5
+ # stub.get '/nigiri/sake.json' do
6
+ # [200, {}, 'hi world']
7
+ # end
8
+ # end
9
+ # end
10
+ #
11
+ # resp = test.get '/nigiri/sake.json'
12
+ # resp.body # => 'hi world'
13
+ #
14
+ class Test < Middleware
15
+ attr_accessor :stubs
16
+
17
+ def self.loaded?() false end
18
+
19
+ class Stubs
20
+ def initialize
21
+ # {:get => [Stub, Stub]}
22
+ @stack = {}
23
+ yield self if block_given?
24
+ end
25
+
26
+ def empty?
27
+ @stack.empty?
28
+ end
29
+
30
+ def match(request_method, path, body)
31
+ return false if !@stack.key?(request_method)
32
+ stub = @stack[request_method].detect { |stub| stub.matches?(path, body) }
33
+ @stack[request_method].delete stub
34
+ end
35
+
36
+ def get(path, &block)
37
+ new_stub(:get, path, &block)
38
+ end
39
+
40
+ def head(path, &block)
41
+ new_stub(:head, path, &block)
42
+ end
43
+
44
+ def post(path, body=nil, &block)
45
+ new_stub(:post, path, body, &block)
46
+ end
47
+
48
+ def put(path, body=nil, &block)
49
+ new_stub(:put, path, body, &block)
50
+ end
51
+
52
+ def delete(path, &block)
53
+ new_stub(:delete, path, &block)
54
+ end
55
+
56
+ def new_stub(request_method, path, body=nil, &block)
57
+ (@stack[request_method] ||= []) << Stub.new(path, body, block)
58
+ end
59
+ end
60
+
61
+ class Stub < Struct.new(:path, :body, :block)
62
+ def matches?(request_path, request_body)
63
+ request_path == path && request_body == body
64
+ end
65
+ end
66
+
67
+ def initialize app, stubs=nil, &block
68
+ super(app)
69
+ @stubs = stubs || Stubs.new
70
+ configure(&block) if block
71
+ end
72
+
73
+ def configure
74
+ yield stubs
75
+ end
76
+
77
+ def request_uri url
78
+ (url.path != "" ? url.path : "/") +
79
+ (url.query ? "?#{url.query}" : "")
80
+ end
81
+
82
+ def call(env)
83
+ if stub = stubs.match(env[:method], request_uri(env[:url]), env[:body])
84
+ status, headers, body = stub.block.call(env)
85
+ env.update \
86
+ :status => status,
87
+ :response_headers => headers,
88
+ :body => body
89
+ else
90
+ raise "no stubbed request for #{env[:method]} #{request_uri(env[:url])} #{env[:body]}"
91
+ end
92
+ @app.call(env)
93
+ end
94
+ end
95
+ end
96
+ end