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}