lib/weary/resource.rb in weary-0.7.2 vs lib/weary/resource.rb in weary-1.0.0.rc1

- old
+ new

@@ -1,135 +1,159 @@ +require 'addressable/template' +require 'weary/request' + module Weary + # A description of a resource made available by an HTTP request. That + # description is composed primarily of a url template, the HTTP method to + # retrieve the resource and some constraints on the parameters necessary + # to complete the request. class Resource - attr_reader :name, :via, :with, :requires, :url - attr_accessor :headers - - def initialize(name) - self.name = name - self.via = :get - self.authenticates = false - self.follows = true + UnmetRequirementsError = Class.new(StandardError) + + attr_reader :method + + def initialize(method, uri) + @method = method + self.url uri end - - # The name of the Resource. Will be a lowercase string, whitespace replaced with underscores. - def name=(resource_name) - @name = resource_name.to_s.downcase.strip.gsub(/\s/,'_') + + # An accessor method to set the url to retrieve the resource. Use either + # brackets to delimit url variables or prefix them with a colon, like + # Sinatra. + # + # Returns an Addressable::Template + def url(uri=nil) + @uri = Addressable::Template.new(uri.gsub(/:(\w+)/) { "{#{$1}}" }) unless uri.nil? + @uri end - - # The HTTP Method used to fetch the Resource - def via=(http_verb) - verb = HTTPVerb.new(http_verb).normalize - @via = Methods.include?(verb) ? verb : :get + + # An accessor to set optional parameters permitted by the resource. + # + # Returns an Array of parameters. + def optional(*params) + @optional = params unless params.empty? + @optional ||= [] end - - # Optional params. Should be an array. Merges with requires if that is set. - def with=(params) - @with = [params].flatten.collect {|x| x.to_sym} - @with = (requires | @with) if requires + + # An accessor to set optional parameters required in order to access the + # resource. + # + # Returns an Array of parameters. + def required(*params) + @required = params unless params.empty? + @required ||= [] end - - # Required params. Should be an array. Merges with `with` or sets `with`. - def requires=(params) - @requires = [params].flatten.collect {|x| x.to_sym} - with ? @with = (with | @requires) : (@with = @requires) + + # An accessor to set default paramers to send to the resource. + def defaults(hash=nil) + @defaults = hash unless hash.nil? + @defaults ||= {} end - - # Sets whether the Resource requires authentication. Always sets to a boolean value. - def authenticates=(bool) - @authenticates = bool ? true : false + + # An accessor to set HTTP request headers. + def headers(hash=nil) + @headers = hash unless hash.nil? + @headers ||= {} end - - # Does the Resource require authentication? - def authenticates? - @authenticates + + # Set up a Rack::Middleware to be used by the Request (which is + # Rack-friendly). + def use(middleware, *args, &block) + @middlewares ||= [] + @middlewares << [middleware, args, block] end - - # Sets whether the Resource should follow redirection. Always sets to a boolean value. - def follows=(bool) - @follows = bool ? true : false + + # Convenience method to set a User Agent Header + def user_agent(agent) + headers.update 'User-Agent' => agent end - - # Should the resource follow redirection? - def follows? - @follows + + # Tell the Resource to anticipate Basic Authentication. Optionally, + # tell the Resource what parameters to use as credentials. + # + # user - The parameter in which to expect the username (defaults to :username) + # pass - The parameter in which to expect the password (defaults to :password) + def basic_auth!(user = :username, pass = :password) + @authenticates = :basic_auth + @credentials = [user, pass] end - - # Set the Resource's URL as a URI - def url=(uri) - @url = URI.parse(uri) + + # Tell the Resource to anticipate OAuth. Optionally, tell the Resource + # what parameters to use as the consumer key and access token + # + # key - The parameter in which to expect the consumer key (defaults to + # :consumer_key) + # token - The parameter in which to expect the user access token (defaults + # to :token) + def oauth!(key = :consumer_key, token = :token) + @authenticates = :oauth + @credentials = [key, token] end - - # A hash representation of the Resource - def to_hash - {@name.to_sym => { :via => via, - :with => with, - :requires => requires, - :follows => follows?, - :authenticates => authenticates?, - :url => url, - :headers => headers}} + + # Does the Resource anticipate some sort of authentication parameters? + def authenticates? + !!@authenticates end - - # Take parameters, default params, and credentials and build a Request object for this Resource - def build!(params={}, defaults=nil, credentials=nil) - uri = @url - parameters = setup_parameters(params, defaults) - request_opts = setup_options(parameters, credentials) - uri.query = request_opts[:query].to_params if request_opts[:query] - Request.new(uri.normalize.to_s, @via, request_opts) + + # The keys expected as parameters to the Request. + def expected_params + defaults.keys.map(&:to_s) | optional.map(&:to_s) | required.map(&:to_s) end - - # Setup the parameters to make the Request with - def setup_parameters(params={}, defaults=nil) - params = defaults ? defaults.merge(params) : params - find_missing_requirements(params) - remove_unnecessary_params(params) + + # Does the Resource expect this parameter to be used to make the Request? + def expects?(param) + expected_params.include? param.to_s end - - # Search the given parameters to see if they are missing any required params - def find_missing_requirements(params) - if (@requires && !@requires.empty?) - missing_requirements = @requires - params.keys - raise ArgumentError, "This resource is missing required parameters: '#{missing_requirements.inspect}'" unless missing_requirements.empty? - end + + # The parameter keys that must be fulfilled to create the Request. + def requirements + required.map(&:to_s) | url.keys end - - # Remove params that have not been specified with #with - def remove_unnecessary_params(params) - params.delete_if {|k,v| !@with.include?(k) } if (@with && !@with.empty?) + + # Given a hash of Request parameters, do they meet the requirements? + def meets_requirements?(params) + requirements.reject {|k| params.keys.map(&:to_s).include? k.to_s }.empty? end - - # Setup the options to be passed into the Request - def setup_options(params={}, credentials=nil) - options = {} - prepare_request_body(params, options) - setup_authentication(options, credentials) - options[:no_follow] = true if !follows? - options[:headers] = @headers if !@headers.blank? - options - end - - # Prepare the Request query or body depending on the HTTP method - def prepare_request_body(params, options={}) - if (@via == :post || @via == :put) - options[:body] = params unless params.blank? - else - options[:query] = params unless params.blank? - end - options - end - - # Prepare authentication credentials for the Request - def setup_authentication(options, credentials=nil) - if authenticates? - raise ArgumentError, "This resource requires authentication and no credentials were given." if credentials.blank? - if credentials.is_a?(OAuth::AccessToken) - options[:oauth] = credentials - else - options[:basic_auth] = credentials + + # Construct the request from the given parameters. + # + # Yields the Request + # + # Returns the Request. + # Raises a Weary::Resource::UnmetRequirementsError if the requirements + # are not met. + def request(params={}) + params.delete_if {|k,v| v.nil? || v.to_s.empty? } + params.update(defaults) + raise UnmetRequirementsError, "Required parameters: #{requirements}" \ + unless meets_requirements? params + credentials = pull_credentials params if authenticates? + mapping = url.keys.map {|k| [k, params.delete(k) || params.delete(k.to_sym)] } + request = Weary::Request.new url.expand(Hash[mapping]), @method do |r| + r.headers headers + if !expected_params.empty? + r.params params.reject {|k,v| !expects? k } end + r.send @authenticates, *credentials if authenticates? + if !@middlewares.nil? && !@middlewares.empty? + @middlewares.each {|middleware| r.use *middleware } + end end - options + yield request if block_given? + request end - + alias build request + + + private + + # Private: Separate the credentials for authentication from the other + # request parameters. + def pull_credentials(params) + (@credentials || []).map do |credential| + params.delete(credential) || params.delete(credential.to_s) + end.compact + end + + end end \ No newline at end of file