This commit is contained in:
rick 2010-10-05 11:19:22 -07:00
commit 647c8fe6e1
14 changed files with 171 additions and 79 deletions

View File

@ -9,7 +9,7 @@ Gem::Specification.new do |s|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["rick"]
s.date = %q{2010-05-28}
s.date = %q{2010-10-02}
s.description = %q{HTTP/REST API client library with pluggable components}
s.email = %q{technoweenie@gmail.com}
s.extra_rdoc_files = [
@ -25,6 +25,7 @@ Gem::Specification.new do |s|
"VERSION",
"faraday.gemspec",
"lib/faraday.rb",
"lib/faraday/adapter/action_dispatch.rb",
"lib/faraday/adapter/net_http.rb",
"lib/faraday/adapter/patron.rb",
"lib/faraday/adapter/test.rb",
@ -39,6 +40,7 @@ Gem::Specification.new do |s|
"lib/faraday/response.rb",
"lib/faraday/response/active_support_json.rb",
"lib/faraday/response/yajl.rb",
"lib/faraday/utils.rb",
"test/adapters/live_test.rb",
"test/adapters/test_middleware_test.rb",
"test/adapters/typhoeus_test.rb",
@ -53,7 +55,7 @@ Gem::Specification.new do |s|
s.homepage = %q{http://github.com/technoweenie/faraday}
s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["lib"]
s.rubygems_version = %q{1.3.6}
s.rubygems_version = %q{1.3.7}
s.summary = %q{HTTP/REST API client library}
s.test_files = [
"test/adapters/live_test.rb",
@ -72,7 +74,7 @@ Gem::Specification.new do |s|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<rack>, [">= 1.0.1"])
s.add_runtime_dependency(%q<addressable>, [">= 2.1.1"])
else

View File

@ -1,5 +1,3 @@
require 'rack/utils'
module Faraday
class << self
attr_accessor :default_adapter
@ -49,19 +47,21 @@ module Faraday
extend AutoloadHelper
autoload_all 'faraday',
:Adapter => 'adapter',
:Connection => 'connection',
autoload_all 'faraday',
:Middleware => 'middleware',
:Builder => 'builder',
:Request => 'request',
:Response => 'response',
:CompositeReadIO => 'upload_io',
:UploadIO => 'upload_io',
:Parts => 'upload_io',
:Error => 'error'
:Parts => 'upload_io'
end
require 'faraday/utils'
require 'faraday/connection'
require 'faraday/adapter'
require 'faraday/error'
# not pulling in active-support JUST for this method.
class Object
# Yields <code>x</code> to the block, and then returns <code>x</code>.

View File

@ -56,7 +56,7 @@ module Faraday
@app.call env
rescue Errno::ECONNREFUSED
raise Error::ConnectionFailed, "connection refused"
raise Error::ConnectionFailed.new(Errno::ECONNREFUSED)
end
def net_http_class(env)

View File

@ -26,7 +26,7 @@ module Faraday
@app.call env
rescue Errno::ECONNREFUSED
raise Error::ConnectionFailed, "connection refused"
raise Error::ConnectionFailed.new(Errno::ECONNREFUSED)
end
# TODO: build in support for multipart streaming if patron supports it.

View File

@ -19,7 +19,7 @@ module Faraday
class Stubs
def initialize
# {:get => [Stub, Stub]}
@stack = {}
@stack, @consumed = {}, {}
yield self if block_given?
end
@ -29,8 +29,15 @@ module Faraday
def match(request_method, path, body)
return false if !@stack.key?(request_method)
stub = @stack[request_method].detect { |stub| stub.matches?(path, body) }
@stack[request_method].delete stub
stack = @stack[request_method]
consumed = (@consumed[request_method] ||= [])
if stub = matches?(stack, path, body)
consumed << stack.delete(stub)
stub
else
matches?(consumed, path, body)
end
end
def get(path, &block)
@ -53,10 +60,6 @@ module Faraday
new_stub(:delete, path, &block)
end
def new_stub(request_method, path, body=nil, &block)
(@stack[request_method] ||= []) << Stub.new(path, body, block)
end
# Raises an error if any of the stubbed calls have not been made.
def verify_stubbed_calls
failed_stubs = []
@ -69,6 +72,16 @@ module Faraday
end
raise failed_stubs.join(" ") unless failed_stubs.size == 0
end
protected
def new_stub(request_method, path, body=nil, &block)
(@stack[request_method] ||= []) << Stub.new(path, body, block)
end
def matches?(stack, path, body)
stack.detect { |stub| stub.matches?(path, body) }
end
end
class Stub < Struct.new(:path, :body, :block)
@ -91,17 +104,8 @@ module Faraday
yield stubs
end
def request_uri(url)
(url.path != "" ? url.path : "/") +
(url.query ? "?#{sort_query_params(url.query)}" : "")
end
def sort_query_params(query)
query.split('&').sort.join('&')
end
def call(env)
if stub = stubs.match(env[:method], request_uri(env[:url]), env[:body])
if stub = stubs.match(env[:method], Faraday::Utils.normalize_path(env[:url]), env[:body])
status, headers, body = stub.block.call(env)
env.update \
:status => status,

View File

@ -43,7 +43,7 @@ module Faraday
@app.call env
rescue Errno::ECONNREFUSED
raise Error::ConnectionFailed, "connection refused"
raise Error::ConnectionFailed.new(Errno::ECONNREFUSED)
end
def in_parallel(options = {})

View File

@ -4,20 +4,7 @@ require 'base64'
module Faraday
class Connection
include Addressable, Rack::Utils
HEADERS = Hash.new do |h, k|
if k.respond_to?(:to_str)
k
else
k.to_s.split('_'). # :user_agent => %w(user agent)
each { |w| w.capitalize! }. # => %w(User Agent)
join('-') # => "User-Agent"
end
end
HEADERS.update \
:etag => "ETag"
HEADERS.values.each { |v| v.freeze }
include Addressable, Faraday::Utils
METHODS = Set.new [:get, :post, :put, :delete, :head]
METHODS_WITH_BODIES = Set.new [:post, :put]
@ -226,33 +213,6 @@ module Faraday
uri
end
# turns param keys into strings
def merge_params(existing_params, new_params)
new_params.each do |key, value|
existing_params[key.to_s] = value
end
end
# turns headers keys and values into strings. Look up symbol keys in the
# the HEADERS hash.
#
# h = merge_headers(HeaderHash.new, :content_type => 'text/plain')
# h['Content-Type'] # = 'text/plain'
#
def merge_headers(existing_headers, new_headers)
new_headers.each do |key, value|
existing_headers[HEADERS[key]] = value.to_s
end
end
# Be sure to URI escape '+' symbols to %2B. Otherwise, they get interpreted
# as spaces.
def escape(s)
s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) do
'%' << $1.unpack('H2'*bytesize($1)).join('%').tap { |c| c.upcase! }
end
end
def proxy_arg_to_uri(arg)
case arg
when String then URI.parse(arg)

View File

@ -1,7 +1,31 @@
module Faraday
module Error
class ClientError < StandardError; end
class ClientError < StandardError
def initialize(exception)
@inner_exception = exception
end
def message
@inner_exception.message
end
def backtrace
@inner_exception.backtrace
end
alias to_str message
def to_s
@inner_exception.to_s
end
def inspect
@inner_exception.inspect
end
end
class ConnectionFailed < ClientError; end
class ResourceNotFound < ClientError; end
class ParsingError < ClientError; end
end
end

View File

@ -8,16 +8,23 @@ module Faraday
def self.register_on_complete(env)
env[:response].on_complete do |finished_env|
finished_env[:body] = ActiveSupport::JSON.decode(finished_env[:body])
finished_env[:body] = parse(finished_env[:body])
end
end
rescue LoadError, NameError => e
self.load_error = e
end
def initialize(app)
super
@parser = nil
end
def self.parse(body)
ActiveSupport::JSON.decode(body)
rescue Object => err
raise Faraday::Error::ParsingError.new(err)
end
end
end

View File

@ -5,16 +5,22 @@ module Faraday
def self.register_on_complete(env)
env[:response].on_complete do |finished_env|
finished_env[:body] = Yajl::Parser.parse(finished_env[:body])
finished_env[:body] = parse(finished_env[:body])
end
end
rescue LoadError, NameError => e
self.load_error = e
end
def initialize(app)
super
@parser = nil
end
def self.parse(body)
Yajl::Parser.parse(body)
rescue Object => err
raise Faraday::Error::ParsingError.new(err)
end
end
end

64
lib/faraday/utils.rb Normal file
View File

@ -0,0 +1,64 @@
require 'rack/utils'
module Faraday
module Utils
include Rack::Utils
extend Rack::Utils
extend self
HEADERS = Hash.new do |h, k|
if k.respond_to?(:to_str)
k
else
k.to_s.split('_'). # :user_agent => %w(user agent)
each { |w| w.capitalize! }. # => %w(User Agent)
join('-') # => "User-Agent"
end
end
HEADERS.merge! :etag => "ETag"
HEADERS.values.each { |v| v.freeze }
# Make Rack::Utils build_query method public.
public :build_query
# Be sure to URI escape '+' symbols to %2B. Otherwise, they get interpreted
# as spaces.
def escape(s)
s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) do
'%' << $1.unpack('H2'*bytesize($1)).join('%').tap { |c| c.upcase! }
end
end
# Turns param keys into strings
def merge_params(existing_params, new_params)
new_params.each do |key, value|
existing_params[key.to_s] = value
end
end
# Turns headers keys and values into strings. Look up symbol keys in the
# the HEADERS hash.
#
# h = merge_headers(HeaderHash.new, :content_type => 'text/plain')
# h['Content-Type'] # = 'text/plain'
#
def merge_headers(existing_headers, new_headers)
new_headers.each do |key, value|
existing_headers[HEADERS[key]] = value.to_s
end
end
# Receives a URL and returns just the path with the query string sorted.
def normalize_path(url)
(url.path != "" ? url.path : "/") +
(url.query ? "?#{sort_query_params(url.query)}" : "")
end
protected
def sort_query_params(query)
query.split('&').sort.join('&')
end
end
end

View File

@ -22,5 +22,16 @@ module Adapters
def test_middleware_with_simple_path_sets_body
assert_equal 'hello', @resp.body
end
def test_middleware_can_be_called_several_times
assert_equal 'hello', @conn.get("/hello").body
end
def test_middleware_allow_different_outcomes_for_the_same_request
@stubs.get('/hello') { [200, {'Content-Type' => 'text/html'}, 'hello'] }
@stubs.get('/hello') { [200, {'Content-Type' => 'text/html'}, 'world'] }
assert_equal 'hello', @conn.get("/hello").body
assert_equal 'world', @conn.get("/hello").body
end
end
end

View File

@ -6,9 +6,11 @@ class RequestMiddlewareTest < Faraday::TestCase
next if !encoder.loaded?
define_method "test_encodes_json_with_#{key}" do
raw_json = create_json_connection(encoder).post('echo_body', :a => 1).body
resp = create_json_connection(encoder).post('echo_body', :a => 1)
raw_json = resp.body
raw_json.gsub! /: 1/, ':1' # sometimes rails_json adds a space
assert_equal %({"a":1}), raw_json
assert_match /json/, resp.headers['Content-Type']
end
end
@ -17,7 +19,12 @@ private
Faraday::Connection.new do |b|
b.use encoder
b.adapter :test do |stub|
stub.post('echo_body') { |env| [200, {'Content-Type' => 'text/html'}, env[:body]] }
stub.post('echo_body') do |env|
[200,
{'Content-Type' => env[:request_headers]['Content-Type']},
env[:body]
]
end
end
end
end

View File

@ -4,7 +4,7 @@ class ResponseMiddlewareTest < Faraday::TestCase
[:yajl, :rails_json].each do |key|
encoder = Faraday::Response.lookup_module(key)
next if !encoder.loaded?
define_method "test_uses_#{key}_to_parse_json_content" do
response = create_json_connection(encoder).get('json')
assert response.success?
@ -22,6 +22,12 @@ class ResponseMiddlewareTest < Faraday::TestCase
assert response.success?
assert !response.body
end
define_method "test_use_#{key}_to_raise_Faraday_Error_Parsing_with_no_json_content" do
assert_raises Faraday::Error::ParsingError do
response = create_json_connection(encoder).get('bad_json')
end
end
end
def create_json_connection(encoder)
@ -30,6 +36,7 @@ class ResponseMiddlewareTest < Faraday::TestCase
stub.get('json') { [200, {'Content-Type' => 'text/html'}, "[1,2,3]"] }
stub.get('blank') { [200, {'Content-Type' => 'text/html'}, ''] }
stub.get('nil') { [200, {'Content-Type' => 'text/html'}, nil] }
stub.get("bad_json") {[200, {'Content-Type' => 'text/html'}, '<body></body>']}
end
b.use encoder
end