include module type of Tezos_error_monad.Error_monad
type error_category = [
| `Branch
Errors that may not happen in another context
| `Temporary
Errors that may not happen in a later context
| `Permanent
Errors that will happen no matter the context
]
Categories of error
Note: this is only meaningful within the protocol. It may be removed from the error monad and pushed to the protocol environment in the future. See https://gitlab.com/tezos/tezos/-/issues/1576
Assembling the different components of the error monad.
The main error type.
Whenever you add a variant to this type (with type Error_monad.error += …
) you must also register the error with register_error_kind
(or register_recursive_error_kind
if the error payload contains errors).
These errors are not meant to be inspected in general. Meaning that they should not be matched upon. Consequently it is acceptable to register an error in an implementation file and not mention it in the corresponding interface file.
CORE
: encoding and pretty-printing for errors
include Tezos_error_monad.Sig.CORE
with type error := error
and type error_category := error_category
val pp : Stdlib.Format.formatter -> error -> unit
val register_error_kind :
error_category ->
id:string ->
title:string ->
description:string ->
?pp:(Stdlib.Format.formatter -> 'err -> unit) ->
'err Data_encoding.t ->
(error -> 'err option) ->
('err -> error) ->
unit
The error data type is extensible. Each module can register specialized error serializers id
unique name of this error. Ex.: overflow_time_counter title
more readable name. Ex.: Overflow of time counter description
human readable description. Ex.: The time counter overflowed while computing delta increase pp
formatter used to pretty print additional arguments. Ex.: The time counter overflowed while computing delta increase. Previous value %d. Delta: %d encoder
decoder
data encoding for this error. If the error has no value, specify Data_encoding.empty
Same as register_error_kind
but allow errors to wrap other errors.
The encoding argument is a function which will be given the encoding of errors as argument so that you can encode errors in errors using a fixpoint.
Another difference with register_error_kind
is that pp
is mandatory.
Classify an error using the registered kinds
type error += private
| Unclassified of string
Catch all error when 'deserializing' an error.
Catch all error when 'serializing' an error.
Error documentation
val pp_info : Stdlib.Format.formatter -> error_info -> unit
find_info_of_error e
retrieves the `error_info` associated with the given error `e`.
val get_registered_errors : unit -> error_info list
Retrieves information of registered errors
WITH_WRAPPED
: wrapping of errors from other instantiations within this one. Specifically, this is used to wrap errors of the economic protocol (e.g., operation is invalid) within the errors of the shell (e.g., failed to validate protocol data).
Functions from this module should only be used within the environment.
include Tezos_error_monad.Sig.WITH_WRAPPED with type error := error
The purpose of this module is to wrap a specific error monad E
into a more general error monad Eg
.
val register_wrapped_error_kind :
(module Wrapped_error_monad) ->
id:string ->
title:string ->
description:string ->
unit
Same as register_error_kind
but for a wrapped error monad. The codec is defined in the module parameter. It makes the category of the error Wrapped
instead of Main
.
TzTrace
: trace module specific to the Tezos Error monad. The trace
type of this module is meant to become abstract in the medium-term (see https://gitlab.com/tezos/tezos/-/issues/1577).
include Tezos_lwt_result_stdlib.Lwtreslib.TRACED_MONAD
with type 'error trace := 'error TzTrace.trace
Import the non-traced modules as-is
include Bare_sigs.Monad.S
The tower of monads
The Lwt monad: for concurrency
Syntax module for Lwt. This is intended to be opened locally in functions which use Lwt for control-flow. Within the scope of this module, the code can include binding operators, leading to a let
-style syntax.
The (generic) Result monad: for success/failure
Syntax module for Result. This is intended to be opened locally in functions which use result
for control-flow. Within the scope of this module, the code can include binding operators, leading to a let
-style syntax.
The combined Lwt+Result monad: for concurrent successes/failures
Syntax module for Lwt+Result. This is intended to be opened locally in functions which use Lwt and result
for control-flow. Within the scope of this module, the code can include binding operators, leading to a let
-style syntax.
Syntax module (with let and returns) for the TzResult monad (i.e., the TzTraced Result monad).
Syntax module (with let and returns) for the TzResult+Lwt monad (i.e., the Lwt TzTraced Result monad
Syntax module (with let and returns) for the error-agnostic Result monad is available under the name Result_syntax
from the TRACED_MONAD
. Unlike Tzresult_syntax
, with syntax module
fail
does not wrap errors in traces,- there is no
and*
(because there is no way to compose errors).
Syntax module (with let and returns) for the Lwt and error-agnostic Result combined monad is available under the name Lwt_result_syntax
from the TRACED_MONAD
. Unlike Lwt_tzresult_syntax
, with syntax module
fail
does not wrap errors in traces,- there is no
and*
(because there is no way to compose errors).
MONAD_EXTENSION
: the Tezos-specific extension to the monad part of Error_monad
. It includes
- consistent defaults,
- some tracing helpers,
- some other misc helpers.
include Tezos_error_monad.Sig.MONAD_EXTENSION
with type error := error
and type 'error trace := 'error TzTrace.trace
type 'a tzresult = ('a, tztrace) Stdlib.result
val return : 'a -> ('a, 'e) Stdlib.result Lwt.t
val return_unit : (unit, 'e) Stdlib.result Lwt.t
val return_none : ('a option, 'e) Stdlib.result Lwt.t
val return_some : 'a -> ('a option, 'e) Stdlib.result Lwt.t
val return_nil : ('a list, 'e) Stdlib.result Lwt.t
val return_true : (bool, 'e) Stdlib.result Lwt.t
val return_false : (bool, 'e) Stdlib.result Lwt.t
val ok : 'a -> ('a, 'e) Stdlib.result
val (>>?) :
('a, 'e) Stdlib.result ->
('a -> ('b, 'e) Stdlib.result) ->
('b, 'e) Stdlib.result
val (>|?) : ('a, 'e) Stdlib.result -> ('a -> 'b) -> ('b, 'e) Stdlib.result
val (>>=?) :
('a, 'e) Stdlib.result Lwt.t ->
('a -> ('b, 'e) Stdlib.result Lwt.t) ->
('b, 'e) Stdlib.result Lwt.t
val (>|=?) :
('a, 'e) Stdlib.result Lwt.t ->
('a -> 'b) ->
('b, 'e) Stdlib.result Lwt.t
val (>>?=) :
('a, 'e) Stdlib.result ->
('a -> ('b, 'e) Stdlib.result Lwt.t) ->
('b, 'e) Stdlib.result Lwt.t
val (>|?=) :
('a, 'e) Stdlib.result ->
('a -> 'b Lwt.t) ->
('b, 'e) Stdlib.result Lwt.t
Pretty-prints the top error of a trace
A serializer for result of a given type
record_trace err res
is either res
if res
is Ok _
, or it is Error (Trace.cons err tr)
if res
is Error tr
.
In other words, record_trace err res
enriches the trace that is carried by res
(if it is carrying a trace) with the error err
. It leaves res
untouched if res
is not carrying a trace.
You can use this to add high-level information to potential low-level errors. E.g.,
record_trace
Failure_to_load_config
(load_data_from_file config_encoding config_file_name)
Note that record_trace
takes a fully evaluated error err
as argument. It means that, whatever the value of the result res
, the error err
is evaluated. This is not an issue if the error is a simple expression (a literal or a constructor with simple parameters). However, for any expression that is more complex (e.g., one that calls a function) you should prefer record_trace_eval
.
trace
is identical to record_trace
but applies to a promise. More formally, trace err p
is a promise that resolves to Ok v
if p
resolves to Ok v
, or it resolves to Error (Trace.cons err tr)
if res
resolves to Error tr
.
In other words, trace err p
enriches the trace that p
resolves to (if it does resolve to a trace) with the error err
. It leaves the value that p
resolves to untouched if it is not a trace.
You can use this to add high-level information to potential low-level errors.
Note that, like record_trace
, trace
takes a fully evaluated error as argument. For a similar reason as explained there, you should only use trace
with simple expressions (literal or constructor with simple parameters) and prefer trace_eval
for any other expression (such as ones that include functions calls).
record_trace_eval
is identical to record_trace
except that the error that enriches the trace is wrapped in a function that is evaluated only if it is needed. More formally record_trace_eval mkerr res
is res
if res
is Ok _
, or it is Error (Trace.cons (mkerr ()) tr)
if res
is Error tr
.
You can achieve the same effect by hand with
match res with
| Ok _ -> res
| Error tr -> Error (Trace.cons (mkerr ()) tr)
Prefer record_trace_eval
over record_trace
when the enriching error is expensive to compute or heavy to allocate.
trace_eval
is identical to trace
except that the error that enriches the trace is wrapped in a function that is evaluated only if and when it is needed. More formally trace_eval mkerr p
is a promise that resolves to Ok v
if p
resolves to Ok v
, or it resolves to Error (Trace.cons (mkerr ()) tr)
if p
resolves to Error tr
.
You can achieve the same effect by hand with
p >>= function
| Ok _ -> p
| Error tr ->
Lwt.return (Error (Trace.cons (mkerr ()) tr))
Note that the evaluation of the error can be arbitrarily delayed. Avoid using references and other mutable values in the function mkerr
.
Prefer trace_eval
over trace
when the enriching error is expensive to compute or heavy to allocate or when evaluating it requires the use of Lwt.
val error_unless : bool -> 'err -> (unit, 'err TzTrace.trace) Stdlib.result
error_unless flag err
is Ok ()
if b
is true
, it is Error (Trace.make err)
otherwise.
val error_when : bool -> 'err -> (unit, 'err TzTrace.trace) Stdlib.result
error_when flag err
is Error (Trace.make err)
if b
is true
, it is Ok ()
otherwise.
fail_unless flag err
is Lwt.return @@ Ok ()
if b
is true
, it is Lwt.return @@ Error (Trace.make err)
otherwise.
fail_when flag err
is Lwt.return @@ Error (Trace.make err)
if b
is true
, it is Lwt.return @@ Ok ()
otherwise.
val unless :
bool ->
(unit -> (unit, 'trace) Stdlib.result Lwt.t) ->
(unit, 'trace) Stdlib.result Lwt.t
unless b f
is f ()
if b
is false
and it is a promise already resolved to Ok ()
otherwise.
You can use unless
to avoid having to write an if
statement that you then need to populate entirely to satisfy the type-checker. E.g, you can write unless b f
instead of if not b then f () else return_unit
.
val when_ :
bool ->
(unit -> (unit, 'trace) Stdlib.result Lwt.t) ->
(unit, 'trace) Stdlib.result Lwt.t
when_ b f
is f ()
if b
is true
and it is a promise already resolved to Ok ()
otherwise.
You can use when_
to avoid having to write an if
statement that you then need to populate entirely to satisfy the type-checker. E.g, you can write when_ b f
instead of if b then f () else return_unit
.
val dont_wait :
(unit -> (unit, 'trace) Stdlib.result Lwt.t) ->
('trace -> unit) ->
(exn -> unit) ->
unit
Wrapper around Lwt_utils.dont_wait
Exception-Error bridge
This part of the interface groups functions that are used to interact with code that raises exceptions. Typically, you should be using these functions when calling into a library that raises exceptions.
Remember that the keyword error
is for failure within the Result
monad (or, more specifically, the TracedResult
monad) whilst fail
is for failure within the LwtResult
monad (or, more specifically, the LwtTracedResult
monad).
Failing: to error out and to fail
This sub-part of the interface groups functions that fail (either in the TracedResult
monad or the LwtTracedResult
monad) whilst carrying information provided as argument. When reading this sub-part you should read error
and fail
as verbs. E.g., error_with_exn
errors out and carries a provided exception. The next sub-part will group noun-like, declarative functions.
val error_with :
('a, Stdlib.Format.formatter, unit, 'b tzresult) Stdlib.format4 ->
'a
error_with fmt …
errors out: it fails within the TracedResult
monad. The payload of the Error
constructor is unspecified beyond the fact that it includes the string formatted by fmt …
. E.g.,
if n < 0 then
error_with "Index (%d) is negative" n
else if n >= Array.length a then
error_with "Index (%d) is beyond maximum index (%d)" n (Array.length a - 1)
else
Ok a.(n)
Note: this is somewhat equivalent to Stdlib.failwith
in that it is a generic failure mechanism with a simple error message. Like Stdlib.failwith
it should be replaced by a more specific error mechanism in most cases.
val failwith :
('a, Stdlib.Format.formatter, unit, 'b tzresult Lwt.t) Stdlib.format4 ->
'a
failwith fmt …
fails: it fails within the LwtTracedResult
monad. The payload of the Error
constructor is unspecified beyond the fact that it includes the string formatted by fmt …
. E.g.,
match find key store with
| None ->
failwith "Key %a not found in store" pp_key key
| Some value ->
LwtResult.return value
Note: this is somewhat equivalent to Stdlib.failwith
in that it is a generic failure mechanism with a simple error message. Like Stdlib.failwith
it should be replaced by a more specific error mechanism in most cases.
error_with_exn exc
errors out: it fails within the TracedResult
monad. The payload of the Error
constructor is unspecified but it includes the exception.
It is meant as a way to switch from exception-based error management to tzresult-based error management, e.g., when calling external libraries that use exceptions.
try Ok (parse_input s) with Lex_error | Parse_error as exc -> error_with_exn exc
Whilst it is useful in specific places, it is generally better to use a dedicated error.
fail_with_exn exc
fails: it fails within the LwtTracedResult
monad. The payload of the Error
constructor is unspecified but it includes the info from the exception.
It is meant as a way to switch, inside of Lwt code, from exception-based error management to tzresult-based error management, e.g., when calling external libraries that use exceptions.
Lwt.catch
(fun () -> parse_input s)
(function
| Lex_error | Parse_error as exc -> fail_with_exn exc
| exn -> raise exn (* re-raise by default *))
Whilst it is useful in specific places, it is generally better to use a dedicated error.
Conversions: an exception, an error, a trace, a result
This sub-part of the interface groups declarative functions that convert between different styles of error (exceptions, errors, traces, results). By themselves these functions have no effect within the Result or LwtResult monad, and they are generally used along with constructors or combinators.
val error_of_exn : exn -> error
error_of_exn e
is an error that carries the exception e
. This function is intended to be used when interacting with a part of the code (most likely an external library) which uses exceptions.
val error_of_fmt :
('a, Stdlib.Format.formatter, unit, error) Stdlib.format4 ->
'a
error_of_fmt …
is like error_with …
but the error isn't wrapped in a trace in a result
. Instead, an error is returned and the caller is expected to pass it to whichever error-combinator is appropriate to the situation. E.g.,
fail_unless (check_valid input) (error_of_fmt "Invalid_input: %a" pp input)
Standard errors
type error +=
| Exn of exn
Wrapped OCaml/Lwt exception
Catching exceptions
protect
is a wrapper around Lwt.catch
where the error handler operates over trace
instead of exn
. Besides, protect ~on_error ~canceler ~f
may *cancel* f
via a Lwt_canceler.t
.
More precisely, protect ~on_error ~canceler f
runs f ()
. An Lwt failure triggered by f ()
is wrapped into an Exn
. If a canceler
is given and Lwt_canceler.cancellation canceler
is determined before f ()
, a Canceled
error is returned.
Errors are caught by ~on_error
(if given), otherwise the previous value is returned. An Lwt failure triggered by ~on_error
is wrapped into an Exn
val catch : ?catch_only:(exn -> bool) -> (unit -> 'a) -> 'a tzresult
catch f
executes f
within a try-with block and wraps exceptions within a tzresult
. catch f
is equivalent to try Ok (f ()) with e -> Error (error_of_exn e)
.
If catch_only
is set, then only exceptions e
such that catch_only e
is true
are caught.
Whether catch_only
is set or not, this function never catches non-deterministic runtime exceptions of OCaml such as Stack_overflow
and Out_of_memory
.
catch_e
is like catch
but when f
returns a tzresult
. I.e., catch_e f
is equivalent to try f () with e -> Error (error_of_exn e)
.
catch_only
has the same use as with catch
. The same restriction on catching non-deterministic runtime exceptions applies.
val catch_f :
?catch_only:(exn -> bool) ->
(unit -> 'a) ->
(exn -> error) ->
'a tzresult
catch_f f handler
is equivalent to map_error (catch f) handler
. In other words, it catches exceptions in f ()
and either returns the value in an Ok
or passes the exception to handler
for the Error
.
No attempt is made to catch the exceptions raised by handler
.
catch_only
has the same use as with catch
. The same restriction on catching non-deterministic runtime exceptions applies.
catch_s
is like catch
but when f
returns a promise. It is equivalent to
Lwt.try_bind f
(fun v -> Lwt.return (Ok v))
(fun e -> Lwt.return (Error (error_of_exn e)))
catch_only
has the same use as with catch
. The same restriction on catching non-deterministic runtime exceptions applies.
catch_es
is like catch_s
but when f
returns a promise of a tzresult
. I.e., catch_es f
is equivalent to Lwt.catch f (fun e -> Lwt.return_error (error_of_exn e))
.
catch_only
has the same use as with catch
. The same restriction on catching non-deterministic runtime exceptions applies.
Misc