rename Alice => Faraday.

This commit is contained in:
rick 2010-01-12 17:03:34 -08:00
parent 86f5a2a83d
commit 67d619d780
35 changed files with 1100 additions and 769 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2009 rick
Copyright (c) 2009-* rick olson, zack hobson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@ -1,79 +1,66 @@
= faraday
Experiments in a REST API lib
Super alpha! Don't use it if you mind throwing away all the code when I change
the API on a whim.
Modular HTTP client library using middleware heavily inspired by Rack.
This mess is gonna get raw, like sushi. So, haters to the left.
== Usage
# uses Net/HTTP, no response parsing
conn = Faraday::Connection.new("http://sushi.com")
conn.extend Faraday::Adapter::NetHttp
resp = conn.get("/sake.json")
resp.body # => %({"name":"Sake"})
conn = Alice::Connection.new(:url => 'http://sushi.com') do |builder|
builder.use Alice::Request::Yajl # convert body to json with Yajl lib
builder.use Alice::Adapter::Logger # log the request somewhere?
builder.use Alice::Adapter::Typhoeus # make http request with typhoeus
builder.use Alice::Response::Yajl # # parse body with yajl
# uses Net/HTTP, Yajl parsing
conn = Faraday::Connection.new("http://sushi.com")
conn.extend Faraday::Adapter::NetHttp
conn.response_class = Faraday::Response::YajlResponse
resp = conn.get("/sake.json")
resp.body # => {"name": "Sake"}
# or use shortcuts
builder.request :yajl # Alice::Request::Yajl
builder.adapter :logger # Alice::Adapter::Logger
builder.adapter :typhoeus # Alice::Adapter::Typhoeus
builder.response :yajl # Alice::Response::Yajl
end
# uses Typhoeus, no response parsing
conn = Faraday::Connection.new("http://sushi.com")
conn.extend Faraday::Adapter::Typhoeus
resp = conn.get("/sake.json")
resp.body # => %({"name":"Sake"})
# uses Typhoeus, Yajl parsing, performs requests in parallel
conn = Faraday::Connection.new("http://sushi.com")
conn.extend Faraday::Adapter::Typhoeus
conn.response_class = Faraday::Response::YajlResponse
resp1, resp2 = nil, nil
conn.in_parallel do
resp1 = conn.get("/sake.json")
resp2 = conn.get("/unagi.json")
# requests have not been made yet
resp1.body # => nil
resp2.body # => nil
end
resp1.body # => {"name": "Sake"}
resp2.body # => {"name": "Unagi"}
resp1 = conn.get '/nigiri/sake.json'
resp2 = conn.post do |req|
req.url "/nigiri.json", :page => 2
req[:content_type] = 'application/json'
req.body = {:name => 'Unagi'}
end
== Testing
* Yajl is needed for tests :(
* Live Sinatra server is required for tests: `ruby test/live_server.rb` to start it.
# It's possible to define stubbed request outside a test adapter block.
stubs = Alice::Test::Stubs.new do |stub|
stub.get('/tamago') { [200, 'egg', {} }
end
=== Writing tests based on faraday
Using the MockRequest connection adapter you can implement your own test
connection class:
# customize your own TestConnection or just use Faraday::TestConnection
class TestConnection < Faraday::Connection
include Faraday::Adapter::MockRequest
# You can pass stubbed request to the test adapter or define them in a block
# or a combination of the two.
test = Alice::Connection.new do |builder|
builder.adapter :test, stubs do |stub|
stub.get('/ebi') {[ 200, 'shrimp', {} ]}
end
end
conn = TestConnection.new do |stub|
# response mimics a rack response
stub.get('/hello.json') { [200, {}, 'hi world'] }
end
resp = conn.get '/hello.json'
resp.body # => 'hi world'
resp = conn.get '/whatever' # => <not stubbed, raises connection error>
# It's also possible to stub additional requests after the connection has
# been initialized. This is useful for testing.
stubs.get('/uni') {[ 200, 'urchin', {} ]}
resp = test.get '/tamago'
resp.body # => 'egg'
resp = test.get '/ebi'
resp.body # => 'shrimp'
resp = test.get '/uni'
resp.body # => 'urchin'
resp = test.get '/else' #=> raises "no such stub" error
== TODO
* other HTTP methods besides just GET
* gracefully skip tests for Yajl and other optional libraries if they don't exist.
* gracefully skip live http server tests if the sinatra server is not running.
* use Typhoeus' request mocking facilities in the Typhoeus adapter test
* lots of other crap
* Add curb/em-http support
* Add xml parsing
* Support timeouts, proxy servers, ssl options
* Add streaming requests and responses
* Add default middleware load out for common cases
* Add symbol => string index for mime types (:json => 'application/json')
== Note on Patches/Pull Requests
@ -87,4 +74,4 @@ connection class:
== Copyright
Copyright (c) 2009 rick. See LICENSE for details.
Copyright (c) 2009-2010 rick, hobson. See LICENSE for details.

View File

@ -1,5 +1,16 @@
require 'rack/utils'
module Faraday
module AutoloadHelper
def register_lookup_modules(mods)
(@lookup_module_index ||= {}).update(mods)
end
def lookup_module(key)
return if !@lookup_module_index
const_get @lookup_module_index[key] || key
end
def autoload_all(prefix, options)
options.each do |const_name, path|
autoload const_name, File.join(prefix, path)
@ -8,45 +19,41 @@ module Faraday
# Loads each autoloaded constant. If thread safety is a concern, wrap
# this in a Mutex.
def load
def load_autoloaded_constants
constants.each do |const|
const_get(const) if autoload?(const)
end
end
def all_loaded_constants
constants.map { |c| const_get(c) }.select { |a| a.loaded? }
end
end
extend AutoloadHelper
autoload_all 'faraday',
:Connection => 'connection',
:TestConnection => 'test_connection',
:Response => 'response',
:Error => 'error',
:Loadable => 'loadable'
module Request
extend AutoloadHelper
autoload_all 'faraday/request',
:YajlRequest => 'yajl_request',
:PostRequest => 'post_request'
end
:Connection => 'connection',
:Middleware => 'middleware',
:Builder => 'builder',
:Request => 'request',
:Response => 'response',
:Error => 'error'
module Adapter
extend AutoloadHelper
autoload_all 'faraday/adapter',
:NetHttp => 'net_http',
:Typhoeus => 'typhoeus',
:MockRequest => 'mock_request'
autoload_all 'faraday/adapter',
:NetHttp => 'net_http',
:Typhoeus => 'typhoeus',
:Patron => 'patron',
:Test => 'test'
# Names of available adapters. Should not actually load them.
def self.adapters
constants
end
# Array of Adapters. These have been loaded and confirmed to work (right gems, etc).
def self.loaded_adapters
adapters.map { |c| const_get(c) }.select { |a| a.loaded? }
end
register_lookup_modules \
:test => :Test,
:net_http => :NetHttp,
:typhoeus => :Typhoeus,
:patron => :patron,
:net_http => :NetHttp
end
end

View File

@ -1,120 +0,0 @@
module Faraday
module Adapter
module MockRequest
extend Faraday::Connection::Options
def self.loaded?() false end
include Faraday::Error # ConnectionFailed
class Stubs
def initialize
# {:get => [Stub, Stub]}
@stack = {}
yield self if block_given?
end
def empty?
@stack.empty?
end
def match(request_method, path, data, request_headers)
return false if !@stack.key?(request_method)
stub = @stack[request_method].detect { |stub| stub.matches?(path, data, request_headers) }
@stack[request_method].delete(stub) if stub
end
def get(path, request_headers = {}, &block)
(@stack[:get] ||= []) << new_stub(path, {}, request_headers, block)
end
def delete(path, request_headers = {}, &block)
(@stack[:delete] ||= []) << new_stub(path, {}, request_headers, block)
end
def post(path, data, request_headers = {}, &block)
(@stack[:post] ||= []) << new_stub(path, data, request_headers, block)
end
def put(path, data, request_headers = {}, &block)
(@stack[:put] ||= []) << new_stub(path, data, request_headers, block)
end
def new_stub(path, data, request_headers, block)
status, response_headers, body = block.call
Stub.new(path, request_headers, status, response_headers, body, data)
end
end
class Stub < Struct.new(:path, :request_headers, :status, :response_headers, :body, :data)
def matches?(request_path, params, headers)
return false if request_path != path
return false if params != data
return true if request_headers.empty?
request_headers.each do |key, value|
return false if headers[key] != value
end
true
end
end
def initialize &block
super
configure(&block) if block
end
def configure
yield stubs
end
def stubs
@stubs ||= Stubs.new
end
def _get(uri, headers)
raise ConnectionFailed, "no stubbed requests" if stubs.empty?
if stub = @stubs.match(:get, uri.path, {}, headers)
response_class.new do |resp|
resp.headers = stub.response_headers
resp.process stub.body
end
else
nil
end
end
def _delete(uri, headers)
raise ConnectionFailed, "no stubbed requests" if stubs.empty?
if stub = @stubs.match(:delete, uri.path, {}, headers)
response_class.new do |resp|
resp.headers = stub.response_headers
resp.process stub.body
end
else
nil
end
end
def _post(uri, data, headers)
raise ConnectionFailed, "no stubbed requests" if stubs.empty?
if stub = @stubs.match(:post, uri.path, data, headers)
response_class.new do |resp|
resp.headers = stub.response_headers
resp.process stub.body
end
else
nil
end
end
def _put(uri, data, headers)
raise ConnectionFailed, "no stubbed requests" if stubs.empty?
if stub = @stubs.match(:put, uri.path, data, headers)
response_class.new do |resp|
resp.headers = stub.response_headers
resp.process stub.body
end
else
nil
end
end
end
end
end

View File

@ -1,42 +1,28 @@
require 'net/http'
require 'cgi'
module Faraday
module Adapter
module NetHttp
extend Faraday::Connection::Options
class NetHttp < Middleware
def call(env)
process_body_for_request(env)
def _perform(method, uri, data, request_headers)
http = Net::HTTP.new(uri.host, uri.port)
response_class.new do |resp|
http_resp = http.send_request(method, path_for(uri), data, request_headers)
raise Faraday::Error::ResourceNotFound if http_resp.code == '404'
resp.process http_resp.body
http_resp.each_header do |key, value|
resp.headers[key] = value
end
http = Net::HTTP.new(env[:url].host, env[:url].port)
full_path = full_path_for(env[:url].path, env[:url].query, env[:url].fragment)
http_resp = http.send_request(env[:method].to_s.upcase, full_path, env[:body], env[:request_headers])
resp_headers = {}
http_resp.each_header do |key, value|
resp_headers[key] = value
end
env.update \
:status => http_resp.code.to_i,
:response_headers => resp_headers,
:body => http_resp.body
@app.call env
rescue Errno::ECONNREFUSED
raise Faraday::Error::ConnectionFailed, "connection refused"
raise Error::ConnectionFailed, "connection refused"
end
def _put(uri, data, request_headers)
request = request_class.new(data, request_headers)
_perform('PUT', uri, request.body, request.headers)
end
def _post(uri, data, request_headers)
request = request_class.new(data, request_headers)
_perform('POST', uri, request.body, request.headers)
end
def _get(uri, request_headers)
_perform('GET', uri, uri.query, request_headers)
end
def _delete(uri, request_headers)
_perform('DELETE', uri, uri.query, request_headers)
end
end
end
end

View File

@ -0,0 +1,33 @@
module Faraday
module Adapter
class Patron < Middleware
begin
require 'patron'
rescue LoadError => e
self.load_error = e
end
def call(env)
process_body_for_request(env)
sess = ::Patron::Session.new
args = [env[:method], env[:url].to_s, env[:request_headers]]
if Faraday::Connection::METHODS_WITH_BODIES.include?(env[:method])
args.insert(2, env[:body].to_s)
end
resp = sess.send *args
env.update \
:status => resp.status,
:response_headers => resp.headers.
inject({}) { |memo, (k, v)| memo.update(k.downcase => v) },
:body => resp.body
env[:response].finish(env)
@app.call env
rescue Errno::ECONNREFUSED
raise Error::ConnectionFailed, "connection refused"
end
end
end
end

View File

@ -0,0 +1,96 @@
module Faraday
module Adapter
# test = Faraday::Connection.new do
# use Faraday::Adapter::Test do |stub|
# stub.get '/nigiri/sake.json' do
# [200, {}, 'hi world']
# end
# end
# end
#
# resp = test.get '/nigiri/sake.json'
# resp.body # => 'hi world'
#
class Test < Middleware
attr_accessor :stubs
def self.loaded?() false end
class Stubs
def initialize
# {:get => [Stub, Stub]}
@stack = {}
yield self if block_given?
end
def empty?
@stack.empty?
end
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
end
def get(path, &block)
new_stub(:get, path, &block)
end
def head(path, &block)
new_stub(:head, path, &block)
end
def post(path, body=nil, &block)
new_stub(:post, path, body, &block)
end
def put(path, body=nil, &block)
new_stub(:put, path, body, &block)
end
def delete(path, &block)
new_stub(:delete, path, &block)
end
def new_stub(request_method, path, body=nil, &block)
(@stack[request_method] ||= []) << Stub.new(path, body, block)
end
end
class Stub < Struct.new(:path, :body, :block)
def matches?(request_path, request_body)
request_path == path && request_body == body
end
end
def initialize app, stubs=nil, &block
super(app)
@stubs = stubs || Stubs.new
configure(&block) if block
end
def configure
yield stubs
end
def request_uri url
(url.path != "" ? url.path : "/") +
(url.query ? "?#{url.query}" : "")
end
def call(env)
if stub = stubs.match(env[:method], request_uri(env[:url]), env[:body])
status, headers, body = stub.block.call(env)
env.update \
:status => status,
:response_headers => headers,
:body => body
else
raise "no stubbed request for #{env[:method]} #{request_uri(env[:url])} #{env[:body]}"
end
@app.call(env)
end
end
end
end

View File

@ -1,76 +1,60 @@
module Faraday
module Adapter
module Typhoeus
extend Faraday::Connection::Options
class Typhoeus < Middleware
self.supports_parallel_requests = true
def self.setup_parallel_manager(options = {})
options.empty? ? ::Typhoeus::Hydra.hydra : ::Typhoeus::Hydra.new(options)
end
begin
require 'typhoeus'
def in_parallel?
!!@parallel_manager
end
def in_parallel(options = {})
setup_parallel_manager(options)
yield
run_parallel_requests
end
def setup_parallel_manager(options = {})
@parallel_manager ||= ::Typhoeus::Hydra.new(options)
end
def run_parallel_requests
@parallel_manager.run
@parallel_manager = nil
end
def _post(uri, data, request_headers)
request = request_class.new(data, request_headers)
_perform(:post, uri, :headers => request.headers, :body => request.body)
end
def _get(uri, request_headers)
_perform(:get, uri, :headers => request_headers)
end
def _put(uri, data, request_headers)
request = request_class.new(data, request_headers)
_perform(:put, uri, :headers => request.headers, :body => request.body)
end
def _delete(uri, request_headers)
_perform(:delete, uri, :headers => request_headers)
end
def _perform method, uri, params
response_class.new do |resp|
is_async = in_parallel?
setup_parallel_manager
params[:method] = method
req = ::Typhoeus::Request.new(uri.to_s, params)
req.on_complete do |response|
raise Faraday::Error::ResourceNotFound if response.code == 404
resp.process!(response.body)
resp.headers = parse_response_headers(response.headers)
end
@parallel_manager.queue(req)
if !is_async then run_parallel_requests end
end
rescue Errno::ECONNREFUSED
raise Faraday::Error::ConnectionFailed, "connection refused"
end
def parse_response_headers(header_string)
return {} unless header_string # XXX
Hash[*header_string.split(/\r\n/).
tap { |a| a.shift }. # drop the HTTP status line
map! { |h| h.split(/:\s+/,2) }. # split key and value
map! { |(k, v)| [k.downcase, v] }.flatten!]
end
rescue LoadError => e
self.load_error = e
end
def call(env)
process_body_for_request(env)
hydra = env[:parallel_manager] || self.class.setup_parallel_manager
req = ::Typhoeus::Request.new env[:url].to_s,
:method => env[:method],
:body => env[:body],
:headers => env[:request_headers]
req.on_complete do |resp|
env.update \
:status => resp.code,
:response_headers => parse_response_headers(resp.headers),
:body => resp.body
env[:response].finish(env)
end
hydra.queue req
if !env[:parallel_manager]
hydra.run
end
@app.call env
rescue Errno::ECONNREFUSED
raise Error::ConnectionFailed, "connection refused"
end
def in_parallel(options = {})
@hydra = ::Typhoeus::Hydra.new(options)
yield
@hydra.run
@hydra = nil
end
def parse_response_headers(header_string)
return {} unless header_string && !header_string.empty?
Hash[*header_string.split(/\r\n/).
tap { |a| a.shift }. # drop the HTTP status line
map! { |h| h.split(/:\s+/,2) }. # split key and value
map! { |(k, v)| [k.downcase, v] }.flatten!]
end
end
end
end

57
lib/faraday/builder.rb Normal file
View File

@ -0,0 +1,57 @@
module Faraday
# Possibly going to extend this a bit.
#
# Faraday::Connection.new(:url => 'http://sushi.com') do
# request :yajl # Faraday::Request::Yajl
# adapter :logger # Faraday::Adapter::Logger
# response :yajl # Faraday::Response::Yajl
# end
class Builder
attr_accessor :handlers
def self.create_with_inner_app(&block)
inner = lambda do |env|
if !env[:parallel_manager]
env[:response].finish(env)
else
env[:response]
end
end
Builder.new(&block).tap { |builder| builder.run(inner) }
end
def initialize
@handlers = []
yield self
end
def run app
@handlers.unshift app
end
def to_app
inner_app = @handlers.first
@handlers[1..-1].inject(inner_app) { |app, middleware| middleware.call(app) }
end
def use klass, *args, &block
@handlers.unshift(lambda { |app| klass.new(app, *args, &block) })
end
def request(key, *args, &block)
use_symbol Faraday::Request, key, *args, &block
end
def response(key, *args, &block)
use_symbol Faraday::Response, key, *args, &block
end
def adapter(key, *args, &block)
use_symbol Faraday::Adapter, key, *args, &block
end
def use_symbol(mod, key, *args, &block)
use mod.lookup_module(key), *args, &block
end
end
end

View File

@ -1,119 +1,115 @@
require 'addressable/uri'
require 'set'
module Faraday
class Connection
module Options
def load_error() @load_error end
def load_error=(v) @load_error = v end
def supports_async() @supports_async end
def supports_async=(v) @supports_async = v end
def loaded?() !@load_error end
alias supports_async? supports_async
end
include Addressable, Rack::Utils
include Addressable
HEADERS = Hash.new { |h, k| k.respond_to?(:to_str) ? k : k.to_s.capitalize }.update \
:content_type => "Content-Type",
:content_length => "Content-Length",
:accept_charset => "Accept-Charset",
:accept_encoding => "Accept-Encoding"
HEADERS.values.each { |v| v.freeze }
attr_accessor :host, :port, :scheme, :params, :headers
attr_reader :path_prefix
METHODS = Set.new [:get, :post, :put, :delete, :head]
METHODS_WITH_BODIES = Set.new [:post, :put]
attr_accessor :host, :port, :scheme, :params, :headers, :parallel_manager
attr_reader :path_prefix, :builder
# :url
# :params
# :headers
# :response
def initialize(url = nil, options = {})
def initialize(url = nil, options = {}, &block)
if url.is_a?(Hash)
options = url
url = options[:url]
end
@response_class = options[:response]
@params = options[:params] || {}
@headers = options[:headers] || {}
@headers = HeaderHash.new
@params = {}
@parallel_manager = options[:parallel]
self.url_prefix = url if url
end
def url_prefix=(url)
uri = URI.parse(url)
self.scheme = uri.scheme
self.host = uri.host
self.port = uri.port
self.path_prefix = uri.path
end
# Override in a subclass, or include an adapter
#
# def _post(uri, post_params, headers)
# end
#
def post(uri, params = {}, headers = {})
_post build_uri(uri), build_params(params), build_headers(headers)
end
# Override in a subclass, or include an adapter
#
# def _put(uri, post_params, headers)
# end
#
def put(uri, params = {}, headers = {})
_put build_uri(uri), build_params(params), build_headers(headers)
end
# Override in a subclass, or include an adapter
#
# def _delete(uri, headers)
# end
#
def delete(uri, params = {}, headers = {})
_delete build_uri(uri, build_params(params)), build_headers(headers)
end
# Override in a subclass, or include an adapter
#
# def _get(uri, headers)
# end
#
def get(url, params = nil, headers = nil)
uri = build_uri(url, build_params(params))
_get(uri, build_headers(headers))
end
def request_class
@request_class || Request::PostRequest
end
def response_class
@response_class || Response
end
def response_class=(v)
if v.respond_to?(:loaded?) && !v.loaded?
raise ArgumentError, "The response class: #{v.inspect} does not appear to be loaded."
merge_params @params, options[:params] if options[:params]
merge_headers @headers, options[:headers] if options[:headers]
if block
@builder = Builder.create_with_inner_app(&block)
end
@response_class = v
end
def request_class=(v)
if v.respond_to?(:loaded?) && !v.loaded?
raise ArgumentError, "The request class: #{v.inspect} does not appear to be loaded."
def get(url = nil, headers = nil, &block)
run_request :get, url, nil, headers, &block
end
def post(url = nil, body = nil, headers = nil, &block)
run_request :post, url, body, headers, &block
end
def put(url = nil, body = nil, headers = nil, &block)
run_request :put, url, body, headers, &block
end
def head(url = nil, headers = nil, &block)
run_request :head, url, nil, headers, &block
end
def delete(url = nil, headers = nil, &block)
run_request :delete, url, nil, headers, &block
end
def run_request(method, url, body, headers)
if !METHODS.include?(method)
raise ArgumentError, "unknown http method: #{method}"
end
Request.run(self, method) do |req|
req.url(url) if url
req.headers.update(headers) if headers
req.body = body if body
yield req if block_given?
end
@request_class = v
end
def in_parallel?
!!@parallel_manager
end
def in_parallel(options = {})
@parallel_manager = true
def in_parallel(manager)
@parallel_manager = manager
yield
@parallel_manager = false
@parallel_manager && @parallel_manager.run
ensure
@parallel_manager = nil
end
def setup_parallel_manager(options = {})
# return the assembled Rack application for this instance.
def to_app
@builder.to_app
end
def run_parallel_requests
# Parses the giving url with Addressable::URI and stores the individual
# components in this connection. These components serve as defaults for
# requests made by this connection.
#
# conn = Faraday::Connection.new { ... }
# conn.url_prefix = "https://sushi.com/api"
# conn.scheme # => https
# conn.path_prefix # => "/api"
#
# conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri
#
def url_prefix=(url)
uri = URI.parse(url)
self.scheme = uri.scheme
self.host = uri.host
self.port = uri.port
self.path_prefix = uri.path
if uri.query && !uri.query.empty?
merge_params @params, parse_query(uri.query)
end
end
# Ensures that the path prefix always has a leading / and no trailing /
def path_prefix=(value)
if value
value.chomp! "/"
@ -122,54 +118,65 @@ module Faraday
@path_prefix = value
end
def build_uri(url, params = nil)
uri = URI.parse(url)
# Takes a relative url for a request and combines it with the defaults
# set on the connection instance.
#
# conn = Faraday::Connection.new { ... }
# conn.url_prefix = "https://sushi.com/api?token=abc"
# conn.scheme # => https
# conn.path_prefix # => "/api"
#
# conn.build_url("nigiri?page=2") # => https://sushi.com/api/nigiri?token=abc&page=2
# conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
#
def build_url(url, params = nil)
uri = URI.parse(url.to_s)
uri.scheme ||= @scheme
uri.host ||= @host
uri.port ||= @port
if @path_prefix && uri.path !~ /^\//
uri.path = "#{@path_prefix.size > 1 ? @path_prefix : nil}/#{uri.path}"
end
if params && !params.empty?
uri.query = params_to_query(params)
end
replace_query(uri, params)
uri
end
def path_for(uri)
uri.path.tap do |s|
s << "?#{uri.query}" if uri.query
s << "##{uri.fragment}" if uri.fragment
def replace_query(uri, params)
url_params = @params.dup
if uri.query && !uri.query.empty?
merge_params url_params, parse_query(uri.query)
end
if params && !params.empty?
merge_params url_params, params
end
uri.query = url_params.empty? ? nil : build_query(url_params)
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
def build_params(existing)
build_hash :params, existing
end
def build_headers(existing)
build_hash(:headers, existing).tap do |headers|
headers.keys.each do |key|
headers[key] = headers.delete(key).to_s
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
def build_hash(method, existing)
existing ? send(method).merge(existing) : send(method)
end
def params_to_query(params)
params.inject([]) do |memo, (key, val)|
memo << "#{escape_for_querystring(key)}=#{escape_for_querystring(val)}"
end.join("&")
end
# Some servers convert +'s in URL query params to spaces.
# Go ahead and encode it.
def escape_for_querystring(s)
URI.encode_component(s.to_s, Addressable::URI::CharacterClasses::QUERY).tap do |escaped|
escaped.gsub! /\+/, "%2B"
# 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
end

View File

@ -1,13 +0,0 @@
module Faraday
module Loadable
def self.extended mod
class << mod
attr_accessor :load_error
end
end
def self.loaded?
load_error.nil?
end
end
end

54
lib/faraday/middleware.rb Normal file
View File

@ -0,0 +1,54 @@
module Faraday
class Middleware
include Rack::Utils
class << self
attr_accessor :load_error, :supports_parallel_requests
alias supports_parallel_requests? supports_parallel_requests
# valid parallel managers should respond to #run with no parameters.
# otherwise, return a short wrapper around it.
def setup_parallel_manager(options = {})
nil
end
end
def self.loaded?
@load_error.nil?
end
def initialize(app = nil)
@app = app
end
# assume that query and fragment are already encoded properly
def full_path_for(path, query = nil, fragment = nil)
full_path = path.dup
if query && !query.empty?
full_path << "?#{query}"
end
if fragment && !fragment.empty?
full_path << "##{fragment}"
end
full_path
end
def process_body_for_request(env)
# if it's a string, pass it through
return if env[:body].nil? || env[:body].empty? || !env[:body].respond_to?(:each_key)
env[:request_headers]['Content-Type'] ||= 'application/x-www-form-urlencoded'
env[:body] = create_form_params(env[:body])
end
def create_form_params(params, base = nil)
[].tap do |result|
params.each_key do |key|
key_str = base ? "#{base}[#{key}]" : key
value = params[key]
wee = (value.kind_of?(Hash) ? create_form_params(value, key_str) : "#{key_str}=#{escape(value.to_s)}")
result << wee
end
end.join("&")
end
end
end

77
lib/faraday/request.rb Normal file
View File

@ -0,0 +1,77 @@
module Faraday
# Used to setup urls, params, headers, and the request body in a sane manner.
#
# @connection.post do |req|
# req.url 'http://localhost', 'a' => '1' # 'http://localhost?a=1'
# req.headers['b'] = '2' # header
# req['b'] = '2' # header
# req.body = 'abc'
# end
#
class Request < Struct.new(:path, :params, :headers, :body)
extend AutoloadHelper
autoload_all 'faraday/request',
:Yajl => 'yajl',
:ActiveSupportJson => 'active_support_json'
register_lookup_modules \
:yajl => :Yajl,
:activesupport_json => :ActiveSupportJson,
:rails_json => :ActiveSupportJson,
:active_support_json => :ActiveSupportJson
def self.run(connection, request_method)
req = create
yield req if block_given?
req.run(connection, request_method)
end
def self.create
req = new(nil, {}, {}, nil)
yield req if block_given?
req
end
def url(path, params = {})
self.path = path
self.params = params
end
def [](key)
headers[key]
end
def []=(key, value)
headers[key] = value
end
# ENV Keys
# :method - a symbolized request method (:get, :post)
# :body - the request body that will eventually be converted to a string.
# :url - Addressable::URI instance of the URI for the current request.
# :status - HTTP response status code
# :request_headers - hash of HTTP Headers to be sent to the server
# :response_headers - Hash of HTTP headers from the server
# :parallel_manager - sent if the connection is in parallel mode
# :response - the actual response object that stores the rack response
def to_env_hash(connection, request_method)
env_headers = connection.headers.dup
env_params = connection.params.dup
connection.merge_headers env_headers, headers
connection.merge_params env_params, params
{ :method => request_method,
:body => body,
:url => connection.build_url(path, env_params),
:request_headers => env_headers,
:parallel_manager => connection.parallel_manager,
:response => Response.new}
end
def run(connection, request_method)
app = connection.to_app
env = to_env_hash(connection, request_method)
app.call(env)
end
end
end

View File

@ -0,0 +1,20 @@
module Faraday
class Request::ActiveSupportJson < Faraday::Middleware
begin
if !defined?(ActiveSupport::JSON)
require 'active_support'
end
rescue LoadError => e
self.load_error = e
end
def call(env)
env[:request_headers]['Content-Type'] = 'application/json'
if env[:body] && !env[:body].respond_to?(:to_str)
env[:body] = ActiveSupport::JSON.encode env[:body]
end
@app.call env
end
end
end

View File

@ -1,30 +0,0 @@
module Faraday
module Request
class PostRequest
extend Loadable
def initialize params, headers={}
@params = params
@headers = headers
end
def headers
@headers.merge('Content-Type' => 'application/x-www-form-urlencoded')
end
def body
create_post_params @params
end
private
def create_post_params(params, base = "")
[].tap do |toreturn|
params.each_key do |key|
keystring = base == '' ? key : "#{base}[#{key}]"
toreturn << (params[key].kind_of?(Hash) ? create_post_params(params[key], keystring) : "#{keystring}=#{CGI.escape(params[key].to_s)}")
end
end.join('&')
end
end
end
end

View File

@ -0,0 +1,18 @@
module Faraday
class Request::Yajl < Faraday::Middleware
begin
require 'yajl'
rescue LoadError => e
self.load_error = e
end
def call(env)
env[:request_headers]['Content-Type'] = 'application/json'
if env[:body] && !env[:body].respond_to?(:to_str)
env[:body] = Yajl::Encoder.encode env[:body]
end
@app.call env
end
end
end

View File

@ -1,27 +0,0 @@
module Faraday
module Request
class YajlRequest
extend Loadable
begin
require 'yajl'
def initialize params, headers={}
@params = params
@headers = headers
end
def headers
@headers.merge('Content-Type' => 'application/json')
end
# TODO streaming
def body
Yajl::Encoder.encode @params
end
rescue LoadError => e
self.load_error = e
end
end
end
end

View File

@ -1,38 +1,53 @@
module Faraday
class Response < Struct.new(:headers, :body)
extend Loadable
class Response
class Middleware < Faraday::Middleware
self.load_error = :abstract
# Use a response callback in case the request is parallelized.
#
# env[:response].on_complete do |finished_env|
# finished_env[:body] = do_stuff_to(finished_env[:body])
# end
#
def self.register_on_complete(env)
end
def call(env)
self.class.register_on_complete(env)
@app.call env
end
end
extend AutoloadHelper
autoload_all 'faraday/response',
:YajlResponse => 'yajl_response'
def initialize(headers = nil, body = nil)
super(headers || {}, body)
if block_given?
yield self
processed!
end
autoload_all 'faraday/response',
:Yajl => 'yajl',
:ActiveSupportJson => 'active_support_json'
register_lookup_modules \
:yajl => :Yajl,
:activesupport_json => :ActiveSupportJson,
:rails_json => :ActiveSupportJson,
:active_support_json => :ActiveSupportJson
attr_accessor :status, :headers, :body
def initialize
@status, @headers, @body = nil, nil, nil
@on_complete_callbacks = []
end
# TODO: process is a funky name. change it
# processes a chunk of the streamed body.
def process(chunk)
if !body
self.body = []
end
body << chunk
def on_complete(&block)
@on_complete_callbacks << block
end
# Assume the given content is the full body, and not streamed.
def process!(full_body)
process(full_body)
processed!
def finish(env)
@on_complete_callbacks.each { |c| c.call(env) }
@status, @headers, @body = env[:status], env[:response_headers], env[:body]
self
end
# Signals the end of streamed content. Do whatever you need to clean up
# the streamed body.
def processed!
self.body = body.join if body.respond_to?(:join)
def success?
status == 200
end
end
end

View File

@ -0,0 +1,22 @@
module Faraday
class Response::ActiveSupportJson < Response::Middleware
begin
if !defined?(ActiveSupport::JSON)
require 'active_support'
end
def self.register_on_complete(env)
env[:response].on_complete do |finished_env|
finished_env[:body] = ActiveSupport::JSON.decode(finished_env[:body])
end
end
rescue LoadError => e
self.load_error = e
end
def initialize(app)
super
@parser = nil
end
end
end

View File

@ -0,0 +1,20 @@
module Faraday
class Response::Yajl < Response::Middleware
begin
require 'yajl'
def self.register_on_complete(env)
env[:response].on_complete do |finished_env|
finished_env[:body] = Yajl::Parser.parse(finished_env[:body])
end
end
rescue LoadError => e
self.load_error = e
end
def initialize(app)
super
@parser = nil
end
end
end

View File

@ -1,35 +0,0 @@
module Faraday
class Response
class YajlResponse < Response
attr_reader :body
begin
require 'yajl'
def initialize(headers = nil, body = nil)
super
@parser = nil
end
def process(chunk)
if !@parser
@parser = Yajl::Parser.new
@parser.on_parse_complete = method(:object_parsed)
end
@parser << chunk
end
def processed!
@parser = nil
end
def object_parsed(obj)
@body = obj
end
rescue LoadError => e
self.load_error = e
end
end
end
end

View File

@ -1,5 +0,0 @@
module Faraday
class TestConnection < Connection
include Faraday::Adapter::MockRequest
end
end

View File

@ -1,26 +0,0 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'helper'))
if Faraday::Adapter::Typhoeus.loaded?
class TyphoeusTest < Faraday::TestCase
describe "#parse_response_headers" do
before do
@conn = Object.new.extend(Faraday::Adapter::Typhoeus)
end
it "leaves http status line out" do
headers = @conn.parse_response_headers("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
assert_equal %w(content-type), headers.keys
end
it "parses lower-cased header name and value" do
headers = @conn.parse_response_headers("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
assert_equal 'text/html', headers['content-type']
end
it "parses lower-cased header name and value with colon" do
headers = @conn.parse_response_headers("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nLocation: http://sushi.com/\r\n\r\n")
assert_equal 'http://sushi.com/', headers['location']
end
end
end
end

View File

@ -1,118 +0,0 @@
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
class AdapterTest < Faraday::TestCase
before do
@connection = Faraday::Connection.new(LIVE_SERVER)
end
Faraday::Adapter.loaded_adapters.each do |adapter|
describe "with #{adapter} adapter" do
before do
@connection.extend adapter
end
describe "#delete" do
it "retrieves the response body with YajlResponse" do
@connection.response_class = Faraday::Response::YajlResponse
assert_equal({'deleted' => true},
@connection.delete('delete_with_json').body)
end
it "send url-encoded params" do
assert_equal('foobar', @connection.delete('delete_with_params', 'deleted' => 'foobar').body)
end
end
it "passes params" do
@connection.params = {:a => 1}
assert_equal "params[:a] == 1", @connection.get('params').body
end
it "passes headers" do
@connection.headers = {"X-Test" => 1}
assert_equal "env[HTTP_X_TEST] == 1", @connection.get('headers').body
end
it "retrieves the response body" do
assert_equal 'hello world', @connection.get('hello_world').body
end
describe "#put" do
it "sends params" do
assert_equal 'hello zack', @connection.put('hello', 'name' => 'zack').body
end
it "retrieves the response body with YajlResponse" do
@connection.response_class = Faraday::Response::YajlResponse
assert_equal({'name' => 'zack'},
@connection.put('echo_name', 'name' => 'zack').body)
end
end
describe "#post" do
it "sends params" do
assert_equal 'hello zack', @connection.post('hello', 'name' => 'zack').body
end
it "retrieves the response body with YajlResponse" do
@connection.response_class = Faraday::Response::YajlResponse
assert_equal({'name' => 'zack'},
@connection.post('echo_name', 'name' => 'zack').body)
end
end
describe "#get" do
it "raises on 404" do
assert_raise(Faraday::Error::ResourceNotFound) { @connection.get('/nothing') }
end
it "retrieves the response body" do
assert_equal 'hello world', @connection.get('hello_world').body
end
it "send url-encoded params" do
assert_equal('hello zack', @connection.get('hello', 'name' => 'zack').body)
end
it "retrieves the response body with YajlResponse" do
@connection.response_class = Faraday::Response::YajlResponse
assert_equal [1,2,3], @connection.get('json').body
end
it "retrieves the response headers" do
assert_equal 'text/html', @connection.get('hello_world').headers['content-type']
end
end
end
describe "async requests" do
before do
@connection.extend adapter
end
it "clears parallel manager after running a single request" do
assert !@connection.in_parallel?
resp = @connection.get('hello_world')
assert !@connection.in_parallel?
assert_equal 'hello world', @connection.get('hello_world').body
end
it "uses parallel manager to run multiple json requests" do
resp1, resp2 = nil, nil
@connection.response_class = Faraday::Response::YajlResponse
@connection.in_parallel do
resp1 = @connection.get('json')
resp2 = @connection.get('json')
assert @connection.in_parallel?
if adapter.supports_async?
assert_nil resp1.body
assert_nil resp2.body
end
end
assert !@connection.in_parallel?
assert_equal [1,2,3], resp1.body
assert_equal [1,2,3], resp2.body
end
end
end
end

157
test/adapters/live_test.rb Normal file
View File

@ -0,0 +1,157 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'helper'))
if Faraday::TestCase::LIVE_SERVER
module Adapters
class LiveTest < Faraday::TestCase
Faraday::Adapter.all_loaded_constants.each do |adapter|
describe "with #{adapter} adapter" do
before do
@connection = Faraday::Connection.new LIVE_SERVER do
use adapter
end
end
describe "#get" do
it "raises on 404" do
assert_raise(Faraday::Error::ResourceNotFound) { @connection.get('/nothing') }
end
it "retrieves the response body" do
assert_equal 'hello world', @connection.get('hello_world').body
end
it "send url-encoded params" do
resp = @connection.get do |req|
req.url 'hello', 'name' => 'zack'
end
assert_equal('hello zack', resp.body)
end
it "retrieves the response headers" do
assert_equal 'text/html', @connection.get('hello_world').headers['content-type']
end
end
describe "#post" do
it "raises on 404" do
assert_raise(Faraday::Error::ResourceNotFound) { @connection.post('/nothing') }
end
it "send url-encoded params" do
resp = @connection.post do |req|
req.url 'echo_name'
req.body = {'name' => 'zack'}
end
assert_equal %("zack"), resp.body
end
it "send url-encoded nested params" do
resp = @connection.post do |req|
req.url 'echo_name'
req.body = {'name' => {'first' => 'zack'}}
end
assert_equal %({"first"=>"zack"}), resp.body
end
it "retrieves the response headers" do
assert_equal 'text/html', @connection.post('echo_name').headers['content-type']
end
end
# http://github.com/toland/patron/issues/#issue/9
if ENV['FORCE'] || adapter != Faraday::Adapter::Patron
describe "#put" do
it "raises on 404" do
assert_raise(Faraday::Error::ResourceNotFound) { @connection.put('/nothing') }
end
it "send url-encoded params" do
resp = @connection.put do |req|
req.url 'echo_name'
req.body = {'name' => 'zack'}
end
assert_equal %("zack"), resp.body
end
it "send url-encoded nested params" do
resp = @connection.put do |req|
req.url 'echo_name'
req.body = {'name' => {'first' => 'zack'}}
end
assert_equal %({"first"=>"zack"}), resp.body
end
it "retrieves the response headers" do
assert_equal 'text/html', @connection.put('echo_name').headers['content-type']
end
end
end
# http://github.com/pauldix/typhoeus/issues#issue/7
if ENV['FORCE'] || adapter != Faraday::Adapter::Typhoeus
describe "#head" do
it "raises on 404" do
assert_raise(Faraday::Error::ResourceNotFound) { @connection.head('/nothing') }
end
it "send url-encoded params" do
resp = @connection.head do |req|
req.url 'hello', 'name' => 'zack'
end
assert_equal 'text/html', resp.headers['content-type']
end
it "retrieves no response body" do
assert_equal '', @connection.head('hello_world').body.to_s
end
it "retrieves the response headers" do
assert_equal 'text/html', @connection.head('hello_world').headers['content-type']
end
end
end
describe "#delete" do
it "raises on 404" do
assert_raise(Faraday::Error::ResourceNotFound) { @connection.delete('/nothing') }
end
it "retrieves the response headers" do
assert_equal 'text/html', @connection.delete('delete_with_json').headers['content-type']
end
it "retrieves the body" do
assert_match /deleted/, @connection.delete('delete_with_json').body
end
end
describe "async requests" do
it "clears parallel manager after running a single request" do
assert !@connection.in_parallel?
resp = @connection.get('hello_world')
assert !@connection.in_parallel?
assert_equal 'hello world', @connection.get('hello_world').body
end
it "uses parallel manager to run multiple json requests" do
resp1, resp2 = nil, nil
@connection.in_parallel(adapter.setup_parallel_manager) do
resp1 = @connection.get('json')
resp2 = @connection.get('json')
if adapter.supports_parallel_requests?
assert @connection.in_parallel?
assert_nil resp1.body
assert_nil resp2.body
end
end
assert !@connection.in_parallel?
assert_equal '[1,2,3]', resp1.body
assert_equal '[1,2,3]', resp2.body
end
end
end
end
end
end
end

View File

@ -0,0 +1,28 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'helper'))
module Adapters
class TestMiddleware < Faraday::TestCase
describe "Test Middleware with simple path" do
before :all do
@stubs = Faraday::Adapter::Test::Stubs.new
@conn = Faraday::Connection.new do |builder|
builder.adapter :test, @stubs
end
@stubs.get('/hello') { [200, {'Content-Type' => 'text/html'}, 'hello'] }
@resp = @conn.get('/hello')
end
it "sets status" do
assert_equal 200, @resp.status
end
it "sets headers" do
assert_equal 'text/html', @resp.headers['Content-Type']
end
it "sets body" do
assert_equal 'hello', @resp.body
end
end
end
end

View File

@ -0,0 +1,28 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'helper'))
if Faraday::Adapter::Typhoeus.loaded?
module Adapters
class TestTyphoeus < Faraday::TestCase
describe "#parse_response_headers" do
before do
@adapter = Faraday::Adapter::Typhoeus.new
end
it "leaves http status line out" do
headers = @adapter.parse_response_headers("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
assert_equal %w(content-type), headers.keys
end
it "parses lower-cased header name and value" do
headers = @adapter.parse_response_headers("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
assert_equal 'text/html', headers['content-type']
end
it "parses lower-cased header name and value with colon" do
headers = @adapter.parse_response_headers("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nLocation: http://sushi.com/\r\n\r\n")
assert_equal 'http://sushi.com/', headers['location']
end
end
end
end
end

View File

@ -0,0 +1,49 @@
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
class TestConnectionApps < Faraday::TestCase
class TestAdapter
def initialize(app)
@app = app
end
def call(env)
[200, {}, env[:test]]
end
end
class TestMiddleWare
def initialize(app)
@app = app
end
def call(env)
env[:test] = 'hi'
@app.call(env)
end
end
before do
@conn = Faraday::Connection.new do |b|
b.use TestMiddleWare
b.use TestAdapter
end
end
describe "#builder" do
it "is built from Faraday::Connection constructor" do
assert_kind_of Faraday::Builder, @conn.builder
assert_equal 3, @conn.builder.handlers.size
end
it "adds middleware to the Builder stack" do
assert_kind_of TestMiddleWare, @conn.builder.handlers[2].call(nil)
assert_kind_of TestAdapter, @conn.builder.handlers[1].call(nil)
end
end
describe "#to_app" do
it "returns rack-compatible object" do
assert @conn.to_app.respond_to?(:call)
end
end
end

View File

@ -1,6 +1,6 @@
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
class ConnectionTest < Faraday::TestCase
class TestConnection < Faraday::TestCase
describe "#initialize" do
it "parses @host out of given url" do
conn = Faraday::Connection.new "http://sushi.com"
@ -39,92 +39,118 @@ class ConnectionTest < Faraday::TestCase
it "stores default params from options" do
conn = Faraday::Connection.new :params => {:a => 1}
assert_equal 1, conn.params[:a]
assert_equal 1, conn.params['a']
end
it "stores default params from uri" do
conn = Faraday::Connection.new "http://sushi.com/fish?a=1", :params => {'b' => '2'}
assert_equal '1', conn.params['a']
assert_equal '2', conn.params['b']
end
it "stores default headers from options" do
conn = Faraday::Connection.new :headers => {:a => 1}
assert_equal 1, conn.headers[:a]
assert_equal '1', conn.headers['A']
end
end
describe "#build_uri" do
describe "#build_url" do
it "uses Connection#host as default URI host" do
conn = Faraday::Connection.new
conn.host = 'sushi.com'
uri = conn.build_uri("/sake.html")
uri = conn.build_url("/sake.html")
assert_equal 'sushi.com', uri.host
end
it "uses Connection#port as default URI port" do
conn = Faraday::Connection.new
conn.port = 23
uri = conn.build_uri("http://sushi.com")
uri = conn.build_url("http://sushi.com")
assert_equal 23, uri.port
end
it "uses Connection#scheme as default URI scheme" do
conn = Faraday::Connection.new 'http://sushi.com'
uri = conn.build_uri("/sake.html")
uri = conn.build_url("/sake.html")
assert_equal 'http', uri.scheme
end
it "uses Connection#path_prefix to customize the path" do
conn = Faraday::Connection.new
conn.path_prefix = '/fish'
uri = conn.build_uri("sake.html")
uri = conn.build_url("sake.html")
assert_equal '/fish/sake.html', uri.path
end
it "uses '/' Connection#path_prefix to customize the path" do
conn = Faraday::Connection.new
conn.path_prefix = '/'
uri = conn.build_uri("sake.html")
uri = conn.build_url("sake.html")
assert_equal '/sake.html', uri.path
end
it "forces Connection#path_prefix to be absolute" do
conn = Faraday::Connection.new
conn.path_prefix = 'fish'
uri = conn.build_uri("sake.html")
uri = conn.build_url("sake.html")
assert_equal '/fish/sake.html', uri.path
end
it "ignores Connection#path_prefix trailing slash" do
conn = Faraday::Connection.new
conn.path_prefix = '/fish/'
uri = conn.build_uri("sake.html")
uri = conn.build_url("sake.html")
assert_equal '/fish/sake.html', uri.path
end
it "allows absolute URI to ignore Connection#path_prefix" do
conn = Faraday::Connection.new
conn.path_prefix = '/fish'
uri = conn.build_uri("/sake.html")
uri = conn.build_url("/sake.html")
assert_equal '/sake.html', uri.path
end
it "parses url/params into #path" do
conn = Faraday::Connection.new
uri = conn.build_uri("http://sushi.com/sake.html")
uri = conn.build_url("http://sushi.com/sake.html")
assert_equal '/sake.html', uri.path
end
it "parses url/params into #query" do
conn = Faraday::Connection.new
uri = conn.build_uri("http://sushi.com/sake.html", 'a[b]' => '1 + 2')
uri = conn.build_url("http://sushi.com/sake.html", 'a[b]' => '1 + 2')
assert_equal "a%5Bb%5D=1%20%2B%202", uri.query
end
it "mashes default params and given params together" do
conn = Faraday::Connection.new 'http://sushi.com/api?token=abc', :params => {'format' => 'json'}
url = conn.build_url("nigiri?page=1", :limit => 5)
assert_match /limit=5/, url.query
assert_match /page=1/, url.query
assert_match /format=json/, url.query
assert_match /token=abc/, url.query
end
it "overrides default params with given params" do
conn = Faraday::Connection.new 'http://sushi.com/api?token=abc', :params => {'format' => 'json'}
url = conn.build_url("nigiri?page=1", :limit => 5, :token => 'def', :format => 'xml')
assert_match /limit=5/, url.query
assert_match /page=1/, url.query
assert_match /format=xml/, url.query
assert_match /token=def/, url.query
assert_no_match /format=json/, url.query
assert_no_match /token=abc/, url.query
end
it "parses url into #host" do
conn = Faraday::Connection.new
uri = conn.build_uri("http://sushi.com/sake.html")
uri = conn.build_url("http://sushi.com/sake.html")
assert_equal "sushi.com", uri.host
end
it "parses url into #port" do
conn = Faraday::Connection.new
uri = conn.build_uri("http://sushi.com/sake.html")
uri = conn.build_url("http://sushi.com/sake.html")
assert_nil uri.port
end
end
@ -132,7 +158,10 @@ class ConnectionTest < Faraday::TestCase
describe "#params_to_query" do
it "converts hash of params to URI-escaped query string" do
conn = Faraday::Connection.new
assert_equal "a%5Bb%5D=1%20%2B%202", conn.params_to_query('a[b]' => '1 + 2')
class << conn
public :build_query
end
assert_equal "a%5Bb%5D=1%20%2B%202", conn.build_query('a[b]' => '1 + 2')
end
end
end
end

35
test/env_test.rb Normal file
View File

@ -0,0 +1,35 @@
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
class TestEnv < Faraday::TestCase
describe "Request#create" do
before :all do
@conn = Faraday::Connection.new :url => 'http://sushi.com/api'
@input = {
:body => 'abc',
:headers => {'Server' => 'Faraday'}}
@env_setup = Faraday::Request.create do |req|
req.url 'foo.json', 'a' => 1
req['Server'] = 'Faraday'
req.body = @input[:body]
end
@env = @env_setup.to_env_hash(@conn, :get)
end
it "stores method in :method" do
assert_equal :get, @env[:method]
end
it "stores Addressable::URI in :url" do
assert_equal 'http://sushi.com/api/foo.json?a=1', @env[:url].to_s
end
it "stores headers in :headers" do
assert_kind_of Rack::Utils::HeaderHash, @env[:request_headers]
assert_equal @input[:headers], @env[:request_headers]
end
it "stores body in :body" do
assert_equal @input[:body], @env[:body]
end
end
end

View File

@ -1,6 +1,5 @@
require 'rubygems'
require 'context'
require 'ruby-debug'
if ENV['LEFTRIGHT']
require 'leftright'
end
@ -17,6 +16,10 @@ end
module Faraday
class TestCase < Test::Unit::TestCase
LIVE_SERVER = 'http://localhost:4567'
LIVE_SERVER = case ENV['LIVE']
when /^http/ then ENV['LIVE']
when nil then nil
else 'http://localhost:4567'
end
end
end

View File

@ -17,16 +17,12 @@ get '/hello' do
"hello #{params[:name]}"
end
put '/hello' do
"hello #{params[:name]}"
end
post '/echo_name' do
%/{"name":#{params[:name].inspect}}/
params[:name].inspect
end
put '/echo_name' do
%/{"name":#{params[:name].inspect}}/
params[:name].inspect
end
delete '/delete_with_json' do
@ -36,12 +32,3 @@ end
delete '/delete_with_params' do
params[:deleted]
end
get '/params' do
%(params[:a] == #{params[:a]})
end
get "/headers" do
%(env[HTTP_X_TEST] == #{env["HTTP_X_TEST"]})
end

View File

@ -0,0 +1,19 @@
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
class RequestMiddlewareTest < Faraday::TestCase
describe "encoding json" do
[:yajl, :rails_json].each do |key|
encoder = Faraday::Request.lookup_module(key)
next if !encoder.loaded?
it "uses #{encoder}" do
@connection = Faraday::Connection.new do |b|
b.use encoder
b.adapter :test do |stub|
stub.post('echo_body', '{"a":1}') { |env| [200, {'Content-Type' => 'text/html'}, env[:body]] }
end
end
assert_equal %({"a":1}), @connection.post('echo_body', :a => 1).body
end
end
end
end

View File

@ -0,0 +1,21 @@
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
class ResponseMiddlewareTest < Faraday::TestCase
describe "parsing json" do
[:yajl, :rails_json].each do |key|
parser = Faraday::Response.lookup_module(key)
next if !parser.loaded?
it "uses #{parser}" do
@connection = Faraday::Connection.new do |b|
b.adapter :test do |stub|
stub.get('json') { [200, {'Content-Type' => 'text/html'}, "[1,2,3]"] }
end
b.use parser
end
response = @connection.get('json')
assert response.success?
assert_equal [1,2,3], response.body
end
end
end
end

View File

@ -1,34 +0,0 @@
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
class ResponseTest < Faraday::TestCase
describe "unloaded response class" do
it "is not allowed to be set" do
resp_class = Object.new
def resp_class.loaded?() false end
conn = Faraday::Connection.new
assert_raises ArgumentError do
conn.response_class = resp_class
end
end
end
describe "TestConnection#get with default Faraday::Response class" do
it "returns Faraday::Response" do
conn = Faraday::TestConnection.new do |stub|
stub.get('/hello') { [200, {}, 'hello world']}
end
resp = conn.get('/hello')
assert_equal 'hello world', resp.body
end
end
describe "TestConnection#get with Faraday::YajlResponse class" do
it "returns string body" do
conn = Faraday::TestConnection.new do |stub|
stub.get('/hello') { [200, {}, '[1,2,3]']}
end
conn.response_class = Faraday::Response::YajlResponse
assert_equal [1,2,3], conn.get('/hello').body
end
end
end