Err
Err is an abstraction to report located errors and warnings to the user.
The canonical syntax for an error produced by this lib is:
File "my-file", line 42, character 6-11:
42 | Hello World
^^^^^
Error: Some message that gives a general explanation of the issue.
Followed by more details.
It is heavily inspired by dune's user_messages and uses dune's error message rendering under the hood.
A value of type t
is an immutable piece of information that the programmer intends to report to the user, in a way that breaks flow. To give some concrete examples:
exception E of t
The exception that is raised to break the control flow of programs using Err
.
Examples:
When reporting an error:
if had_error then Err.raise [ Pp.text "An error occurred" ]
When requesting an exit code:
if shall_exit_42 then Err.exit 42
The standard usage for this library is to wrap entire sections in a protect
, which takes care of catching E
and handling it accordingly. You may also catch this exception manually if you need, for more advanced uses (in which case you'll take care of recording and re-raising backtraces, etc).
val sexp_of_t : t -> Sexplib0.Sexp.t
Return a Sexp
to inspect what inside t
. Note the sexp is incomplete and it is not meant for supporting any kind of round trip serialization (there is no t_of_sexp
). Rather, this is for quick inspection of what's inside t
. We think exposing this can help accommodating some use cases, making it easier to write expect tests involving Err
, etc.
This part allows breaking the control flow with an exception indicating a request to end the program with a given exit code.
module Exit_code : sig ... end
The handling of exit code is based on Cmdliner
conventions.
val exit : Exit_code.t -> _
Request the termination of the program with the provided exit code. Make sure you have documented that particular exit code in the man page of your CLI. We recommend to stick to the error codes exposed by Exit_code
, which are documented by default and pervasively used by existing CLIs built with cmdliner. Raises E
.
module Style : sig ... end
You may decorate the messages built with this library with styles that will make them look awesome in a console. Enhance your users experience today!
val raise :
?loc:Loc.t ->
?hints:Style.t Pp.t list ->
?exit_code:Exit_code.t ->
Style.t Pp.t list ->
_
Raise a user error. You may override exit_code
with the requested exit code to end the program with. It defaults to Exit_code.some_error
.
Example:
let unknown_var var =
Err.raise
~loc
[ Pp.textf "Unknown variable '%s'" var ]
~hints:(Err.did_you_mean var ~candidates:[ "foo"; "bar"; "baz" ])
;;
val reraise :
Stdlib.Printexc.raw_backtrace ->
t ->
?loc:Loc.t ->
?hints:Style.t Pp.t list ->
?exit_code:Exit_code.t ->
Style.t Pp.t list ->
_
Reraise with added context. Usage:
match do_x (Y.to_x y) with
| exception Err.E e ->
let bt = Printexc.get_raw_backtrace () in
Err.reraise bt e [ Pp.text "Trying to do x with y"; Y.pp y ]
val create :
?loc:Loc.t ->
?hints:Style.t Pp.t list ->
?exit_code:Exit_code.t ->
Style.t Pp.t list ->
t
Create a err and return it, instead of raising it right away.
val append : ?exit_code:Exit_code.t -> t -> t -> t
append ?exit_code t1 t2
creates a new err that contains all messages of t1 and t2. The exit_code of this new t
may be specified, otherwise it will be that of t2
.
val of_stdune_user_message :
?exit_code:Exit_code.t ->
Stdune.User_message.t ->
t
This is exposed to help with libraries compatibility if needed.
val ok_exn : ('a, t) Stdlib.result -> 'a
Helper to raise a user error from a result type.
ok_exn (Ok x)
is x
ok_exn (Error msg)
is Stdlib.raise (E msg)
Whether that is during a migration, or to keep experimenting, we are currently exploring other ways to build and raise errors, using things like sexp, json or dyn.
val create_s :
?loc:Loc.t ->
?hints:Style.t Pp.t list ->
?exit_code:Exit_code.t ->
string ->
Sexplib0.Sexp.t ->
t
val raise_s :
?loc:Loc.t ->
?hints:Style.t Pp.t list ->
?exit_code:Exit_code.t ->
string ->
Sexplib0.Sexp.t ->
_
val reraise_s :
Stdlib.Printexc.raw_backtrace ->
t ->
?loc:Loc.t ->
?hints:Style.t Pp.t list ->
?exit_code:Exit_code.t ->
string ->
Sexplib0.Sexp.t ->
_
Reraise with added context. Usage:
match do_x x with
| exception Err.E e ->
let bt = Printexc.get_raw_backtrace () in
Err.reraise_s bt e "Trying to do x with y" [%sexp { y : Y.t }]
When you need to render a Sexp.t
into a _ Pp.t
paragraph, things may become tricky - newlines inserted in surprising places, etc. This functions attempts to do an OK job at it.
val did_you_mean : string -> candidates:string list -> Style.t Pp.t list
Produces a "Did you mean ...?" hint
Set by the For_test.wrap
when wrapping sections for tests, accessed by libraries if needed.
This return the number of errors that have been emitted via Err.error
since the last reset_counts
. Beware, note that errors raised as exceptions via functions such as Err.raise
do not affect the error count. The motivation is to allow exceptions to be caught without impacting the overall exit code.
A convenient wrapper for Err.error_count () > 0
.
This is useful if you are trying not to stop at the first error encountered, but still want to stop the execution at a specific breakpoint after some numbers of errors. To be used in places where you want to stop the flow at a given point rather than returning meaningless data.
Return the number of warnings that have been emitted via Err.warning
since the last reset_counts
.
val prerr : ?reset_separator:bool -> t -> unit
Print to stderr
(not thread safe). By default, prerr
will start by writing a blank line on stderr
if Err
messages have already been emitted during the lifetime of the program. That is a reasonable default to ensure that err messages are always nicely separated by an empty line, to make them more readable. However, if you structure your output manually, perhaps you do not want this. If reset_separator=true
, this behavior is turned off, and the first message of this batch will be printed directly without a leading blank line.
This part of the library allows the production of messages that do not raise.
For example: - Emitting multiple errors before terminating - Non fatal Warnings - Debug and Info messages
Errors and warnings are going to affect error_count
(and resp. warning_count
), which is going to be used by protect
to impact the exit code of the application. Use with care.
Emit an error on stderr and increase the global error count.
Emit a warning on stderr and increase the global warning count.
Emit a information message on stderr. Required verbosity level of Info
or more, disabled by default.
The last argument to debug
is lazy in order to avoid the allocation when debug messages are disabled. This isn't done with the other functions, because we don't expect other logging functions to be used in a way that impacts the program's performance, and using lazy causes added programming friction.
To be used by command line handlers, as well as tests.
val protect :
?exn_handler:(exn -> t option) ->
(unit -> 'a) ->
('a, int) Stdlib.Result.t
protect f
will take care of running f
, and catch any user error. If the exit code must be affected it is returned as an Error
. This also takes care of catching uncaught exceptions and printing them to the screen. You may provide exn_handler
to match on custom exceptions and turn them into Err
for display and exit code. Any uncaught exception will be reported as an internal errors with a backtrace. When Err.am_running_test ()
is true the backtrace is redacted to avoid making expect test traces too brittle. protect
starts by performing a reset of the error and warning counts with a call to reset_counts
.
module For_test : sig ... end
module Private : sig ... end
Private
is used by Err_config
. We mean both libraries to work as companion libs. Note any of this can change without notice and without requiring a semver bump, so use at your own risk (or don't).