weary 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,7 @@ require 'addressable/uri'
2
2
  require 'future'
3
3
  require 'rack'
4
4
 
5
+ require 'weary/requestable'
5
6
  require 'weary/env'
6
7
  require 'weary/adapter'
7
8
 
@@ -15,12 +16,13 @@ module Weary
15
16
  # Rack application. Because the Request performs so much manipulation on
16
17
  # the Rack env, you can attach middleware to it to act on its mutated env.
17
18
  class Request
19
+ include Weary::Requestable
20
+
18
21
  attr_reader :uri
19
22
 
20
23
  def initialize(url, method='GET')
21
24
  self.uri = url
22
25
  self.method = method
23
- @middlewares = []
24
26
  yield self if block_given?
25
27
  end
26
28
 
@@ -38,7 +40,7 @@ module Weary
38
40
  # Returns an Array of three items; a Rack tuple.
39
41
  def call(environment)
40
42
  app = adapter.new
41
- middlewares = @middlewares
43
+ middlewares = @middlewares || []
42
44
  stack = Rack::Builder.new do
43
45
  middlewares.each do |middleware|
44
46
  klass, *args = middleware
@@ -64,20 +66,11 @@ module Weary
64
66
  @method = verb.to_s.upcase
65
67
  end
66
68
 
67
- def headers(hash=nil)
68
- @headers = hash unless hash.nil?
69
- @headers ||= {}
70
- end
71
-
72
- def user_agent(agent)
73
- headers.update 'User-Agent' => agent
74
- end
75
-
76
69
  def params(parameters=nil)
77
70
  if !parameters.nil?
78
71
  if ["POST", "PUT"].include? method
79
72
  @body = query_params_from_hash(parameters)
80
- body StringIO.new(@body)
73
+ body stringio_encode(@body)
81
74
  use Weary::Middleware::ContentType
82
75
  else
83
76
  uri.query_values = parameters
@@ -89,18 +82,13 @@ module Weary
89
82
 
90
83
  def json(parameters)
91
84
  json = MultiJson.encode(parameters)
92
- body StringIO.new(json)
85
+ body stringio_encode(json)
93
86
  json
94
87
  end
95
88
 
96
89
  def body(io=nil)
97
90
  @attachment = io unless io.nil?
98
- @attachment ||= StringIO.new('')
99
- end
100
-
101
- def adapter(connection=nil)
102
- @connection = connection unless connection.nil?
103
- @connection ||= Weary::Adapter::NetHttp
91
+ @attachment ||= stringio_encode("")
104
92
  end
105
93
 
106
94
  def basic_auth(*credentials)
@@ -133,10 +121,6 @@ module Weary
133
121
  end
134
122
  end
135
123
 
136
- def use(middleware, *args, &block)
137
- @middlewares << [middleware, args.compact, block]
138
- end
139
-
140
124
  private
141
125
 
142
126
  def query_params_from_hash(hash)
@@ -153,5 +137,12 @@ module Weary
153
137
  'rack.run_once' => false }
154
138
  end
155
139
 
140
+ def stringio_encode(content)
141
+ io = StringIO.new(content)
142
+ io.binmode
143
+ io.set_encoding "ASCII-8BIT" if io.respond_to? :set_encoding
144
+ io
145
+ end
146
+
156
147
  end
157
148
  end
@@ -0,0 +1,69 @@
1
+ module Weary
2
+ module Requestable
3
+
4
+ # An accessor to set a Weary::Adapter to use to forward the connection.
5
+ # When a request is made, it will be passed along to this adapter to
6
+ # get the eventual Response. Defaults to Weary::Adapter::NetHttp.
7
+ #
8
+ # connection - An optional Weary::Adapter.
9
+ #
10
+ # Returns the Weary::Adapter.
11
+ def adapter(connection=nil)
12
+ @connection = connection unless connection.nil?
13
+ @connection ||= Weary::Adapter::NetHttp
14
+ end
15
+
16
+ # An accessor to set HTTP request headers.
17
+ #
18
+ # hash - An optional Hash of key/value pairs that are sent as HTTP
19
+ # request headers when a resource's request is performed.
20
+ #
21
+ # Returns a Hash of the headers.
22
+ def headers(hash=nil)
23
+ @headers = hash unless hash.nil?
24
+ @headers ||= {}
25
+ end
26
+
27
+ # Send a Rack middleware to be used by the Request.
28
+ #
29
+ # middleware - An object that implements the rack middleware interface.
30
+ # args - Zero or more optional arguments to send to the middleware.
31
+ # block - An optional block to send to the middleware.
32
+ #
33
+ # Returns an Array of middlewares.
34
+ def use(middleware, *args, &block)
35
+ @middlewares ||= []
36
+ @middlewares << [middleware, args.compact, block]
37
+ end
38
+
39
+ # Convenience method to set a User Agent Header
40
+ #
41
+ # agent - A user agent String. See Weary::USER_AGENTS for some help
42
+ #
43
+ # Returns the updated headers Hash.
44
+ def user_agent(agent)
45
+ headers.update 'User-Agent' => agent
46
+ end
47
+
48
+ # Should the Request use one or more Rack::Middleware?
49
+ def has_middleware?
50
+ !@middlewares.nil? && !@middlewares.empty?
51
+ end
52
+
53
+ # Pass Requestable values on to another Requestable object
54
+ # (including Middleware).
55
+ #
56
+ # requestable - Another Requestable object.
57
+ #
58
+ # Returns the Requestable object.
59
+ def pass_values_onto_requestable(requestable)
60
+ requestable.headers self.headers
61
+ requestable.adapter self.adapter
62
+ if has_middleware?
63
+ @middlewares.each {|middleware| requestable.use *middleware }
64
+ end
65
+ requestable
66
+ end
67
+
68
+ end
69
+ end
@@ -7,6 +7,8 @@ module Weary
7
7
  # retrieve the resource and some constraints on the parameters necessary
8
8
  # to complete the request.
9
9
  class Resource
10
+ include Weary::Requestable
11
+
10
12
  UnmetRequirementsError = Class.new(StandardError)
11
13
 
12
14
  attr_reader :method
@@ -49,24 +51,6 @@ module Weary
49
51
  @defaults ||= {}
50
52
  end
51
53
 
52
- # An accessor to set HTTP request headers.
53
- def headers(hash=nil)
54
- @headers = hash unless hash.nil?
55
- @headers ||= {}
56
- end
57
-
58
- # Set up a Rack::Middleware to be used by the Request (which is
59
- # Rack-friendly).
60
- def use(middleware, *args, &block)
61
- @middlewares ||= []
62
- @middlewares << [middleware, args.compact, block]
63
- end
64
-
65
- # Convenience method to set a User Agent Header
66
- def user_agent(agent)
67
- headers.update 'User-Agent' => agent
68
- end
69
-
70
54
  # Tell the Resource to anticipate Basic Authentication. Optionally,
71
55
  # tell the Resource what parameters to use as credentials.
72
56
  #
@@ -96,7 +80,7 @@ module Weary
96
80
 
97
81
  # The keys expected as parameters to the Request.
98
82
  def expected_params
99
- defaults.keys.map(&:to_s) | optional.map(&:to_s) | required.map(&:to_s)
83
+ (defaults.keys | optional | required).map(&:to_s).uniq
100
84
  end
101
85
 
102
86
  # Does the Resource expect this parameter to be used to make the Request?
@@ -122,29 +106,48 @@ module Weary
122
106
  # Raises a Weary::Resource::UnmetRequirementsError if the requirements
123
107
  # are not met.
124
108
  def request(params={})
125
- params.delete_if {|k,v| v.nil? || v.to_s.empty? }
126
- params.update(defaults)
109
+ normalize_parameters params
127
110
  raise UnmetRequirementsError, "Required parameters: #{requirements}" \
128
111
  unless meets_requirements? params
129
- credentials = pull_credentials params if authenticates?
130
- mapping = url.keys.map {|k| [k, params.delete(k) || params.delete(k.to_sym)] }
131
- request = Weary::Request.new url.expand(Hash[mapping]), @method do |r|
132
- r.headers headers
133
- if !@middlewares.nil? && !@middlewares.empty?
134
- @middlewares.each {|middleware| r.use *middleware }
135
- end
112
+ credentials = pull_credentials params
113
+ pairs = pull_url_pairs params
114
+ request = construct_request expand_url(pairs), params, credentials
115
+ yield request if block_given?
116
+ request
117
+ end
118
+ alias build request
119
+
120
+ private
121
+
122
+ # Private: Build the Request object with the given Resource parameters.
123
+ def construct_request(uri, params, credentials=[])
124
+ Weary::Request.new uri, @method do |r|
125
+ pass_values_onto_requestable(r)
136
126
  if !expected_params.empty?
137
127
  r.params params.reject {|k,v| !expects? k }
138
128
  end
139
129
  r.send @authenticates, *credentials if authenticates?
140
130
  end
141
- yield request if block_given?
142
- request
143
131
  end
144
- alias build request
145
132
 
133
+ # Private: For a set of parameters passed in to build a Request, delete
134
+ # those with no values, and merge them with the defaults.
135
+ def normalize_parameters(params)
136
+ params.delete_if {|k,v| v.nil? || v.to_s.empty? }
137
+ params.update(defaults)
138
+ params
139
+ end
146
140
 
147
- private
141
+ # Private: Expand the url template with the passed pairs to get the
142
+ # final url.
143
+ def expand_url(pairs)
144
+ url.expand Hash[pairs]
145
+ end
146
+
147
+ # Private: Separate the parameters needed to construct a url.
148
+ def pull_url_pairs(params)
149
+ url.keys.map {|k| [k, params.delete(k) || params.delete(k.to_sym)] }
150
+ end
148
151
 
149
152
  # Private: Separate the credentials for authentication from the other
150
153
  # request parameters.
@@ -52,8 +52,12 @@ module Weary
52
52
 
53
53
  def parse
54
54
  raise "The response does not contain a body" if body.nil? || body.empty?
55
- raise "Unable to parse Content-Type: #{content_type}" unless content_type =~ /json($|;.*)/
56
- MultiJson.decode body
55
+ if block_given?
56
+ yield body, content_type
57
+ else
58
+ raise "Unable to parse Content-Type: #{content_type}" unless content_type =~ /json($|;.*)/
59
+ MultiJson.decode body
60
+ end
57
61
  end
58
62
  end
59
63
  end
@@ -39,7 +39,12 @@ module Weary
39
39
 
40
40
  def select_by_url(url, set=@resources)
41
41
  set.select do |resource|
42
- !resource.url.extract(url).nil?
42
+ mapping = resource.url.extract(url)
43
+ if mapping.respond_to?(:empty?) && !mapping.empty?
44
+ !mapping.values_at(*resource.url.variables).compact.empty?
45
+ else
46
+ !mapping.nil?
47
+ end
43
48
  end
44
49
  end
45
50
 
@@ -1,3 +1,3 @@
1
1
  module Weary
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -1,6 +1,7 @@
1
1
  require 'weary'
2
2
  require 'webmock/rspec'
3
+ require 'multi_json'
3
4
 
4
5
  Dir['./spec/support/**/*.rb'].each {|f| require f }
5
6
 
6
- WebMock.disable_net_connect!
7
+ WebMock.disable_net_connect!
@@ -18,6 +18,18 @@ shared_examples_for "a Rack application" do
18
18
  subject.call(env).length.should be 3
19
19
  end
20
20
 
21
+ it "passes Rack::Lint" do
22
+ rack_defaults = {
23
+ 'rack.version' => Rack::VERSION,
24
+ 'rack.errors' => $stderr,
25
+ 'rack.multithread' => true,
26
+ 'rack.multiprocess' => false,
27
+ 'rack.run_once' => false
28
+ }
29
+ lint = Rack::Lint.new subject
30
+ expect { lint.call(rack_defaults.update(env)) }.to_not raise_error Rack::Lint::LintError
31
+ end
32
+
21
33
  describe "the status" do
22
34
  let(:status) { subject.call(env).first }
23
35
 
@@ -0,0 +1,77 @@
1
+ shared_examples_for "a Requestable" do
2
+
3
+ describe "#adapter" do
4
+ it "sets a new adapter to set the connection" do
5
+ klass = Class.new { include Weary::Adapter }
6
+ subject.adapter(klass)
7
+ subject.adapter.should be klass
8
+ end
9
+
10
+ it "defaults to the Net::HTTP adapter" do
11
+ subject.adapter.should be Weary::Adapter::NetHttp
12
+ end
13
+ end
14
+
15
+ describe "#headers" do
16
+ it "prepares headers for the request" do
17
+ subject.headers 'User-Agent' => 'RSpec'
18
+ subject.headers.should eql 'User-Agent' => 'RSpec'
19
+ end
20
+ end
21
+
22
+ describe "#use" do
23
+ it "adds a middleware to the stack" do
24
+ subject.use Rack::Runtime, "RSpec"
25
+ stack = subject.instance_variable_get :@middlewares
26
+ stack.first.should include(Rack::Runtime, ["RSpec"])
27
+ end
28
+ end
29
+
30
+ describe "#user_agent" do
31
+ it "updates the #headers hash with a User-Agent" do
32
+ subject.user_agent 'RSpec'
33
+ subject.headers.should eql 'User-Agent' => 'RSpec'
34
+ end
35
+ end
36
+
37
+ describe "#has_middleware?" do
38
+ it "is true if the Request is set up to use a Middleware" do
39
+ require 'rack/lobster'
40
+ subject.use Rack::Lobster
41
+ subject.should have_middleware
42
+ end
43
+
44
+ it "is false if no Middleware is attached to this Resource" do
45
+ subject.should_not have_middleware
46
+ end
47
+ end
48
+
49
+ describe "#pass_values_onto_requestable" do
50
+ it "passes middleware onto another Requestable object" do
51
+ require 'rack/lobster'
52
+ klass = Class.new { include Weary::Requestable }
53
+ requestable = klass.new
54
+ subject.use Rack::Lobster
55
+ subject.pass_values_onto_requestable(requestable)
56
+ requestable.should have_middleware
57
+ end
58
+
59
+ it "passes adapter onto another Requestable" do
60
+ klass = Class.new { include Weary::Requestable }
61
+ adapterClass = Class.new { include Weary::Adapter }
62
+ requestable = klass.new
63
+ subject.adapter(adapterClass)
64
+ subject.pass_values_onto_requestable(requestable)
65
+ requestable.adapter.should eql adapterClass
66
+ end
67
+
68
+ it "passes headers onto another Requestable" do
69
+ klass = Class.new { include Weary::Requestable }
70
+ requestable = klass.new
71
+ subject.user_agent "RSpec"
72
+ subject.pass_values_onto_requestable(requestable)
73
+ requestable.headers.should eql "User-Agent" => "RSpec"
74
+ end
75
+ end
76
+
77
+ end
@@ -0,0 +1,16 @@
1
+ shared_examples_for "an Adapter" do
2
+ it { should respond_to :call }
3
+ it { should respond_to :connect }
4
+
5
+ describe "#call" do
6
+ it_behaves_like "a Rack application"
7
+ end
8
+
9
+ describe "#connect" do
10
+ it "returns a Rack::Response" do
11
+ subject.connect(Rack::Request.new(env)).should be_a Rack::Response
12
+ end
13
+ end
14
+
15
+
16
+ end
@@ -3,24 +3,17 @@ require 'spec_helper'
3
3
  describe Weary::Adapter do
4
4
  subject { Class.new { include Weary::Adapter }.new }
5
5
  let(:env) {
6
- {
7
- "REQUEST_METHOD"=>"GET",
8
- "SCRIPT_NAME"=>"",
9
- "PATH_INFO"=>"/api/v2/json/repos/show/mwunsch/weary",
10
- "QUERY_STRING"=>nil,
11
- "SERVER_NAME"=>"github.com",
12
- "SERVER_PORT"=>80,
13
- "REQUEST_URI"=>"/api/v2/json/repos/show/mwunsch/weary"
14
- }
6
+ req = Weary::Request.new("http://github.com/api/v2/json/repos/show/mwunsch/weary")
7
+ req.headers 'User-Agent' => Weary::USER_AGENTS['Lynx 2.8.4rel.1 on Linux']
8
+ req.env
15
9
  }
16
10
 
17
- describe "#call" do
18
- it_behaves_like "a Rack application"
19
- end
11
+ it_behaves_like "an Adapter"
20
12
 
21
- describe "connect" do
22
- it "returns a Rack::Response" do
23
- subject.connect(Rack::Request.new(env)).should be_a Rack::Response
13
+ describe "#normalize_request_headers" do
14
+ it "removes the HTTP_ prefix from request headers" do
15
+ headers = subject.normalize_request_headers(env)
16
+ headers.should have_key "User-Agent"
24
17
  end
25
18
  end
26
19
  end