weary 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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