Module spirit::guide::principles[][src]

Expand description

The basic principles

At the core, the Spirit is the top level manager of things. It is parametrized by two structures, one to hold parsed command line (implementing StructOpt), the other for holding the parsed configuration options (implementing serde’s Derive). It parses the command line, enriched with some options of its own. It uses these options to find the configuration files and loads the configuration for the first time.

To manage the life time of the application, it is possible to register hooks into the manager. They are called on certain events ‒ when new configuration is loaded (both the first time and when it is reloaded), when the application is about to be terminated, on registered signal, etc.

It then runs the „body“ of the whole application and leaves it to run. The manager does its job in its own background thread.

Note that many things here can be turned off (see further chapters about that) if they are not doing what you want.

Extensions and pipelines

Having a place to put callbacks might be handy a bit, but certainly nothing to make any fuss about (or write tutorials about). The added value of the library is the system of extensions, fragments and pipelines. There are other, related crates (eg. spirit-log) that provide these and allow easy and reusable plugging of functionality in.

An extension simply modifies the builder, usually by registering some callbacks. The fragments and pipelines are ways to build extensions from already existing parts. They are explained in detail in their own chapter.

Error handling conventions

In a library like this, quite a lot of things can go wrong. Some of the errors might come from the library, but many would come from user-provided code in callbacks, extensions, etc. Error are passed around as a boxed trait objects (the AnyError is just a boxed trait object). Other option would be something like the anyhow, but that would force the users of the library into a specific one. Future versions might go that way if there’s a clear winner between these crates, but until then we stay with only what std provides.

It is expected that most of these errors can’t be automatically handled by the application, therefore distinguishing types of the errors isn’t really a concern most of the time, though it is possible to get away with downcasting. In practice, most of the errors will end up somewhere in logs or other places where users can read them.

To make the errors more informative, the library constructs layered errors (or error chains). The outer layer is the high level problem, while the inner ones describe the causes of the problem. It is expected all the layers are presented to the user. When the errors are handled by the library (either in termination error or with unsuccessful configuration reload), the library prints all the layers. To replicate similar behaviour in user code, it is possible to use the log_error macro or log_error function.

Internally, the library uses the err-context crate to construct and handle such errors. In addition to constructing such errors, the crate also allows some limited examination of error chains. However, users are not forced to use that crate as the chains constructed are based directly on the std::error::Error trait and are therefore compatible with errors constructed in any other way.