Recordar, presente singular en segunda persona imperativo de recidere
: - retroceder, ven a nada; para reducir
assoc
/ update
en los ex-data de recrecio de erroresdeferror
deferror-group
getCurrentSanitizationLevel()
createSuppressionMap(...)
sanitize(Throwable)
, sanitize(Throwable, IPersistentMap)
ErrorForm
personalizada ex-info
de Clojure es una construcción muy útil: puede adjuntar un mapa de datos arbitrarios a una excepción lanzada, lo que permite un código que ha captado la excepción de examinar, registrar o procesar de otro modo estos ex-data
.
Por ejemplo, las declaraciones assert
proporcionan valiosas verificaciones de cordura en la lógica de negocios, pero cuando fallan, generalmente es muy deseable saber cómo fallaron. Uno podría intentar insertar esta información en la cadena de excepción, pero a veces los datos relevantes son demasiado grandes para un mensaje de excepción manejable. En su lugar, verificar la propiedad deseada y lanzar un ex-info
con todos los datos relevantes adjuntos puede preservar la sucinción de los mensajes de error al tiempo que guarda al desarrollador una gran cantidad de tiempo, especialmente cuando la depuración en el repl.
Una de las principales debilidades de ex-info
es que su uso puede fomentar las excepciones ad-hoc sin estándares: si atrapa un ex-info, su tipo es el mismo que todos los demás ex-infos, su cadena es arbitraria y no Solo no puede contar con ninguna clave en particular que aparezca en los ex-data
, es muy posible que el mapa esté completamente vacío.
Si desea disfrutar de los beneficios de usar ex-info
ampliamente en un proyecto grande, pero también desea retener una medida de cordura Los beneficios de los errores bien definidos, es probable que eventualmente recurra, en cada componente lógico de su aplicación, ya sea para romper con modismos de Clojure universales o definir el suyo definiendo un conjunto estándar de "funciones de lanzamiento" que usan ex-info
pero garantiza una cierta rigidez: quizás un prefijo común a las cadenas de excepción, tal vez ciertas claves garantizadas en los mapas ex-data
.
El propósito principal de esta biblioteca, recordar , es proporcionar herramientas para aliviar este proceso. Proporciona utilidades para definir formularios ex-info estándar, así como la capacidad para verificar en el tiempo de compilación que se utilizan según lo previsto.
Todos los mapas ex-info
generados por herramientas en recide
contienen al menos dos teclas:
:recide/error
, cuyo valor es una instancia de ErrorForm
.ErrorForm
(predeterminada en Recreción es :recide/type
). La documentación de la API de Clojure se puede encontrar aquí. La documentación de la API de Java se puede encontrar aquí.
recide.core/insist
es análogo a assert
. Su firma es la misma, y al igual que assert
solo se ejecuta cuando clojure.core/*assert*
es verdadero.
Pero en lugar de lanzar una AssertionError
, arroja un ex-info
con una cadena explicativa de la forma: "Falló de afirmación: <expresión afirmada o mensaje suministrado>". El tipo en los Ex-Data es :recide/assertion
. Hay otras dos teclas utilizadas por insist
:
:expression
, cuyo valor es la expresión real contenida en la insist
:values
, cuyo valor es un mapa de cada variable utilizada en la expresión a su valor en el momento de la falla. :values
solo están presentes cada vez que recide.impl/*capture-insists*
es verdadero, pero es falso de forma predeterminada. En el tiempo de carga de la biblioteca, se establece en verdadero si al menos uno de los siguientes es verdadero:
La firma de insist
es [[expr] [expr message]]
, y los Ex-Data en el EX-Info resultante tienen la siguiente forma:
{ :recide/type :recide/assertion ,
:expression <expr>
:values { ... }}
Ejemplo en uso:
( let [y not
x true ]
( insist ( y x)))
; ; Unhandled clojure.lang.ExceptionInfo
; ; Assertion failed: (y x)
; ; {:expression (y x),
; ; :values {y #function[clojure.core/not],
; ; x true},
; ; :recide/type :recide/assertion}
recide.core/error
tiene dos aridades: ([type msg data] [type msg data cause])
. error
construye un ex-info
con los data
del mapa conectados, cuyo tipo (nuevamente, significado por :recide/type
de forma predeterminada) es type
. El suministro de una cause
simplemente le da a la excepción una causa según el idioma Java.
( let [x " not b! haha " ]
( raise :my-type
" my explanation! "
{ :a " a "
:b x}))
; ; #error {
; ; :cause "my explanation!"
; ; :data {:a "a",
; ; :b "not b! haha",
; ; :recide/type :my-type, :recide/error #object[...]}
; ; :via
; ; [{:type clojure.lang.ExceptionInfo
; ; :message "my explanation!"
; ; :data {:a "a",
; ; :b "not b! haha",
; ; :recide/type :my-type, :recide/error #object[...]}
; ; :at [clojure.core$ex_info invokeStatic "core.clj" 4725]}]
; ; :trace ... }
recide.core/raise
tiene las mismas dos aridades. raise
Lanza la excepción construida por error
.
assoc
/ update
en los ex-data de recrecio de errores Funciones de conveniencia assoc
recide.core/assoc-error
update
recide.core/update-error
A veces puede ser conveniente pasar y manipular una representación del mapa de un error antes de convertirlo en una excepción y lanzarlo. Para recide.core/error-map->throwable
fin, proporcionamos recide.core/error->map
.
Además, proporcionamos un "constructor" error-map
alternativo para errores que devuelven el formulario de mapa y el error-map?
.
Para cualquier error de type
de tipo con msg
de mensajes, data
ex de datos y cause
, el mapa persistente se ve así:
{ :recide/error <ErrorForm>,
:recide/type type,
:recide/msg msg,
:recide/cause cause,
:recide/data data}
Todas estas claves, excepto :recide/error
, se puede modificar suministrando una ErrorForm
personalizada (consulte a continuación para más detalles).
raise
y error
hacen muy poco para proporcionar tipos de excepción estándar. Para abordar esto aún más, Recrecy proporciona deferror
y deferror-group
.
deferror
deferror
es una macro que toma un nombre de error, un tipo y una cadena "genérica" que prefirirá los mensajes de todos los errores de este tipo. También, opcionalmente, toma una colección de claves requeridas. Si se especifican las claves requeridas, los errores de tiempo de compilación se lanzarán cada vez que las herramientas definidas por deferror
se usen sin especificar esas claves explícitamente en el código fuente.
Ejemplo de uso:
( deferror storage-timeout
:storage/timeout
" A storage operation timed out "
[ :method-at-fault :timeout-ms ])
En este ejemplo, este llamado a deferror
definirá dos nuevas macros, storage-timeout
y raise-storage-timeout
. Para su conveniencia, cualquier IDE competente podrá acceder a las documentos detallados en estos nuevos VAR:
> ( clojure.repl/doc storage-timeout)
; ; -------------------------
; ; my-ns/storage-timeout
; ; [[detail-str data] [detail-str data cause]]
; ; Macro
; ; Records this raise-site under :storage/timeout in recide, and expands into the equivalent of:
; ;
; ; (ex-info (str "A storage operation timed out: " detail-str)
; ; (assoc data :recide/type :storage/timeout)
; ; cause)
; ;
; ; The following keys are required in the data-map:
; ; #{:method-at-fault,
; ; :timeout-ms}
> ( clojure.repl/doc raise-storage-timeout)
; ; -------------------------
; ; [[detail-str data] [detail-str data cause]]
; ; Macro
; ; Records this raise-site under :storage/timeout in recide, and expands into:
; ;
; ; (raise :storage/timeout
; ; (str "A storage operation timed out: " detail-str)
; ; data
; ; cause)
; ;
; ; The following keys are required in the data-map:
; ; #{:method-at-fault,
; ; :timeout-ms}
Si intenta usar cualquiera de estos sin, en su mapa de datos, especificando cada una de las claves requeridas, el compilador Clojure lanzará una excepción:
> ( raise-storage-timeout " blah " { :method-at-fault 'not-really-a-method})
; ; Unhandled clojure.lang.ExceptionInfo
; ; Assertion failed: storage-timeout requires the following missing
; ; keys in its data: :timeout-ms
deferror-group
deferror-group
es una macro que define a toda una familia de errores. Se necesita un nombre de error, una declaración de tipo base y una cantidad de declaraciones de subtipo.
Cada declaración de tipo base debe ser una palabra clave que represente el espacio de nombres común para este grupo de errores, o una tupla cuyo primer elemento es una palabra clave y cuyo segundo elemento es una secuencia de claves requeridas. Las teclas especificadas aquí serán necesarias en cada subtipo.
Cada declaración de subtipo consiste en una secuencia cuyo primer término es un símbolo, el segundo término es una cadena genérica para el error, y el tercer término (opcional) es una secuencia de teclas requeridas para ese subtipo.
Ejemplo:
( deferror-group parse-err
( :query.invalid [ :expression ])
( find-spec " Invalid find spec " )
( inputs " Invalid inputs " [ :invalid ]))
En este ejemplo, hay dos tipos de error definidos :: :query.invalid/find-spec
y :query.invalid/inputs
. El primero requiere :expression
en su mapa de datos, pero el segundo requiere tanto :expression
como :invalid
.
Al igual que con deferror
, las utilidades producidas por deferror-group
tienen documentos detallados:
> ( clojure.repl/doc parse-err)
; ; -------------------------
; ; recide/parse-err
; ; [[subtype detail-str data] [subtype detail-str data cause]]
; ; Macro
; ; Records this raise-site under :query.invalid/<subtype> in recide, and expands into the
; ; equivalent of:
; ;
; ; (ex-info (str "<subtype-generic-str>: " detail-str)
; ; (assoc data
; ; :recide/type
; ; :query.invalid/<subtype>)
; ; cause)
; ;
; ; The following map shows, for each subtype, what keywords are required in
; ; the data map, and what the generic portion of the string will be:
; ;
; ; {:find-spec {:required #{:expression},
; ; :generic-str "Invalid find spec"},
; ; :inputs {:required #{:expression :invalid},
; ; :generic-str "Invalid inputs"}}
> ( clojure.repl/doc raise-parse-err)
; ; -------------------------
; ; recide/raise-parse-err
; ; [[subtype detail-str data] [subtype detail-str data cause]]
; ; Macro
; ; Records this raise-site under :query.invalid/<subtype> in recide, and expands into:
; ;
; ; (raise :query.invalid/<subtype>
; ; (str "<subtype-generic-str>: " detail-str)
; ; data
; ; cause)
; ;
; ; The following map shows, for each subtype, what keywords are required in
; ; the data map, and what the generic portion of the string will be:
; ;
; ; {:find-spec {:required #{:expression},
; ; :generic-str "Invalid find spec"},
; ; :inputs {:required #{:expression :invalid},
; ; :generic-str "Invalid inputs"}}
Como se ve antes, las claves requeridas generan errores en tiempo de compilación cuando se omiten.
> ( raise-parse-err :inputs " detailed this, detailed that " { :expression nil })
; ; Unhandled clojure.lang.ExceptionInfo
; ; Assertion failed: parse-err called with subtype :inputs requires
; ; the following missing keys in its data: :invalid
Si estamos utilizando palabras clave para designar tipos de error, sería útil poder catch
errores por medio de estas palabras clave. Recrecio proporciona try*
para este propósito. try*
es una macro que se expande al try
de Clojure, que es una de las pocas formas especiales de Clojure. En la mayoría de los casos, try*
y luego debería comportarse exactamente como try
. Se diferencia en que expone una funcionalidad catch
mejorada. Puedes atrapar:
instance?
Compruebe.ErrorForm
utilizada para construirlas)recide.core/try*
[( try* expr* catch-clause* finally-clause?)]
Macro
Expands to Clojure's try Special Form, allowing for enhanced `catch` clauses:
You can catch:
* Classes/Interfaces ( represents an instance? check)
`( catch RuntimeException e ...)`
* keywords ( recide error types ; fully-qualified: :namspace/name, wildcard: :namespace/*)
`( catch :library/error e ...)`
* arbitrary predicates
`( catch bad-error? e ...)`
You can also catch conjunctions/disjunctions of these:
* conjunction
`( catch :and [RuntimeException :library/error bad-error?] e ...)`
* disjunction
`( catch :or [IllegalArgumentException :library/error bad-error?] e ...)`
You can also negate each of these:
`( catch ( :not RuntimeException) e ...)`
`( catch :and [( :not RuntimeException) :library/* ] e ...)`
Otherwise, behavior should match 'normal' catch clauses in `clojure.core/try`.
Tenga en cuenta que puede usar palabras clave de la forma :namespace/*
como comodines para atrapar a las familias de errores de recuerdo, como las definidas por deferror-group
.
> ( try* ( raise :genus/species-1
" went extinct "
{ :year -1839421 })
( catch :genus/* e
( println ( :year ( ex-data e)))))
; ; -1839421
Recrecy proporciona una colección de herramientas para adquirir una versión desinfectada de una excepción que debe considerarse segura para registrar (pero como resultado no es útil).
Esta clase contiene un puñado de métodos de utilidad estática:
getCurrentSanitizationLevel()
Equivalente a desef'ing recide.sanex/*sanitization-level*
.
createSuppressionMap(...)
Crea un mapas ipersistente con las palabras clave apropiadas correspondientes a los args booleanos.
sanitize(Throwable)
, sanitize(Throwable, IPersistentMap)
Atajo a clojure ifn recide.sanex/sanitize
.
ErrorForm
personalizada Por defecto, los errores planteados por esta biblioteca usan ex-info
como constructor, recide.utils/serialize-throwable
y recide.utils/deserialize-throwable
para (de) serialización y en forma de mapa que usan :recide/type
, :recide/msg
, :recide/data
y :recide/cause
como sus palabras clave estándar.
Al definir una nueva ErrorForm
, puede cambiar todo este comportamiento para su propia biblioteca. Al modificar las palabras clave, puede "marca" errores que salen de su biblioteca. Puede intercambiar ex-info
por otro constructor de la misma aridad, que se espera que devuelva un IExceptionInfo
. Por ejemplo, las preocupaciones relacionadas con Java Inerop podrían motivar la creación de una nueva clase de excepción, mientras que los modismos de Clojure podrían motivar la retención de compatibilidad de Info.
Puede definir fácilmente su personalización con recide.core/def-error-form
Métodos no especificados predeterminados para recuperar los valores predeterminados de la biblioteca.
Los métodos de manejo de ErrorForm
en recide.core
. Para crear errores utilizando una ErrorForm
personalizada, puede generar fácilmente un conjunto completo de métodos de recordación adaptados específicamente a sus personalizaciones con recide.core/generate-library!
.
( ns my-library.error
( :require [recide.core :as rc]))
( rc/def-error-form custom-error-form
( type-kw [_] :my-library/type )
( constructor [_] my-library/error-constructor)
; ; all other methods are filled out with recide defaults.
( def ^:dynamic *capture-insists?* true )
( rc/generate-library! custom-error-form *capture-insists?*)
; ; recide.core/generate-library!
; ; [custom-error capture-flag]
; ; Macro
; ; Generates and defs custom versions of the following recide.core methods, tailored specifically
; ; to custom-error, with variable capture in the generated insist subject to capture-flag.
; ; * error
; ; * error?
; ; * error-map
; ; * error-map?
; ; * throwable->error-map
; ; * raise
; ; * insist
; ; * deferror
; ; * deferror-group
; ;
; ; custom-error should be an instance of recide.error/ErrorForm (see def-error-form).
; ; capture-flag must be resolvable to a dynamic var.