weary 0.7.2 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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