Herança de exceção Ruby com classes geradas dinamicamente
-
09-06-2019 - |
Pergunta
Sou novo em Ruby, então estou tendo problemas para entender esse estranho problema de exceção que estou tendo.Estou usando a gema ruby-aaws para acessar o Amazon ECS: http://www.caliban.org/ruby/ruby-aws/.Isso define uma classe Amazon::AWS:Error:
module Amazon
module AWS
# All dynamically generated exceptions occur within this namespace.
#
module Error
# An exception generator class.
#
class AWSError
attr_reader :exception
def initialize(xml)
err_class = xml.elements['Code'].text.sub( /^AWS.*\./, '' )
err_msg = xml.elements['Message'].text
unless Amazon::AWS::Error.const_defined?( err_class )
Amazon::AWS::Error.const_set( err_class,
Class.new( StandardError ) )
end
ex_class = Amazon::AWS::Error.const_get( err_class )
@exception = ex_class.new( err_msg )
end
end
end
end
end
Isso significa que se você receber um código de erro como AWS.InvalidParameterValue
, isso produzirá (em sua variável de exceção) uma nova classe Amazon::AWS::Error::InvalidParameterValue
que é uma subclasse de StandardError
.
Agora é aqui que fica estranho.Eu tenho um código parecido com este:
begin
do_aws_stuff
rescue Amazon::AWS::Error => error
puts "Got an AWS error"
end
Agora se do_aws_stuff
lança um NameError
, meu bloco de resgate é acionado.Parece que Amazon::AWS::Error não é a superclasse do erro gerado - acho que por ser um módulo, tudo é uma subclasse dele?Certamente se eu fizer:
irb(main):007:0> NameError.new.kind_of?(Amazon::AWS::Error)
=> true
Diz true
, o que considero confuso, especialmente considerando isto:
irb(main):009:0> NameError.new.kind_of?(Amazon::AWS)
=> false
O que está acontecendo e como devo separar os erros da AWS de outros tipos de erros?Devo fazer algo como:
begin
do_aws_stuff
rescue => error
if error.class.to_s =~ /^Amazon::AWS::Error/
puts "Got an AWS error"
else
raise error
end
end
Isso parece excepcionalmente desagradável.Os erros gerados também não são da classe AWSError - eles são gerados assim:
error = Amazon::AWS::Error::AWSError.new( xml )
raise error.exception
Então, as exceções que estou procurando rescue
from são os tipos de exceção gerados que herdam apenas de StandardError.
Para esclarecer, tenho duas perguntas:
Por que NameError, uma exceção construída em Ruby, é um
kind_of?(Amazon::AWS::Error)
, que é um módulo?
Responder: eu tinha ditoinclude Amazon::AWS::Error
no topo do meu arquivo, pensando que era como uma importação de Java ou inclusão de C++.O que isso realmente fez foi adicionar tudo definido emAmazon::AWS::Error
(presente e futuro) à classe Kernel implícita, que é ancestral de toda classe.Isso significa qualquer coisa passariakind_of?(Amazon::AWS::Error)
.Como posso distinguir melhor as exceções criadas dinamicamente em
Amazon::AWS::Error
de outras exceções aleatórias de outros lugares?
Solução
Ok, vou tentar ajudar aqui:
Primeiro, um módulo não é uma classe, ele permite misturar comportamentos em uma classe.segundo veja o seguinte exemplo:
module A
module B
module Error
def foobar
puts "foo"
end
end
end
end
class StandardError
include A::B::Error
end
StandardError.new.kind_of?(A::B::Error)
StandardError.new.kind_of?(A::B)
StandardError.included_modules #=> [A::B::Error,Kernel]
tipo de?diz que sim, Error possui todo o comportamento de A::B::Error (o que é normal, pois inclui A::B::Error), no entanto, não inclui todo o comportamento de A::B e, portanto, não é do tipo A::B.(digitando pato)
Agora há uma boa chance de que ruby-aws reabra uma das superclasses de NameError e inclua Amazon::AWS:Error lá.(remendação de macaco)
Você pode descobrir programaticamente onde o módulo está incluído na hierarquia com o seguinte:
class Class
def has_module?(module_ref)
if self.included_modules.include?(module_ref) and not self.superclass.included_modules.include?(module_ref)
puts self.name+" has module "+ module_ref.name
else
self.superclass.nil? ? false : self.superclass.has_module?(module_ref)
end
end
end
StandardError.has_module?(A::B::Error)
NameError.has_module?(A::B::Error)
Em relação à sua segunda pergunta, não consigo ver nada melhor do que
begin
#do AWS error prone stuff
rescue Exception => e
if Amazon::AWS::Error.constants.include?(e.class.name)
#awsError
else
whatever
end
end
(editar - o código acima não funciona como está:nome inclui o prefixo do módulo, o que não é o caso das matrizes de constantes.Definitivamente, você deve entrar em contato com o mantenedor da lib, a classe AWSError parece mais uma classe de fábrica para mim:/)
Não tenho Ruby-aws aqui e o site caliban está bloqueado pelo firewall da empresa, então não posso testar muito mais.
Em relação à inclusão:essa pode ser a coisa que está fazendo o patch do macaco na hierarquia StandardError.Não tenho mais certeza, mas provavelmente fazê-lo na raiz de um arquivo fora de cada contexto inclui o módulo no Object ou na metaclasse Object.(isto é o que aconteceria no IRB, onde o contexto padrão é Object, não tenho certeza em um arquivo)
de picareta em módulos :
A couple of points about the include statement before we go on. First, it has nothing to do with files. C programmers use a preprocessor directive called #include to insert the contents of one file into another during compilation. The Ruby include statement simply makes a reference to a named module. If that module is in a separate file, you must use require to drag that file in before using include.
(editar - não consigo comentar usando este navegador: / yay para plataformas bloqueadas)
Outras dicas
Bem, pelo que posso dizer:
Class.new( StandardError )
Está criando uma nova classe com StandardError como classe base, portanto, não será um Amazon::AWS::Error.Ele está definido apenas naquele módulo, provavelmente por isso é um kind_of?Amazon::AWS::Erro.Provavelmente não é uma espécie de?Amazon::AWS porque talvez os módulos não sejam aninhados para fins de kind_of??
Desculpe, não conheço muito bem os módulos em Ruby, mas definitivamente a classe base será StandardError.
ATUALIZAR:Por falar nisso, dos documentos Ruby:
obj.kind_of?(class) => verdadeiro ou falso
Retorna verdadeiro se class for a classe de obj ou se class for uma das superclasses de obj ou módulos incluídos em obj.
Só queria intervir:Eu concordaria que isso é um bug no código lib.Provavelmente deveria ler:
unless Amazon::AWS::Error.const_defined?( err_class )
kls = Class.new( StandardError )
Amazon::AWS::Error.const_set(err_class, kls)
kls.include Amazon::AWS::Error
end
Um problema que você está enfrentando é que Amazon::AWS::Error::AWSError
na verdade não é uma exceção.Quando raise
é chamado, ele verifica se o primeiro parâmetro responde ao exception
método e usará o resultado disso.Qualquer coisa que seja uma subclasse de Exception
retornará quando exception
é chamado para que você possa fazer coisas como raise Exception.new("Something is wrong")
.
Nesse caso, AWSError
tem exception
configurado como um leitor de atributos que define o valor na inicialização para algo como Amazon::AWS::Error::SOME_ERROR
.Isso significa que quando você ligar raise Amazon::AWS::Error::AWSError.new(SOME_XML)
Ruby acaba ligando Amazon::AWS::Error::AWSError.new(SOME_XML).exception
que retornará uma instância de Amazon::AWS::Error::SOME_ERROR
.Como foi apontado por um dos outros respondentes, esta classe é uma subclasse direta de StandardError
em vez de ser uma subclasse de um erro comum da Amazon.Até que isso seja corrigido, a solução de Jean é provavelmente sua melhor aposta.
Espero que isso tenha ajudado a explicar mais sobre o que realmente está acontecendo nos bastidores.