Merge pull request #1054 from BobbyMcWho/create-faraday-deprecation-class

Create faraday deprecation class
This commit is contained in:
risk danger olson 2019-10-17 13:30:08 -06:00 committed by GitHub
commit a4e9bc2c19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 260 additions and 34 deletions

97
lib/faraday/deprecate.rb Normal file
View File

@ -0,0 +1,97 @@
# frozen_string_literal: true
module Faraday
# @param new_klass [Class] new Klass to use
#
# @return [Class] A modified version of new_klass that warns on
# usage about deprecation.
# @see Faraday::Deprecate
module DeprecatedClass
def self.proxy_class(new_klass)
Class.new(new_klass).tap do |k|
class << k
extend Faraday::Deprecate
# Make this more human readable than #<Class:Faraday::ClientError>
klass_name = superclass.to_s[/^#<Class:(\w*::\w*)>$/, 1]
deprecate :new, "#{klass_name}.new", '1.0'
deprecate :inherited, klass_name, '1.0'
end
end
end
end
# Deprecation using semver instead of date, based on Gem::Deprecate
# Provides a single method +deprecate+ to be used to declare when
# something is going away.
#
# class Legacy
# def self.klass_method
# # ...
# end
#
# def instance_method
# # ...
# end
#
# extend Faraday::Deprecate
# deprecate :instance_method, "X.z", '1.0'
#
# class << self
# extend Faraday::Deprecate
# deprecate :klass_method, :none, '1.0'
# end
# end
module Deprecate
def self.skip # :nodoc:
@skip ||= false
end
def self.skip=(value) # :nodoc:
@skip = value
end
# Temporarily turn off warnings. Intended for tests only.
def skip_during
original = Faraday::Deprecate.skip
Faraday::Deprecate.skip, = true
yield
ensure
Faraday::Deprecate.skip = original
end
# Simple deprecation method that deprecates +name+ by wrapping it up
# in a dummy method. It warns on each call to the dummy method
# telling the user of +repl+ (unless +repl+ is :none) and the
# semver that it is planned to go away.
# @param name [Symbol] the method symbol to deprecate
# @param repl [#to_s, :none] the replacement to use, when `:none` it will
# alert the user that no replacemtent is present.
# @param ver [String] the semver the method will be removed.
def deprecate(name, repl, ver)
class_eval do
old = "_deprecated_#{name}"
alias_method old, name
define_method name do |*args, &block|
mod = is_a? Module
target = mod ? "#{self}." : "#{self.class}#"
target_message = if name == :inherited
"Inheriting #{self}"
else
"#{target}#{name}"
end
msg = [
"NOTE: #{target_message} is deprecated",
repl == :none ? ' with no replacement' : "; use #{repl} instead. ",
"It will be removed in or after version #{Gem::Version.new(ver)}",
"\n#{target}#{name} called from #{Gem.location_of_caller.join(':')}"
]
warn "#{msg.join}." unless Faraday::Deprecate.skip
send old, *args, &block
end
end
end
module_function :deprecate, :skip_during
end
end

View File

@ -1,21 +1,25 @@
module Faraday
class Error < StandardError; end
# frozen_string_literal: true
class ClientError < Error
require 'faraday/deprecate'
# Faraday namespace.
module Faraday
# Faraday error base class.
class Error < StandardError
attr_reader :response, :wrapped_exception
def initialize(ex, response = nil)
def initialize(exc, response = nil)
@wrapped_exception = nil
@response = response
if ex.respond_to?(:backtrace)
super(ex.message)
@wrapped_exception = ex
elsif ex.respond_to?(:each_key)
super("the server responded with status #{ex[:status]}")
@response = ex
if exc.respond_to?(:backtrace)
super(exc.message)
@wrapped_exception = exc
elsif exc.respond_to?(:each_key)
super("the server responded with status #{exc[:status]}")
@response = exc
else
super(ex.to_s)
super(exc.to_s)
end
end
@ -28,39 +32,80 @@ module Faraday
end
def inspect
inner = ''
if @wrapped_exception
inner << " wrapped=#{@wrapped_exception.inspect}"
end
if @response
inner << " response=#{@response.inspect}"
end
if inner.empty?
inner << " #{super}"
end
inner = +''
inner << " wrapped=#{@wrapped_exception.inspect}" if @wrapped_exception
inner << " response=#{@response.inspect}" if @response
inner << " #{super}" if inner.empty?
%(#<#{self.class}#{inner}>)
end
end
class ConnectionFailed < ClientError; end
class ResourceNotFound < ClientError; end
class ParsingError < ClientError; end
# Faraday client error class. Represents 4xx status responses.
class ClientError < Error
end
class TimeoutError < ClientError
def initialize(ex = nil)
super(ex || "timeout")
# Raised by Faraday::Response::RaiseError in case of a 400 response.
class BadRequestError < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 401 response.
class UnauthorizedError < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 403 response.
class ForbiddenError < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 404 response.
class ResourceNotFound < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 407 response.
class ProxyAuthError < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 409 response.
class ConflictError < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 422 response.
class UnprocessableEntityError < ClientError
end
# Faraday server error class. Represents 5xx status responses.
class ServerError < Error
end
# A unified client error for timeouts.
class TimeoutError < ServerError
def initialize(exc = 'timeout', response = nil)
super(exc, response)
end
end
class SSLError < ClientError
# A unified error for failed connections.
class ConnectionFailed < Error
end
class RetriableResponse < ClientError; end
[:ClientError, :ConnectionFailed, :ResourceNotFound,
:ParsingError, :TimeoutError, :SSLError, :RetriableResponse].each do |const|
Error.const_set(const, Faraday.const_get(const))
# A unified client error for SSL errors.
class SSLError < Error
end
# Raised by FaradayMiddleware::ResponseMiddleware
class ParsingError < Error
end
# Exception used to control the Retry middleware.
#
# @see Faraday::Request::Retry
class RetriableResponse < Error
end
%i[ClientError ConnectionFailed ResourceNotFound
ParsingError TimeoutError SSLError RetriableResponse].each do |const|
Error.const_set(
const,
DeprecatedClass.proxy_class(Faraday.const_get(const))
)
end
end

View File

@ -0,0 +1,84 @@
# frozen_string_literal: true
RSpec.describe Faraday::ClientError do
describe '.initialize' do
subject { described_class.new(exception, response) }
let(:response) { nil }
context 'with exception only' do
let(:exception) { RuntimeError.new('test') }
it { expect(subject.wrapped_exception).to eq(exception) }
it { expect(subject.response).to be_nil }
it { expect(subject.message).to eq(exception.message) }
it { expect(subject.backtrace).to eq(exception.backtrace) }
it { expect(subject.inspect).to eq('#<Faraday::ClientError wrapped=#<RuntimeError: test>>') }
end
context 'with response hash' do
let(:exception) { { status: 400 } }
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 400') }
it { expect(subject.inspect).to eq('#<Faraday::ClientError response={:status=>400}>') }
end
context 'with string' do
let(:exception) { 'custom message' }
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to be_nil }
it { expect(subject.message).to eq('custom message') }
it { expect(subject.inspect).to eq('#<Faraday::ClientError #<Faraday::ClientError: custom message>>') }
end
context 'with anything else #to_s' do
let(:exception) { %w[error1 error2] }
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to be_nil }
it { expect(subject.message).to eq('["error1", "error2"]') }
it { expect(subject.inspect).to eq('#<Faraday::ClientError #<Faraday::ClientError: ["error1", "error2"]>>') }
end
context 'maintains backward-compatibility until 1.0' do
it 'does not raise an error for error-namespaced classes but prints an error message' do
error_message, error = with_warn_squelching { Faraday::Error::ClientError.new('foo') }
expect(error).to be_a Faraday::ClientError
expect(error_message).to match(
Regexp.new(
'NOTE: Faraday::Error::ClientError.new is deprecated; '\
'use Faraday::ClientError.new instead. It will be removed in or after version 1.0'
)
)
end
it 'does not raise an error for inherited error-namespaced classes but prints an error message' do
error_message, = with_warn_squelching { class E < Faraday::Error::ClientError; end }
expect(error_message).to match(
Regexp.new(
'NOTE: Inheriting Faraday::Error::ClientError is deprecated; '\
'use Faraday::ClientError instead. It will be removed in or after version 1.0'
)
)
end
it 'allows backward-compatible class to be subclassed' do
expect { class CustomError < Faraday::Error::ClientError; end }.not_to raise_error
end
end
def with_warn_squelching
stderr_catcher = StringIO.new
original_stderr = $stderr
$stderr = stderr_catcher
result = yield if block_given?
[stderr_catcher.tap(&:rewind).string, result]
ensure
$stderr = original_stderr
end
end
end

View File

@ -26,7 +26,7 @@ module Adapters
conn = create_connection(:request => {:timeout => 1, :open_timeout => 1})
begin
conn.get '/slow'
rescue Faraday::Error::ClientError
rescue Faraday::Error::TimeoutError
end
end