companion_service/
lib.rs

1//! Run services along with your executable. This is useful, for example, to
2//! run and stop databases and message brokers automatically with `cargo test`.
3//!
4//! Automatic startup and shutdown of services relies on the [`ctor`](::ctor)
5//! crate. Service registration is done with the [`linkme`] crate and the
6//! [`SERVICES`](static@SERVICES) static. And each service is defined by an
7//! object that implements the [`Service`] trait:
8//!
9//! ```rust
10//! use linkme::distributed_slice;
11//! use companion_service::{Service, SERVICES};
12//!
13//! struct Dummy;
14//!
15//! impl Service for Dummy {
16//!   fn name(&self) -> &str {
17//!     "dummy"
18//!   }
19//!
20//!   fn start(&self) {
21//!     print!("start!");
22//!   }
23//!
24//!   fn stop(&self) {
25//!     print!("stop!");
26//!   }
27//! }
28//!
29//! #[distributed_slice(SERVICES)]
30//! static DUMMY: &(dyn Service + Sync) = &Dummy;
31//! ```
32
33use ctor::{ctor, dtor};
34use linkme::distributed_slice;
35
36/// The distributed slice handled by [`linkme`].
37#[distributed_slice]
38pub static SERVICES: [&'static (dyn Service + Sync)] = [..];
39
40/// Trait for all services handled by this crate.
41///
42/// The intent is to provide a very generic interface for implementors that
43/// handle the runtime of external programs (e.g. a database server).
44pub trait Service {
45    /// Get the name of this service. This name can be used to control the
46    /// service through the toplevel functions: [`start`], [`stop`], and
47    /// [`restart`].
48    fn name(&self) -> &str;
49
50    /// Starts the service. This is called once before `main`, and also as a
51    /// result of the toplevel [`start`] function being called with the name of
52    /// this service.
53    fn start(&self);
54
55    /// Stops the service. This is called once after `main`, and also as a
56    /// result of the toplevel [`stop`] function being called with the name of
57    /// this service.
58    fn stop(&self);
59
60    /// Restarts the service. This is called as a result of the toplevel
61    /// [`restart`] function being called with the name of this service.
62    fn restart(&self) {
63        self.stop();
64        self.start();
65    }
66}
67
68/// Starts all services with the given name.
69pub fn start(name: &str) {
70    for service in SERVICES {
71        if service.name() == name {
72            service.start();
73        }
74    }
75}
76
77/// Stops all services with the given name.
78pub fn stop(name: &str) {
79    for service in SERVICES {
80        if service.name() == name {
81            service.stop();
82        }
83    }
84}
85
86/// Restarts all services with the given name.
87pub fn restart(name: &str) {
88    for service in SERVICES {
89        if service.name() == name {
90            service.restart();
91        }
92    }
93}
94
95#[ctor]
96fn init() {
97    for service in SERVICES {
98        service.start();
99    }
100}
101
102#[dtor]
103fn deinit() {
104    for service in SERVICES {
105        service.stop();
106    }
107}