Herencia de excepciones de Ruby con clases generadas dinámicamente
-
09-06-2019 - |
Pregunta
Soy nuevo en Ruby, así que tengo problemas para entender este extraño problema de excepción que estoy teniendo.Estoy usando la gema ruby-aaws para acceder a Amazon ECS: http://www.caliban.org/ruby/ruby-aws/.Esto define una clase 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
Esto significa que si recibe un código de error como AWS.InvalidParameterValue
, esto producirá (en su variable de excepción) una nueva clase Amazon::AWS::Error::InvalidParameterValue
que es una subclase de StandardError
.
Ahora aquí es donde se pone raro.Tengo un código que se parece a este:
begin
do_aws_stuff
rescue Amazon::AWS::Error => error
puts "Got an AWS error"
end
Ahora si do_aws_stuff
lanza un NameError
, mi bloque de rescate se activa.Parece que Amazon::AWS::Error no es la superclase del error generado. Supongo que, dado que es un módulo, ¿todo es una subclase del mismo?Ciertamente si lo hago:
irb(main):007:0> NameError.new.kind_of?(Amazon::AWS::Error)
=> true
Dice true
, lo cual me parece confuso, especialmente teniendo en cuenta esto:
irb(main):009:0> NameError.new.kind_of?(Amazon::AWS)
=> false
¿Qué está pasando y cómo se supone que debo separar los errores de AWS de otros tipos de errores?¿Debo hacer 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
Eso parece excepcionalmente chiflado.Los errores arrojados tampoco son de clase AWSError; se generan así:
error = Amazon::AWS::Error::AWSError.new( xml )
raise error.exception
Entonces, las excepciones que estoy buscando rescue
from son los tipos de excepción generados que solo heredan de StandardError.
Para aclarar, tengo dos preguntas:
¿Por qué NameError, una excepción integrada en Ruby, es una
kind_of?(Amazon::AWS::Error)
, ¿cuál es un módulo?
Respuesta: Había dichoinclude Amazon::AWS::Error
en la parte superior de mi archivo, pensando que era algo así como una importación de Java o una inclusión de C++.Lo que esto realmente hizo fue agregar todo lo definido enAmazon::AWS::Error
(presente y futuro) a la clase Kernel implícita, que es un antepasado de cada clase.Esto significa cualquier cosa pasaríakind_of?(Amazon::AWS::Error)
.¿Cómo puedo distinguir mejor las excepciones creadas dinámicamente en
Amazon::AWS::Error
¿De otras excepciones aleatorias de otros lugares?
Solución
Ok, intentaré ayudar aquí:
Primero, un módulo no es una clase, te permite mezclar comportamientos en una clase.segundo vea el siguiente ejemplo:
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]
¿un poco?le dice que sí, Error posee el comportamiento Todo A::B::Error (lo cual es normal ya que incluye A::B::Error), sin embargo, no incluye todo el comportamiento de A::B y por lo tanto no es del tipo A::B.(escribiendo pato)
Ahora hay muchas posibilidades de que ruby-aws vuelva a abrir una de las superclases de NameError e incluya Amazon::AWS:Error allí.(parche de mono)
Puede averiguar mediante programación dónde está incluido el módulo en la jerarquía con lo siguiente:
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)
Respecto a tu segunda pregunta no veo nada mejor 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: el código anterior no funciona como está:El nombre incluye el prefijo del módulo, lo cual no es el caso de las matrices de constantes.Definitivamente deberías contactar al mantenedor de la biblioteca, la clase AWSError me parece más una clase de fábrica :/)
No tengo Ruby-aws aquí y el sitio de Caliban está bloqueado por el firewall de la compañía, por lo que no puedo realizar pruebas mucho más.
En cuanto a incluir:eso podría ser lo que hace que el mono parchee la jerarquía StandardError.Ya no estoy seguro, pero lo más probable es que hacerlo en la raíz de un archivo fuera de cada contexto incluya el módulo en Objeto o en la metaclase de Objeto.(Esto es lo que sucedería en IRB, donde el contexto predeterminado es Objeto, no estoy seguro en un archivo)
desde el pico en 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: parece que no puedo comentar usando este navegador :/ yay para plataformas bloqueadas)
Otros consejos
Bueno, por lo que puedo decir:
Class.new( StandardError )
Está creando una nueva clase con StandardError como clase base, por lo que no será un Amazon::AWS::Error en absoluto.Simplemente está definido en ese módulo, por lo que probablemente sea un tipo_de?Amazon::AWS::Error.¿Probablemente no sea una especie de?Amazon::AWS porque tal vez los módulos no se anidan para fines de kind_of.?
Lo siento, no conozco muy bien los módulos en Ruby, pero definitivamente la clase base será StandardError.
ACTUALIZAR:Por cierto, de los documentos de rubí:
obj.kind_of?(clase) => verdadero o falso
Devuelve verdadero si clase es la clase de obj, o si clase es una de las superclases de obj o módulos incluidos en obj.
Sólo quería intervenir:Estoy de acuerdo en que esto es un error en el código lib.Probablemente debería leer:
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
Un problema con el que te estás encontrando es que Amazon::AWS::Error::AWSError
en realidad no es una excepción.Cuando raise
se llama, mira para ver si el primer parámetro responde a la exception
método y utilizará el resultado de eso en su lugar.Todo lo que sea una subclase de Exception
regresará solo cuando exception
se llama para que puedas hacer cosas como raise Exception.new("Something is wrong")
.
En este caso, AWSError
tiene exception
configurar como un lector de atributos que define el valor en la inicialización de algo como Amazon::AWS::Error::SOME_ERROR
.Esto significa que cuando llamas raise Amazon::AWS::Error::AWSError.new(SOME_XML)
Ruby termina llamando Amazon::AWS::Error::AWSError.new(SOME_XML).exception
que devolverá una instancia de Amazon::AWS::Error::SOME_ERROR
.Como señaló uno de los otros respondedores, esta clase es una subclase directa de StandardError
en lugar de ser una subclase de un error común de Amazon.Hasta que esto se rectifique, la solución de Jean es probablemente su mejor opción.
Espero que haya ayudado a explicar más de lo que realmente sucede detrás de escena.