Expand description
The builder of Spirit
.
This is returned by the Spirit::new
.
Implementations§
source§impl<O, C> Builder<O, C>where
C: DeserializeOwned + Send + Sync + 'static,
O: Debug + StructOpt + Sync + Send + 'static,
impl<O, C> Builder<O, C>where
C: DeserializeOwned + Send + Sync + 'static,
O: Debug + StructOpt + Sync + Send + 'static,
sourcepub fn config_helper<Cfg, Extractor, Action, Name>(
self,
extractor: Extractor,
action: Action,
name: Name
) -> Selfwhere
Extractor: FnMut(&C) -> Cfg + Send + 'static,
Cfg: CfgHelper<O, C, Action>,
Name: Clone + Display + Send + Sync + 'static,
pub fn config_helper<Cfg, Extractor, Action, Name>(
self,
extractor: Extractor,
action: Action,
name: Name
) -> Selfwhere
Extractor: FnMut(&C) -> Cfg + Send + 'static,
Cfg: CfgHelper<O, C, Action>,
Name: Clone + Display + Send + Sync + 'static,
Apply a config helper to the builder.
For more information see CfgHelper
.
sourcepub fn singleton<T: 'static>(&mut self) -> bool
pub fn singleton<T: 'static>(&mut self) -> bool
Check if this is the first call with the given type.
Some helpers share common part. This common part makes sense to register just once, so this
can be used to check that. The first call with given type returns true
, any future ones
with the same type return false
.
The method has no direct effect on the future spirit constructed from the builder and works only as a note for future helpers that want to manipulate the builder.
A higher-level interface is the with_singleton
method.
Examples
use spirit::{Empty, Spirit};
let mut builder = Spirit::<Empty, Empty>::new();
struct X;
struct Y;
assert!(builder.singleton::<X>());
assert!(!builder.singleton::<X>());
assert!(builder.singleton::<Y>());
sourcepub fn with_singleton<T: Helper<O, C> + 'static>(self, singleton: T) -> Self
pub fn with_singleton<T: Helper<O, C> + 'static>(self, singleton: T) -> Self
Apply the first Helper
of the type.
This applies the passed helper, but only if a helper with the same hasn’t yet been applied
(or the singleton
called manually).
Note that different instances of the same type of a helper can act differently, but are
still considered the same type. This means the first instance wins. This is considered a
feature ‒ many other helpers need some environment to run in (like tokio
). The helpers
try to apply a default configuration, but the user can apply a specific configuration
first.
source§impl<O, C> Builder<O, C>where
C: DeserializeOwned + Send + Sync + 'static,
O: Debug + StructOpt + Sync + Send + 'static,
impl<O, C> Builder<O, C>where
C: DeserializeOwned + Send + Sync + 'static,
O: Debug + StructOpt + Sync + Send + 'static,
sourcepub fn before_body<B>(self, body: B) -> Selfwhere
B: FnOnce(&Arc<Spirit<O, C>>) -> Result<(), Error> + Send + 'static,
pub fn before_body<B>(self, body: B) -> Selfwhere
B: FnOnce(&Arc<Spirit<O, C>>) -> Result<(), Error> + Send + 'static,
Add a closure run before the main body.
The run
will first execute all closures submitted through this method
before running the real body. They are run in the order of submissions.
The purpose of this is mostly integration with helpers ‒ they often need some last minute preparation.
In case of using only build, the bodies are composed into one object and returned as part of the result (the inner body).
Errors
If any of the before-bodies in the chain return an error, the processing ends there and the error is returned right away.
Examples
Spirit::<Empty, Empty>::new()
.before_body(|_spirit| {
println!("Run first");
Ok(())
}).run(|_spirit| {
println!("Run second");
Ok(())
});
sourcepub fn before_config<F>(self, cback: F) -> Selfwhere
F: FnOnce(&O) -> Result<(), Error> + Send + 'static,
pub fn before_config<F>(self, cback: F) -> Selfwhere
F: FnOnce(&O) -> Result<(), Error> + Send + 'static,
A callback that is run after the building started and the command line is parsed, but even before the first configuration is loaded.
If the callback returns an error, the building is aborted.
sourcepub fn body_wrapper<W>(self, wrapper: W) -> Selfwhere
W: FnOnce(&Arc<Spirit<O, C>>, InnerBody) -> Result<(), Error> + Send + 'static,
pub fn body_wrapper<W>(self, wrapper: W) -> Selfwhere
W: FnOnce(&Arc<Spirit<O, C>>, InnerBody) -> Result<(), Error> + Send + 'static,
Wrap the body run by the run
into this closure.
The inner body is passed as an object with a run
method, not as a closure, due to a limitation around boxed FnOnce
.
It is expected the wrapper executes the inner body as part of itself and propagates any returned error.
In case of multiple wrappers, the ones submitted later on are placed inside the sooner ones ‒ the first one is the outermost.
In case of using only build
, all the wrappers composed together are
returned as part of the result.
Examples
Spirit::<Empty, Empty>::new()
.body_wrapper(|_spirit, inner| {
println!("Run first");
inner.run()?;
println!("Run third");
Ok(())
}).run(|_spirit| {
println!("Run second");
Ok(())
});
sourcepub fn config_default_paths<P, I>(self, paths: I) -> Selfwhere
I: IntoIterator<Item = P>,
P: Into<PathBuf>,
pub fn config_default_paths<P, I>(self, paths: I) -> Selfwhere
I: IntoIterator<Item = P>,
P: Into<PathBuf>,
Sets the configuration paths in case the user doesn’t provide any.
This replaces any previously set default paths. If none are specified and the user doesn’t specify any either, no config is loaded (but it is not an error, simply the defaults will be used, if available).
sourcepub fn config_defaults<D: Into<String>>(self, config: D) -> Self
pub fn config_defaults<D: Into<String>>(self, config: D) -> Self
Specifies the default configuration.
This „loads“ the lowest layer of the configuration from the passed string. The expected format is TOML.
sourcepub fn config_env<E: Into<String>>(self, env: E) -> Self
pub fn config_env<E: Into<String>>(self, env: E) -> Self
Enables loading configuration from environment variables.
If this is used, after loading the normal configuration files, the environment of the process is examined. Variables with the provided prefix are merged into the configuration.
Examples
extern crate failure;
#[macro_use]
extern crate serde_derive;
extern crate spirit;
use failure::Error;
use spirit::{Empty, Spirit};
#[derive(Default, Deserialize)]
struct Cfg {
message: String,
}
const DEFAULT_CFG: &str = r#"
message = "Hello"
"#;
fn main() {
Spirit::<Empty, Cfg>::new()
.config_defaults(DEFAULT_CFG)
.config_env("HELLO")
.run(|spirit| -> Result<(), Error> {
println!("{}", spirit.config().message);
Ok(())
});
}
If run like this, it’ll print Hi
. The environment takes precedence ‒ even if there was
configuration file and it set the message
, the Hi
here would win.
HELLO_MESSAGE="Hi" ./hello
sourcepub fn config_ext<E: Into<OsString>>(self, ext: E) -> Self
pub fn config_ext<E: Into<OsString>>(self, ext: E) -> Self
Configures a config dir filter for a single extension.
Sets the config directory filter (see config_filter
) to one
matching this single extension.
sourcepub fn config_exts<I, E>(self, exts: I) -> Selfwhere
I: IntoIterator<Item = E>,
E: Into<OsString>,
pub fn config_exts<I, E>(self, exts: I) -> Selfwhere
I: IntoIterator<Item = E>,
E: Into<OsString>,
Configures a config dir filter for multiple extensions.
Sets the config directory filter (see config_filter
) to one
matching files with any of the provided extensions.
sourcepub fn config_filter<F: FnMut(&Path) -> bool + Send + 'static>(
self,
filter: F
) -> Self
pub fn config_filter<F: FnMut(&Path) -> bool + Send + 'static>(
self,
filter: F
) -> Self
Sets a configuration dir filter.
If the user passes a directory path instead of a file path, the directory is traversed (every time the configuration is reloaded, so if files are added or removed, it is reflected) and files passing this filter are merged into the configuration, in the lexicographical order of their file names.
There’s ever only one filter and the default one passes no files (therefore, directories are ignored by default).
The filter has no effect on files, only on loading directories. Only files directly in the directory are loaded ‒ subdirectories are not traversed.
For more convenient ways to set the filter, see config_ext
and
config_exts
.
sourcepub fn config_validator<R, F>(self, f: F) -> Selfwhere
F: FnMut(&Arc<C>, &mut C, &O) -> R + Send + 'static,
R: Into<ValidationResults>,
pub fn config_validator<R, F>(self, f: F) -> Selfwhere
F: FnMut(&Arc<C>, &mut C, &O) -> R + Send + 'static,
R: Into<ValidationResults>,
Adds another config validator to the chain.
The validators are there to check, possibly modify and possibly refuse a newly loaded configuration.
The callback is passed three parameters:
- The old configuration.
- The new configuration (possible to modify).
- The command line options.
They are run in order of being set. Each one can pass arbitrary number of results, where a result can carry a message (of different severities) that ends up in logs and actions to be taken if the validation succeeds or fails.
If none of the validator returns an error-level message, the validation passes. After it is determined if the configuration passed, either all the success or failure actions are run.
The actions
Sometimes, the only way to validate a piece of config is to try it out ‒ like when you want to open a listening socket, you don’t know if the port is free. But you can’t activate and apply just yet, because something further down the configuration might still fail.
So, you open the socket (or create an error result) and store it into the success action to apply it later on. If something fails, the action is dropped and the socket closed.
The failure action lets you roll back (if it isn’t done by simply dropping the thing).
If the validation and application steps can be separated (you can check if something is OK
by just „looking“ at it ‒ like with a regular expression and using it can’t fail), you
don’t have to use them, just use verification and on_config
separately.
TODO: Threads, deadlocks
Examples
TODO
sourcepub fn on_config<F: FnMut(&O, &Arc<C>) + Send + 'static>(self, hook: F) -> Self
pub fn on_config<F: FnMut(&O, &Arc<C>) + Send + 'static>(self, hook: F) -> Self
Adds a callback for notification about new configurations.
The callback is called once a new configuration is loaded and successfully validated.
TODO: Threads, deadlocks
sourcepub fn on_signal<F: FnMut() + Send + 'static>(
self,
signal: c_int,
hook: F
) -> Self
pub fn on_signal<F: FnMut() + Send + 'static>(
self,
signal: c_int,
hook: F
) -> Self
Adds a callback for reacting to a signal.
The Spirit
reacts to some signals itself, in its own service
thread. However, it is also possible to hook into any signals directly (well, any except
the ones that are off limits).
These are not run inside the real signal handler, but are delayed and run in the service thread. Therefore, restrictions about async-signal-safety don’t apply to the hook.
TODO: Threads, deadlocks
sourcepub fn on_terminate<F: FnMut() + Send + 'static>(self, hook: F) -> Self
pub fn on_terminate<F: FnMut() + Send + 'static>(self, hook: F) -> Self
Adds a callback executed once the Spirit
decides to terminate.
This is called either when someone calls terminate
or when a termination signal is received.
Note that there are ways the application may terminate without calling these hooks ‒ for example terminating the main thread, or aborting.
TODO: Threads, deadlocks
sourcepub fn build(
self,
background_thread: bool
) -> Result<(Arc<Spirit<O, C>>, InnerBody, WrapBody), Error>
pub fn build(
self,
background_thread: bool
) -> Result<(Arc<Spirit<O, C>>, InnerBody, WrapBody), Error>
Finish building the Spirit.
This transitions from the configuration phase of Spirit to actually creating it. This loads
the configuration and executes it for the first time. It launches the background thread for
listening to signals and reloading configuration if the background_thread
parameter is
set to true.
This starts listening for signals, loads the configuration for the first time and starts the background thread.
This version returns the spirit (or error) and error handling is up to the caller. If you
want spirit to take care of nice error logging (even for your application’s top level
errors), use run
.
Result
On success, this returns three things:
- The
spirit
handle, allowing to manipulate it (shutdown, read configuration, …) - The before-body hooks (see
before_body
. - The body wrappers (
body_wrapper
).
The two latter ones are often set by helpers, so you should not ignore them.
Warning
If asked to go to background (when you’re using the
spirit-daemonize
crate, this uses fork
.
Therefore, start any threads after you call build
(or from within run
),
or you’ll lose them ‒ only the thread doing fork is preserved across it.
sourcepub fn run<B: FnOnce(&Arc<Spirit<O, C>>) -> Result<(), Error> + Send + 'static>(
self,
body: B
)
pub fn run<B: FnOnce(&Arc<Spirit<O, C>>) -> Result<(), Error> + Send + 'static>(
self,
body: B
)
Build the spirit and run the application, handling all relevant errors.
In case an error happens (either when creating the Spirit, or returned by the callback), the errors are logged (either to the place where logs are sent to in configuration, or to stderr if the error happens before logging is initialized ‒ for example if configuration can’t be read). The application then terminates with failure exit code.
It first wraps all the calls in the provided wrappers
(body_wrapper
) and runs the before body hooks
(before_body
) before starting the real body provided as parameter.
These are usually provided by helpers.
use std::thread;
use std::time::Duration;
use spirit::{Empty, Spirit};
Spirit::<Empty, Empty>::new()
.run(|spirit| {
while !spirit.is_terminated() {
// Some reasonable work here
thread::sleep(Duration::from_millis(100));
}
Ok(())
});