faraday 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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