./lib/animoto/client.rb in animoto-0.0.0.alpha3 vs ./lib/animoto/client.rb in animoto-0.0.0.alpha4
- old
+ new
@@ -1,10 +1,7 @@
-require 'uri'
-require 'net/http'
-require 'net/https'
-require 'json'
require 'yaml'
+require 'uri'
require 'animoto/errors'
require 'animoto/content_type'
require 'animoto/standard_envelope'
require 'animoto/resource'
@@ -23,23 +20,22 @@
require 'animoto/video'
require 'animoto/job'
require 'animoto/directing_and_rendering_job'
require 'animoto/directing_job'
require 'animoto/rendering_job'
+require 'animoto/dynamic_class_loader'
+require 'animoto/http_engine'
+require 'animoto/response_parser'
module Animoto
class Client
API_ENDPOINT = "https://api2-staging.animoto.com/"
API_VERSION = 1
BASE_CONTENT_TYPE = "application/vnd.animoto"
- HTTP_METHOD_MAP = {
- :get => Net::HTTP::Get,
- :post => Net::HTTP::Post
- }
attr_accessor :key, :secret, :endpoint
- attr_reader :format
+ attr_reader :http_engine, :response_parser
# Creates a new Client object which handles credentials, versioning, making requests, and
# parsing responses.
#
# If you have your key and secret in ~/.animotorc or /etc/.animotorc, those credentials will
@@ -50,31 +46,65 @@
# @param [String] key the API key for your account
# @param [String] secret the secret key for your account
# @return [Client]
# @raise [ArgumentError] if no credentials are supplied
def initialize *args
- @debug = ENV['DEBUG']
options = args.last.is_a?(Hash) ? args.pop : {}
@key = args[0]
@secret = args[1]
@endpoint = options[:endpoint]
-
- home_path = File.expand_path '~/.animotorc'
- config = if File.exist?(home_path)
- YAML.load(File.read(home_path))
- elsif File.exist?('/etc/.animotorc')
- YAML.load(File.read('/etc/.animotorc'))
+ configure_from_rc_file
+ @endpoint ||= API_ENDPOINT
+ __send__ :http_engine=, options[:http_engine] || :net_http
+ __send__ :response_parser=, options[:response_parser] || :json
+ end
+
+ # Set the HTTP engine this client will use.
+ #
+ # @param [HTTPEngine, Symbol, Class] engine you may pass a
+ # HTTPEngine instance to use, or the symbolic name of a adapter to use,
+ # or a Class whose instances respond to #request and return a String of
+ # the response body
+ # @see Animoto::HTTPEngine
+ # @return [HTTPEngine] the engine instance
+ # @raise [ArgumentError] if given a class without the correct interface
+ def http_engine= engine
+ @http_engine = case engine
+ when Animoto::HTTPEngine
+ engine
+ when Class
+ if engine.instance_methods.include?('request')
+ engine.new
+ else
+ raise ArgumentError
+ end
+ else
+ Animoto::HTTPEngine[engine].new
end
- @key ||= config['key']
- @secret ||= config['secret']
- @endpoint ||= config['endpoint']
- unless @key && @secret
- raise ArgumentError, "You must supply your key and secret"
+ end
+
+ # Set the response parser this client will use.
+ #
+ # @param [ResponseParser, Symbol, Class] parser you may pass a
+ # ResponseParser instance to use, or the symbolic name of a adapter to use,
+ # or a Class whose instances respond to #parse, #unparse, and #format.
+ # @see Animoto::ResponseParser
+ # @return [ResponseParser] the parser instance
+ # @raise [ArgumentError] if given a class without the correct interface
+ def response_parser= parser
+ @response_parser = case parser
+ when Animoto::ResponseParser
+ parser
+ when Class
+ if %{format parse unparse}.all? { |m| parser.instance_methods.include? m }
+ parser.new
+ else
+ raise ArgumentError
+ end
+ else
+ Animoto::ResponseParser[parser].new
end
-
- @endpoint ||= API_ENDPOINT
- @format = 'json'
end
# Finds a resource by its URL.
#
# @param [Class] klass the Resource class you're finding
@@ -122,133 +152,97 @@
resource.load(find_request(resource.class, resource.url, options))
end
private
+ # Sets the API credentials from an .animotorc file. First looks for one in the current
+ # directory, then checks ~/.animotorc, then finally /etc/.animotorc.
+ #
+ # @raise [ArgumentError] if none of the files are found
+ def configure_from_rc_file
+ catch(:done) do
+ current_path = Dir.pwd + '/.animotorc'
+ home_path = File.expand_path('~/.animotorc')
+ config = if File.exist?(current_path)
+ YAML.load(File.read(current_path))
+ elsif File.exist?(home_path)
+ home_path = File.expand_path '~/.animotorc'
+ YAML.load(File.read(home_path))
+ elsif File.exist?('/etc/.animotorc')
+ YAML.load(File.read('/etc/.animotorc'))
+ end
+ if config
+ @key ||= config['key']
+ @secret ||= config['secret']
+ @endpoint ||= config['endpoint']
+ throw :done if @key && @secret
+ end
+ raise ArgumentError, "You must supply your key and secret"
+ end
+ end
+
# Builds a request to find a resource.
#
# @param [Class] klass the Resource class you're looking for
# @param [String] url the URL of the resource
# @param [Hash] options
- # @return [Hash] deserialized JSON response body
+ # @return [Hash] deserialized response body
def find_request klass, url, options = {}
- # request(:get, URI.parse(url).path, nil, { "Accept" => content_type_of(klass) }, options)
- request(:get, URI.parse(url), nil, { "Accept" => content_type_of(klass) }, options)
+ request(:get, url, nil, { "Accept" => content_type_of(klass) }, options)
end
# Builds a request requiring a manifest.
#
# @param [Manifest] manifest the manifest being acted on
# @param [String] endpoint the endpoint to send the request to
# @param [Hash] options
- # @return [Hash] deserialized JSON response body
+ # @return [Hash] deserialized response body
def send_manifest manifest, endpoint, options = {}
- # request(:post, endpoint, manifest.to_json, { "Accept" => "application/#{format}", "Content-Type" => content_type_of(manifest) }, options)
u = URI.parse(endpoint)
u.path = endpoint
- request(:post, u, manifest.to_json, { "Accept" => "application/#{format}", "Content-Type" => content_type_of(manifest) }, options)
+ request(
+ :post,
+ u.to_s,
+ response_parser.unparse(manifest.to_hash),
+ { "Accept" => "application/#{response_parser.format}", "Content-Type" => content_type_of(manifest) },
+ options
+ )
end
# Makes a request and parses the response.
#
# @param [Symbol] method which HTTP method to use (should be lowercase, i.e. :get instead of :GET)
- # @param [URI] uri a URI object of the request URI
+ # @param [String] url the URL of the request
# @param [String, nil] body the request body
# @param [Hash<String,String>] headers the request headers (will be sent as-is, which means you should
# specify "Content-Type" => "..." instead of, say, :content_type => "...")
# @param [Hash] options
- # @return [Hash] deserialized JSON response body
- def request method, uri, body, headers = {}, options = {}
- http = Net::HTTP.new uri.host, uri.port
- http.use_ssl = true
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
- req = build_request method, uri, body, headers, options
- if @debug
- puts "********************* REQUEST *******************"
- puts "#{req.method} #{uri.to_s} HTTP/#{http.instance_variable_get(:@curr_http_version)}\r\n"
- req.each_capitalized { |header, value| puts "#{header}: #{value}\r\n" }
- puts "\r\n"
- puts req.body unless req.method == 'GET'
+ # @return [Hash] deserialized response body
+ # @raise [Error]
+ def request method, url, body, headers = {}, options = {}
+ error = catch(:fail) do
+ options = { :username => @key, :password => @secret }.merge(options)
+ response = http_engine.request(method, url, body, headers, options)
+ return response_parser.parse(response)
end
- response = http.request(req)
- if @debug
- puts "********************* RESPONSE *******************"
- puts "#{response.code} #{response.message}\r\n"
- response.each_capitalized { |header, value| puts "#{header}: #{value}\r\n" }
- puts "\r\n"
- body = response.body
- if body.nil? || body.empty?
- puts "(No content)"
- else
- puts body
- end
+ if error
+ errors = response_parser.parse(error)['response']['status']['errors']
+ raise Animoto::Error.new(errors.collect { |e| e['message'] }.join(', '))
+ else
+ raise Animoto::Error
end
- read_response response
+ rescue NoMethodError => e
+ raise Animoto::Error.new("Invalid response (#{error.inspect})")
end
- # Builds the request object.
- #
- # @param [Symbol] method which HTTP method to use (should be lowercase, i.e. :get instead of :GET)
- # @param [String] uri the request path
- # @param [String, nil] body the request body
- # @param [Hash<String,String>] headers the request headers (will be sent as-is, which means you should
- # specify "Content-Type" => "..." instead of, say, :content_type => "...")
- # @param [Hash] options
- # @return [Net::HTTPRequest] the request object
- def build_request method, uri, body, headers, options
- req = HTTP_METHOD_MAP[method].new uri.path
- req.body = body
- req.initialize_http_header headers
- req.basic_auth key, secret
- req
- end
-
- # Verifies and parses the response.
- #
- # @param [Net::HTTPResponse] response the response object
- # @return [Hash] deserialized JSON response body
- def read_response response
- check_status response
- parse_response response
- end
-
- # Checks the status of the response to make sure it's successful.
- #
- # @param [Net::HTTPResponse] response the response object
- # @return [nil]
- # @raise [Error,RuntimeError] if the response code isn't in the 200 range
- def check_status response
- unless (200..299).include?(response.code.to_i)
- if response.body
- begin
- json = JSON.parse(response.body)
- errors = json['response']['status']['errors']
- rescue => e
- raise response.message
- else
- raise Animoto::Error.new(errors.collect { |e| e['message'] }.join(', '))
- end
- else
- raise response.message
- end
- end
- end
-
- # Parses a JSON response body into a Hash.
- # @param [Net::HTTPResponse] response the response object
- # @return [Hash] deserialized JSON response body
- def parse_response response
- JSON.parse(response.body)
- end
-
# Creates the full content type string given a Resource class or instance
# @param [Class,ContentType] klass_or_instance the class or instance to build the
# content type for
# @return [String] the full content type with the version and format included (i.e.
# "application/vnd.animoto.storyboard-v1+json")
def content_type_of klass_or_instance
klass = klass_or_instance.is_a?(Class) ? klass_or_instance : klass_or_instance.class
- "#{BASE_CONTENT_TYPE}.#{klass.content_type}-v#{API_VERSION}+#{format}"
+ "#{BASE_CONTENT_TYPE}.#{klass.content_type}-v#{API_VERSION}+#{response_parser.format}"
end
end
end
\ No newline at end of file