weary 0.7.2 → 1.0.0.rc1

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.
Files changed (56) hide show
  1. data/.gitignore +4 -1
  2. data/.rspec +2 -0
  3. data/.travis.yml +10 -0
  4. data/Gemfile +11 -8
  5. data/Gemfile.lock +49 -53
  6. data/LICENSE +1 -1
  7. data/README.md +134 -208
  8. data/Rakefile +6 -47
  9. data/lib/weary.rb +4 -66
  10. data/lib/weary/adapter.rb +23 -0
  11. data/lib/weary/adapters/net_http.rb +68 -0
  12. data/lib/weary/client.rb +243 -0
  13. data/lib/weary/deferred.rb +35 -0
  14. data/lib/weary/env.rb +32 -0
  15. data/lib/weary/middleware.rb +9 -0
  16. data/lib/weary/middleware/basic_auth.rb +17 -0
  17. data/lib/weary/middleware/content_type.rb +28 -0
  18. data/lib/weary/middleware/oauth.rb +31 -0
  19. data/lib/weary/request.rb +137 -124
  20. data/lib/weary/resource.rb +152 -128
  21. data/lib/weary/response.rb +48 -99
  22. data/lib/weary/route.rb +53 -0
  23. data/lib/weary/version.rb +3 -0
  24. data/spec/spec_helper.rb +4 -56
  25. data/spec/support/shared_examples_for_a_rack_app.rb +70 -0
  26. data/spec/support/shared_examples_for_a_rack_env.rb +14 -0
  27. data/spec/support/shared_examples_for_a_uri.rb +9 -0
  28. data/spec/weary/adapter_spec.rb +26 -0
  29. data/spec/weary/adapters/nethttp_spec.rb +88 -0
  30. data/spec/weary/client_spec.rb +258 -0
  31. data/spec/weary/deferred_spec.rb +35 -0
  32. data/spec/weary/env_spec.rb +12 -0
  33. data/spec/weary/middleware/basic_auth_spec.rb +23 -0
  34. data/spec/weary/middleware/content_type_spec.rb +34 -0
  35. data/spec/weary/middleware/oauth_spec.rb +27 -0
  36. data/spec/weary/request_spec.rb +227 -315
  37. data/spec/weary/resource_spec.rb +233 -233
  38. data/spec/weary/response_spec.rb +82 -159
  39. data/spec/weary/route_spec.rb +72 -0
  40. data/spec/weary_spec.rb +3 -56
  41. data/weary.gemspec +16 -79
  42. metadata +138 -98
  43. data/VERSION +0 -1
  44. data/examples/batch.rb +0 -20
  45. data/examples/repo.rb +0 -16
  46. data/examples/status.rb +0 -26
  47. data/lib/weary/base.rb +0 -124
  48. data/lib/weary/batch.rb +0 -37
  49. data/lib/weary/exceptions.rb +0 -15
  50. data/lib/weary/httpverb.rb +0 -32
  51. data/spec/fixtures/github.yml +0 -11
  52. data/spec/fixtures/twitter.xml +0 -763
  53. data/spec/fixtures/vimeo.json +0 -1
  54. data/spec/weary/base_spec.rb +0 -320
  55. data/spec/weary/batch_spec.rb +0 -71
  56. data/spec/weary/httpverb_spec.rb +0 -25
@@ -0,0 +1,35 @@
1
+ require 'weary/response'
2
+
3
+ module Weary
4
+ # A handy abstract proxy class that is used to proxy a domain model
5
+ # in an asynchronous fashion. Useful if you're not interested in a
6
+ # Weary::Response object, but you want to pass that into a domain object
7
+ # when the response is available.
8
+ class Deferred < defined?(::BasicObject) ? ::BasicObject : ::Object
9
+
10
+ instance_methods.each {|m| undef_method(m) if m.to_s !~ /(?:^__|^nil\?$|^send$|^object_id$)/ } \
11
+ unless defined? ::BasicObject
12
+
13
+ attr_reader :callable
14
+
15
+ def initialize(future, model, callable=nil)
16
+ @future = future
17
+ @model = model
18
+ @callable = callable || ::Proc.new{|klass, response| klass.new(response) }
19
+ @target = nil
20
+ end
21
+
22
+ def complete?
23
+ !!@target
24
+ end
25
+
26
+ def method_missing(method, *args, &block)
27
+ if @model.method_defined? method
28
+ @target ||= @callable.call @model, @future.force
29
+ @target.send(method, *args, &block)
30
+ else
31
+ super
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/weary/env.rb ADDED
@@ -0,0 +1,32 @@
1
+ module Weary
2
+ class Env
3
+ def initialize(request)
4
+ @request = request
5
+ end
6
+
7
+ def headers
8
+ Hash[@request.headers.map {|k,v| ["HTTP_#{k.to_s.upcase.gsub('-','_')}", v] }]
9
+ end
10
+
11
+ def env
12
+ {
13
+ 'REQUEST_METHOD' => @request.method,
14
+ 'SCRIPT_NAME' => "",
15
+ 'PATH_INFO' => @request.uri.path,
16
+ 'QUERY_STRING' => @request.uri.query || "",
17
+ 'SERVER_NAME' => @request.uri.host,
18
+ 'SERVER_PORT' => port,
19
+ 'REQUEST_URI' => @request.uri.request_uri,
20
+ 'HTTP_HOST' => @request.uri.host,
21
+ 'rack.url_scheme' => @request.uri.scheme,
22
+ 'rack.input' => @request.body,
23
+ 'weary.request' => @request
24
+ }.update headers
25
+ end
26
+
27
+ def port
28
+ (@request.uri.port || @request.uri.inferred_port).to_s
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ module Weary
2
+ module Middleware
3
+ autoload :BasicAuth, 'weary/middleware/basic_auth'
4
+ autoload :OAuth, 'weary/middleware/oauth'
5
+ autoload :ContentType, 'weary/middleware/content_type'
6
+ end
7
+ end
8
+
9
+
@@ -0,0 +1,17 @@
1
+ module Weary
2
+ module Middleware
3
+ class BasicAuth
4
+ AUTH_HEADER = "HTTP_AUTHORIZATION"
5
+
6
+ def initialize(app, credentials)
7
+ @app = app
8
+ @auth = [credentials.join(':')].pack('m*')
9
+ end
10
+
11
+ def call(env)
12
+ env.update AUTH_HEADER => "Basic #{@auth}"
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ module Weary
2
+ module Middleware
3
+ class ContentType
4
+
5
+ CONTENT_TYPE = 'CONTENT_TYPE'
6
+ CONTENT_LENGTH = 'CONTENT_LENGTH'
7
+ FORM_URL_ENCODED = 'application/x-www-form-urlencoded'
8
+ MULTIPART_FORM = 'multipart/form-data'
9
+
10
+ attr_reader :type
11
+
12
+ def initialize(app, type = FORM_URL_ENCODED)
13
+ @app = app
14
+ @type = type
15
+ end
16
+
17
+ def call(env)
18
+ env.update CONTENT_TYPE => @type
19
+ env.update CONTENT_LENGTH => length(env['rack.input'])
20
+ @app.call(env)
21
+ end
22
+
23
+ def length(input)
24
+ input.size.to_s
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ require 'weary/middleware'
2
+ require 'simple_oauth'
3
+
4
+ module Weary
5
+ module Middleware
6
+ class OAuth
7
+ AUTH_HEADER = "HTTP_AUTHORIZATION"
8
+
9
+ def initialize(app, consumer_key, access_token)
10
+ @app = app
11
+ @oauth = {
12
+ :consumer_key => consumer_key,
13
+ :token => access_token
14
+ }
15
+ end
16
+
17
+ def call(env)
18
+ env.update AUTH_HEADER => sign(env).to_s
19
+ @app.call(env)
20
+ end
21
+
22
+ def sign(env)
23
+ req = Rack::Request.new(env)
24
+ SimpleOAuth::Header.new req.request_method,
25
+ req.url,
26
+ req.params,
27
+ @oauth
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/weary/request.rb CHANGED
@@ -1,140 +1,153 @@
1
+ require 'addressable/uri'
2
+ require 'future'
3
+ require 'rack'
4
+
5
+ require 'weary/env'
6
+ require 'weary/adapter'
7
+
8
+ autoload :Middleware, 'weary/middleware'
9
+ autoload :MultiJson, 'multi_json'
10
+
1
11
  module Weary
12
+ # A Request is an interface to an http request. It doesn't actually make the
13
+ # request. Instead, it builds itself up into a Rack environment. A Request
14
+ # object is technically a Rack middleware, with a call method. The Request
15
+ # manipulates the passed in environment, then passes on to the adapter: A
16
+ # Rack application. Because the Request performs so much manipulation on
17
+ # the Rack env, you can attach middleware to it to act on its mutated env.
2
18
  class Request
3
-
4
- attr_reader :uri, :with, :credentials
5
- attr_accessor :headers
6
-
7
- def initialize(url, http_verb= :get, options={})
19
+ attr_reader :uri
20
+
21
+ def initialize(url, method='GET')
8
22
  self.uri = url
9
- self.via = http_verb
10
- self.credentials = {:username => options[:basic_auth][:username],
11
- :password => options[:basic_auth][:password]} if options[:basic_auth]
12
- self.credentials = options[:oauth] if options[:oauth]
13
- self.with = options[:body] if options[:body]
14
- self.headers = options[:headers] if options[:headers]
15
- self.follows = options[:no_follow] ? false : true
16
- end
17
-
18
- # Create a URI object for the given URL
23
+ self.method = method
24
+ @middlewares = []
25
+ yield self if block_given?
26
+ end
27
+
28
+ # Set and normalize the url for the Request.
19
29
  def uri=(url)
20
- @uri = URI.parse(url)
21
- if (with && !connection.request_body_permitted?)
22
- @uri.query = with
30
+ uri = Addressable::URI.parse(url).normalize!
31
+ @uri = uri
32
+ end
33
+
34
+ # A Rack interface for the Request. Applies itself and whatever
35
+ # middlewares to the env and passes the new env into the adapter.
36
+ #
37
+ # environment - A Hash for the Rack env.
38
+ #
39
+ # Returns an Array of three items; a Rack tuple.
40
+ def call(environment)
41
+ app = adapter.new
42
+ middlewares = @middlewares
43
+ stack = Rack::Builder.new do
44
+ middlewares.each do |middleware|
45
+ klass, *args = middleware
46
+ use klass, *args[0...-1].flatten, &args.last
47
+ end
48
+ run app
23
49
  end
50
+ stack.call rack_env_defaults.merge(environment.update(env))
51
+ end
52
+
53
+ # Build a Rack environment representing this Request.
54
+ def env
55
+ Weary::Env.new(self).env
24
56
  end
25
-
26
- def via=(http_verb)
27
- verb = HTTPVerb.new(http_verb).normalize
28
- @http_verb = if Methods.include?(verb)
29
- verb
30
- else
31
- :get
57
+
58
+ # The HTTP request method for this Request.
59
+ def method
60
+ @method
61
+ end
62
+
63
+ # Set and normalize the HTTP request method.
64
+ def method=(verb)
65
+ @method = verb.to_s.upcase
66
+ end
67
+
68
+ def headers(hash=nil)
69
+ @headers = hash unless hash.nil?
70
+ @headers ||= {}
71
+ end
72
+
73
+ def user_agent(agent)
74
+ headers.update 'User-Agent' => agent
75
+ end
76
+
77
+ def params(parameters=nil)
78
+ if !parameters.nil?
79
+ if ["POST", "PUT"].include? method
80
+ @body = query_params_from_hash(parameters)
81
+ body StringIO.new(@body)
82
+ use Weary::Middleware::ContentType
83
+ else
84
+ uri.query_values = parameters
85
+ @body = uri.query
86
+ end
32
87
  end
88
+ @body
89
+ end
90
+
91
+ def json(parameters)
92
+ json = MultiJson.encode(parameters)
93
+ body StringIO.new(json)
94
+ json
33
95
  end
34
-
35
- def via
36
- @http_verb
37
- end
38
-
39
- # Set parameters to send with the Request.
40
- # If this Request does not accept a body (a GET request for instance),
41
- # set the query string for the url.
42
- def with=(params)
43
- @with = (params.respond_to?(:to_params) ? params.to_params : params)
44
- if (!connection.request_body_permitted?)
45
- uri.query = @with
96
+
97
+ def body(io=nil)
98
+ @attachment = io unless io.nil?
99
+ @attachment ||= StringIO.new('')
100
+ end
101
+
102
+ def adapter(connection=nil)
103
+ @connection = connection unless connection.nil?
104
+ @connection ||= Weary::Adapter::NetHttp
105
+ end
106
+
107
+ def basic_auth(*credentials)
108
+ if !credentials.empty?
109
+ @basic_auth = true
110
+ use Weary::Middleware::BasicAuth, credentials
46
111
  end
112
+ @basic_auth
47
113
  end
48
-
49
- # Credentials to send to Authorize the Request
50
- # For basic auth, use a hash with keys :username and :password
51
- # For OAuth, use an Access Token
52
- def credentials=(auth)
53
- if auth.is_a?(OAuth::AccessToken)
54
- @credentials = auth
55
- else
56
- @credentials = {:username => auth[:username], :password => auth[:password]}
114
+
115
+ def oauth(consumer_key=nil, access_token=nil)
116
+ if !consumer_key.nil? && !access_token.nil?
117
+ @oauth = true
118
+ use Weary::Middleware::OAuth, [consumer_key, access_token]
57
119
  end
120
+ @oauth
58
121
  end
59
-
60
- # Should the Request follow redirects?
61
- def follows=(bool)
62
- @follows = (bool ? true : false)
63
- end
64
-
65
- def follows?
66
- @follows
67
- end
68
-
69
- # A callback that is triggered after the Response is received.
70
- def on_complete(&block)
71
- @on_complete = block if block_given?
72
- @on_complete
73
- end
74
-
75
- # A callback that is sent before the Request is fired.
76
- def before_send(&block)
77
- @before_send = block if block_given?
78
- @before_send
79
- end
80
-
81
- # Perform the Request, returns the Response. Pass a block in to use
82
- # as the on_complete callback.
83
- def perform(&block)
84
- @on_complete = block if block_given?
85
- response = perform!
86
- response.value
87
- end
88
-
89
- # Spins off a new thread to perform the Request.
90
- def perform!(&block)
91
- @on_complete = block if block_given?
92
- Thread.new {
93
- before_send.call(self) if before_send
94
- req = http.request(request)
95
- response = Response.new(req, self)
96
- if response.redirected? && follows?
97
- response.follow_redirect
98
- else
99
- on_complete.call(response) if on_complete
100
- response
101
- end
102
- }
103
- end
104
-
105
- # Build the HTTP connection.
106
- def http
107
- socket = Net::HTTP.new(uri.host, uri.port)
108
- socket.use_ssl = uri.is_a?(URI::HTTPS)
109
- socket.verify_mode = OpenSSL::SSL::VERIFY_NONE if socket.use_ssl?
110
- socket
111
- end
112
-
113
- # Build the HTTP Request.
114
- def request
115
- req = connection
116
-
117
- req.body = with if (with && req.request_body_permitted?)
118
- if (credentials)
119
- if (credentials.is_a?(OAuth::AccessToken))
120
- credentials.sign!(req)
121
- else
122
- req.basic_auth(credentials[:username], credentials[:password])
123
- end
122
+
123
+ # Returns a future-wrapped Response.
124
+ def perform
125
+ future do
126
+ status, headers, body = call(rack_env_defaults)
127
+ response = Weary::Response.new body, status, headers
128
+ yield response if block_given?
129
+ response
124
130
  end
125
-
126
- headers.each_pair {|key,value| req[key] = value } if headers
127
-
128
- req
129
- end
130
-
131
- # Prepare the HTTP Request.
132
- # The Request has a lifecycle:
133
- # Prepare with `request_preparation`
134
- # Build with `request`
135
- # Fire with `perform`
136
- def connection
137
- HTTPVerb.new(via).request_class.new(uri.request_uri)
131
+ end
132
+
133
+ def use(middleware, *args, &block)
134
+ @middlewares << [middleware, args, block]
135
+ end
136
+
137
+ private
138
+
139
+ def query_params_from_hash(hash)
140
+ tmp_uri = Addressable::URI.new
141
+ tmp_uri.query_values = hash
142
+ tmp_uri.query
143
+ end
144
+
145
+ def rack_env_defaults
146
+ { 'rack.version' => Rack::VERSION,
147
+ 'rack.errors' => $stderr,
148
+ 'rack.multithread' => true,
149
+ 'rack.multiprocess' => false,
150
+ 'rack.run_once' => false }
138
151
  end
139
152
 
140
153
  end
@@ -1,135 +1,159 @@
1
+ require 'addressable/template'
2
+ require 'weary/request'
3
+
1
4
  module Weary
5
+ # A description of a resource made available by an HTTP request. That
6
+ # description is composed primarily of a url template, the HTTP method to
7
+ # retrieve the resource and some constraints on the parameters necessary
8
+ # to complete the request.
2
9
  class Resource
3
- attr_reader :name, :via, :with, :requires, :url
4
- attr_accessor :headers
5
-
6
- def initialize(name)
7
- self.name = name
8
- self.via = :get
9
- self.authenticates = false
10
- self.follows = true
11
- end
12
-
13
- # The name of the Resource. Will be a lowercase string, whitespace replaced with underscores.
14
- def name=(resource_name)
15
- @name = resource_name.to_s.downcase.strip.gsub(/\s/,'_')
16
- end
17
-
18
- # The HTTP Method used to fetch the Resource
19
- def via=(http_verb)
20
- verb = HTTPVerb.new(http_verb).normalize
21
- @via = Methods.include?(verb) ? verb : :get
22
- end
23
-
24
- # Optional params. Should be an array. Merges with requires if that is set.
25
- def with=(params)
26
- @with = [params].flatten.collect {|x| x.to_sym}
27
- @with = (requires | @with) if requires
28
- end
29
-
30
- # Required params. Should be an array. Merges with `with` or sets `with`.
31
- def requires=(params)
32
- @requires = [params].flatten.collect {|x| x.to_sym}
33
- with ? @with = (with | @requires) : (@with = @requires)
34
- end
35
-
36
- # Sets whether the Resource requires authentication. Always sets to a boolean value.
37
- def authenticates=(bool)
38
- @authenticates = bool ? true : false
39
- end
40
-
41
- # Does the Resource require authentication?
10
+ UnmetRequirementsError = Class.new(StandardError)
11
+
12
+ attr_reader :method
13
+
14
+ def initialize(method, uri)
15
+ @method = method
16
+ self.url uri
17
+ end
18
+
19
+ # An accessor method to set the url to retrieve the resource. Use either
20
+ # brackets to delimit url variables or prefix them with a colon, like
21
+ # Sinatra.
22
+ #
23
+ # Returns an Addressable::Template
24
+ def url(uri=nil)
25
+ @uri = Addressable::Template.new(uri.gsub(/:(\w+)/) { "{#{$1}}" }) unless uri.nil?
26
+ @uri
27
+ end
28
+
29
+ # An accessor to set optional parameters permitted by the resource.
30
+ #
31
+ # Returns an Array of parameters.
32
+ def optional(*params)
33
+ @optional = params unless params.empty?
34
+ @optional ||= []
35
+ end
36
+
37
+ # An accessor to set optional parameters required in order to access the
38
+ # resource.
39
+ #
40
+ # Returns an Array of parameters.
41
+ def required(*params)
42
+ @required = params unless params.empty?
43
+ @required ||= []
44
+ end
45
+
46
+ # An accessor to set default paramers to send to the resource.
47
+ def defaults(hash=nil)
48
+ @defaults = hash unless hash.nil?
49
+ @defaults ||= {}
50
+ end
51
+
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, 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
+ # Tell the Resource to anticipate Basic Authentication. Optionally,
71
+ # tell the Resource what parameters to use as credentials.
72
+ #
73
+ # user - The parameter in which to expect the username (defaults to :username)
74
+ # pass - The parameter in which to expect the password (defaults to :password)
75
+ def basic_auth!(user = :username, pass = :password)
76
+ @authenticates = :basic_auth
77
+ @credentials = [user, pass]
78
+ end
79
+
80
+ # Tell the Resource to anticipate OAuth. Optionally, tell the Resource
81
+ # what parameters to use as the consumer key and access token
82
+ #
83
+ # key - The parameter in which to expect the consumer key (defaults to
84
+ # :consumer_key)
85
+ # token - The parameter in which to expect the user access token (defaults
86
+ # to :token)
87
+ def oauth!(key = :consumer_key, token = :token)
88
+ @authenticates = :oauth
89
+ @credentials = [key, token]
90
+ end
91
+
92
+ # Does the Resource anticipate some sort of authentication parameters?
42
93
  def authenticates?
43
- @authenticates
44
- end
45
-
46
- # Sets whether the Resource should follow redirection. Always sets to a boolean value.
47
- def follows=(bool)
48
- @follows = bool ? true : false
49
- end
50
-
51
- # Should the resource follow redirection?
52
- def follows?
53
- @follows
54
- end
55
-
56
- # Set the Resource's URL as a URI
57
- def url=(uri)
58
- @url = URI.parse(uri)
59
- end
60
-
61
- # A hash representation of the Resource
62
- def to_hash
63
- {@name.to_sym => { :via => via,
64
- :with => with,
65
- :requires => requires,
66
- :follows => follows?,
67
- :authenticates => authenticates?,
68
- :url => url,
69
- :headers => headers}}
70
- end
71
-
72
- # Take parameters, default params, and credentials and build a Request object for this Resource
73
- def build!(params={}, defaults=nil, credentials=nil)
74
- uri = @url
75
- parameters = setup_parameters(params, defaults)
76
- request_opts = setup_options(parameters, credentials)
77
- uri.query = request_opts[:query].to_params if request_opts[:query]
78
- Request.new(uri.normalize.to_s, @via, request_opts)
79
- end
80
-
81
- # Setup the parameters to make the Request with
82
- def setup_parameters(params={}, defaults=nil)
83
- params = defaults ? defaults.merge(params) : params
84
- find_missing_requirements(params)
85
- remove_unnecessary_params(params)
86
- end
87
-
88
- # Search the given parameters to see if they are missing any required params
89
- def find_missing_requirements(params)
90
- if (@requires && !@requires.empty?)
91
- missing_requirements = @requires - params.keys
92
- raise ArgumentError, "This resource is missing required parameters: '#{missing_requirements.inspect}'" unless missing_requirements.empty?
93
- end
94
- end
95
-
96
- # Remove params that have not been specified with #with
97
- def remove_unnecessary_params(params)
98
- params.delete_if {|k,v| !@with.include?(k) } if (@with && !@with.empty?)
99
- end
100
-
101
- # Setup the options to be passed into the Request
102
- def setup_options(params={}, credentials=nil)
103
- options = {}
104
- prepare_request_body(params, options)
105
- setup_authentication(options, credentials)
106
- options[:no_follow] = true if !follows?
107
- options[:headers] = @headers if !@headers.blank?
108
- options
109
- end
110
-
111
- # Prepare the Request query or body depending on the HTTP method
112
- def prepare_request_body(params, options={})
113
- if (@via == :post || @via == :put)
114
- options[:body] = params unless params.blank?
115
- else
116
- options[:query] = params unless params.blank?
117
- end
118
- options
119
- end
120
-
121
- # Prepare authentication credentials for the Request
122
- def setup_authentication(options, credentials=nil)
123
- if authenticates?
124
- raise ArgumentError, "This resource requires authentication and no credentials were given." if credentials.blank?
125
- if credentials.is_a?(OAuth::AccessToken)
126
- options[:oauth] = credentials
127
- else
128
- options[:basic_auth] = credentials
94
+ !!@authenticates
95
+ end
96
+
97
+ # The keys expected as parameters to the Request.
98
+ def expected_params
99
+ defaults.keys.map(&:to_s) | optional.map(&:to_s) | required.map(&:to_s)
100
+ end
101
+
102
+ # Does the Resource expect this parameter to be used to make the Request?
103
+ def expects?(param)
104
+ expected_params.include? param.to_s
105
+ end
106
+
107
+ # The parameter keys that must be fulfilled to create the Request.
108
+ def requirements
109
+ required.map(&:to_s) | url.keys
110
+ end
111
+
112
+ # Given a hash of Request parameters, do they meet the requirements?
113
+ def meets_requirements?(params)
114
+ requirements.reject {|k| params.keys.map(&:to_s).include? k.to_s }.empty?
115
+ end
116
+
117
+ # Construct the request from the given parameters.
118
+ #
119
+ # Yields the Request
120
+ #
121
+ # Returns the Request.
122
+ # Raises a Weary::Resource::UnmetRequirementsError if the requirements
123
+ # are not met.
124
+ def request(params={})
125
+ params.delete_if {|k,v| v.nil? || v.to_s.empty? }
126
+ params.update(defaults)
127
+ raise UnmetRequirementsError, "Required parameters: #{requirements}" \
128
+ 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 !expected_params.empty?
134
+ r.params params.reject {|k,v| !expects? k }
135
+ end
136
+ r.send @authenticates, *credentials if authenticates?
137
+ if !@middlewares.nil? && !@middlewares.empty?
138
+ @middlewares.each {|middleware| r.use *middleware }
129
139
  end
130
140
  end
131
- options
132
- end
133
-
141
+ yield request if block_given?
142
+ request
143
+ end
144
+ alias build request
145
+
146
+
147
+ private
148
+
149
+ # Private: Separate the credentials for authentication from the other
150
+ # request parameters.
151
+ def pull_credentials(params)
152
+ (@credentials || []).map do |credential|
153
+ params.delete(credential) || params.delete(credential.to_s)
154
+ end.compact
155
+ end
156
+
157
+
134
158
  end
135
159
  end