--- /dev/null
+module Faraday
+ # Subclasses Struct with some special helpers for converting from a Hash to
+ # a Struct.
+ class Options < Struct
+ # Public
+ def self.from(value)
+ value ? new.update(value) : new
+ end
+
+ # Public
+ def each
+ return to_enum(:each) unless block_given?
+ members.each do |key|
+ yield(key.to_sym, send(key))
+ end
+ end
+
+ # Public
+ def update(obj)
+ obj.each do |key, value|
+ if sub_options = self.class.options_for(key)
+ value = sub_options.from(value) if value
+ elsif Hash === value
+ hash = {}
+ value.each do |hash_key, hash_value|
+ hash[hash_key] = hash_value
+ end
+ value = hash
+ end
+
+ self.send("#{key}=", value) unless value.nil?
+ end
+ self
+ end
+
+ alias merge! update
+
+ # Public
+ def delete(key)
+ value = send(key)
+ send("#{key}=", nil)
+ value
+ end
+
+ # Public
+ def clear
+ members.each { |member| delete(member) }
+ end
+
+ # Public
+ def merge(value)
+ dup.update(value)
+ end
+
+ # Public
+ def fetch(key, *args)
+ unless symbolized_key_set.include?(key.to_sym)
+ key_setter = "#{key}="
+ if args.size > 0
+ send(key_setter, args.first)
+ elsif block_given?
+ send(key_setter, Proc.new.call(key))
+ else
+ raise self.class.fetch_error_class, "key not found: #{key.inspect}"
+ end
+ end
+ send(key)
+ end
+
+ # Public
+ def values_at(*keys)
+ keys.map { |key| send(key) }
+ end
+
+ # Public
+ def keys
+ members.reject { |member| send(member).nil? }
+ end
+
+ # Public
+ def empty?
+ keys.empty?
+ end
+
+ # Public
+ def each_key
+ return to_enum(:each_key) unless block_given?
+ keys.each do |key|
+ yield(key)
+ end
+ end
+
+ # Public
+ def key?(key)
+ keys.include?(key)
+ end
+
+ alias has_key? key?
+
+ # Public
+ def each_value
+ return to_enum(:each_value) unless block_given?
+ values.each do |value|
+ yield(value)
+ end
+ end
+
+ # Public
+ def value?(value)
+ values.include?(value)
+ end
+
+ alias has_value? value?
+
+ # Public
+ def to_hash
+ hash = {}
+ members.each do |key|
+ value = send(key)
+ hash[key.to_sym] = value unless value.nil?
+ end
+ hash
+ end
+
+ # Internal
+ def inspect
+ values = []
+ members.each do |member|
+ value = send(member)
+ values << "#{member}=#{value.inspect}" if value
+ end
+ values = values.empty? ? ' (empty)' : (' ' << values.join(", "))
+
+ %(#<#{self.class}#{values}>)
+ end
+
+ # Internal
+ def self.options(mapping)
+ attribute_options.update(mapping)
+ end
+
+ # Internal
+ def self.options_for(key)
+ attribute_options[key]
+ end
+
+ # Internal
+ def self.attribute_options
+ @attribute_options ||= {}
+ end
+
+ def self.memoized(key)
+ memoized_attributes[key.to_sym] = Proc.new
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{key}() self[:#{key}]; end
+ RUBY
+ end
+
+ def self.memoized_attributes
+ @memoized_attributes ||= {}
+ end
+
+ def [](key)
+ key = key.to_sym
+ if method = self.class.memoized_attributes[key]
+ super(key) || (self[key] = instance_eval(&method))
+ else
+ super
+ end
+ end
+
+ def symbolized_key_set
+ @symbolized_key_set ||= Set.new(keys.map { |k| k.to_sym })
+ end
+
+ def self.inherited(subclass)
+ super
+ subclass.attribute_options.update(attribute_options)
+ subclass.memoized_attributes.update(memoized_attributes)
+ end
+
+ def self.fetch_error_class
+ @fetch_error_class ||= if Object.const_defined?(:KeyError)
+ ::KeyError
+ else
+ ::IndexError
+ end
+ end
+ end
+
+ class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
+ :timeout, :open_timeout, :boundary,
+ :oauth)
+
+ def []=(key, value)
+ if key && key.to_sym == :proxy
+ super(key, value ? ProxyOptions.from(value) : nil)
+ else
+ super(key, value)
+ end
+ end
+ end
+
+ class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
+ :cert_store, :client_cert, :client_key, :certificate, :private_key, :verify_depth, :version)
+
+ def verify?
+ verify != false
+ end
+
+ def disable?
+ !verify?
+ end
+ end
+
+ class ProxyOptions < Options.new(:uri, :user, :password)
+ extend Forwardable
+ def_delegators :uri, :scheme, :scheme=, :host, :host=, :port, :port=, :path, :path=
+
+ def self.from(value)
+ case value
+ when String
+ value = {:uri => Utils.URI(value)}
+ when URI
+ value = {:uri => value}
+ when Hash, Options
+ if uri = value.delete(:uri)
+ value[:uri] = Utils.URI(uri)
+ end
+ end
+ super(value)
+ end
+
+ memoized(:user) { uri.user && Utils.unescape(uri.user) }
+ memoized(:password) { uri.password && Utils.unescape(uri.password) }
+ end
+
+ class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
+ :parallel_manager, :params, :headers, :builder_class)
+
+ options :request => RequestOptions, :ssl => SSLOptions
+
+ memoized(:request) { self.class.options_for(:request).new }
+
+ memoized(:ssl) { self.class.options_for(:ssl).new }
+
+ memoized(:builder_class) { RackBuilder }
+
+ def new_builder(block)
+ builder_class.new(&block)
+ end
+ end
+
+ class Env < Options.new(:method, :body, :url, :request, :request_headers,
+ :ssl, :parallel_manager, :params, :response, :response_headers, :status)
+
+ ContentLength = 'Content-Length'.freeze
+ StatusesWithoutBody = Set.new [204, 304]
+ SuccessfulStatuses = 200..299
+
+ # A Set of HTTP verbs that typically send a body. If no body is set for
+ # these requests, the Content-Length header is set to 0.
+ MethodsWithBodies = Set.new [:post, :put, :patch, :options]
+
+ options :request => RequestOptions,
+ :request_headers => Utils::Headers, :response_headers => Utils::Headers
+
+ extend Forwardable
+
+ def_delegators :request, :params_encoder
+
+ # Public
+ def [](key)
+ if in_member_set?(key)
+ super(key)
+ else
+ custom_members[key]
+ end
+ end
+
+ # Public
+ def []=(key, value)
+ if in_member_set?(key)
+ super(key, value)
+ else
+ custom_members[key] = value
+ end
+ end
+
+ # Public
+ def success?
+ SuccessfulStatuses.include?(status)
+ end
+
+ # Public
+ def needs_body?
+ !body && MethodsWithBodies.include?(method)
+ end
+
+ # Public
+ def clear_body
+ request_headers[ContentLength] = '0'
+ self.body = ''
+ end
+
+ # Public
+ def parse_body?
+ !StatusesWithoutBody.include?(status)
+ end
+
+ # Public
+ def parallel?
+ !!parallel_manager
+ end
+
+ def inspect
+ attrs = [nil]
+ members.each do |mem|
+ if value = send(mem)
+ attrs << "@#{mem}=#{value.inspect}"
+ end
+ end
+ if !custom_members.empty?
+ attrs << "@custom=#{custom_members.inspect}"
+ end
+ %(#<#{self.class}#{attrs.join(" ")}>)
+ end
+
+ # Internal
+ def custom_members
+ @custom_members ||= {}
+ end
+
+ # Internal
+ if members.first.is_a?(Symbol)
+ def in_member_set?(key)
+ self.class.member_set.include?(key.to_sym)
+ end
+ else
+ def in_member_set?(key)
+ self.class.member_set.include?(key.to_s)
+ end
+ end
+
+ # Internal
+ def self.member_set
+ @member_set ||= Set.new(members)
+ end
+ end
+end