docs/options-detach-plan.md
module Faraday
module OptionsLike; end
class BaseOptions
include OptionsLike
MEMBERS = [].freeze # override in subclasses
COERCIONS = {}.freeze # key => Class or Proc
def self.from(value)
case value
when nil
new
when self
value
when OptionsLike
new.update(value.to_hash)
when Hash
new.update(value)
else
raise ArgumentError, "unsupported options: #{value.class}"
end
end
def initialize(**attrs)
attrs.each { |k, v| public_send("#{k}=", coerce(k, v)) }
end
def update(hash_like)
hash_like.each { |k, v| public_send("#{k}=", coerce(k, v)) }
self
end
def merge!(other)
other.each do |k, v|
next if v.nil?
cur = public_send(k)
newv = coerce(k, v)
if cur.is_a?(OptionsLike) && newv.is_a?(OptionsLike)
cur.merge!(newv.to_hash)
else
public_send("#{k}=", newv)
end
end
self
end
def merge(other)
self.class.from(to_hash).merge!(other)
end
def deep_dup
self.class.from(to_hash)
end
def to_hash
self.class::MEMBERS.each_with_object({}) do |k, h|
v = public_send(k)
next if v.nil?
h[k] = v.is_a?(OptionsLike) ? v.to_hash : v
end
end
def inspect
pairs = to_hash.map { |k, v| "#{k}=#{v.inspect}" }
"#<#{self.class} #{pairs.join(', ')}>"
end
private
def coerce(key, value)
return value if value.nil?
coercer = self.class::COERCIONS[key.to_sym]
case coercer
when Class then coercer.from(value)
when Proc then coercer.call(value)
else value
end
end
end
end
class Faraday::ProxyOptions < Faraday::BaseOptions
MEMBERS = [:uri, :user, :password].freeze
attr_accessor(*MEMBERS)
COERCIONS = {
uri: ->(v) do
case v
when String
v = "http://#{v}" unless v.include?('://')
Faraday::Utils.URI(v)
when URI
v
else
v
end
end
}.freeze
def user
@user || (uri && Faraday::Utils.unescape(uri.user))
end
def password
@password || (uri && Faraday::Utils.unescape(uri.password))
end
end
# lib/faraday/utils.rb
# Treat OptionsLike like Options when deep-merging nested structures
if value.is_a?(Hash) && (target_value.is_a?(Hash) || target_value.is_a?(Faraday::OptionsLike))
target[key] = deep_merge(target_value, value)
else
target[key] = value
end