Recordar, presente singular en segunda persona imperativo de recidere : - retroceder, ven a nada; para reducir
assoc / update en los ex-data de recrecio de erroresdeferrordeferror-groupgetCurrentSanitizationLevel()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-msdeferror-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.