Skip to main content

majordomus_cli/capability/
module.rs

1//! Modules compose capabilities; the application composes modules. A module is one Rust
2//! module with a `module()` function built by [`module!`](crate::module), owning its id,
3//! its metadata and the executables it ships; [`compose_modules!`](crate::compose_modules)
4//! names the modules the application is made of, and that list of modules is the only
5//! composition a person writes at the root. Adding a capability to an existing module
6//! touches that module alone.
7//!
8//! The macros build plain values. Nothing is registered behind the caller's back: the
9//! result is handed to the registry builder explicitly in `app.rs`.
10
11use super::handler::Executable;
12use super::model::{ModuleId, Stability};
13
14/// One module: its identity, what it is for, and the executables it composes. The module
15/// stamps its id on every capability it carries, and the registry refuses a capability
16/// whose id namespace is not its module.
17pub struct ModuleDescriptor {
18    /// The identity, `[a-z][a-z0-9_-]*`; the namespace of every capability in it.
19    pub id: ModuleId,
20    /// The short name.
21    pub title: String,
22    /// One paragraph.
23    pub description: String,
24    /// Where the module stands, as a whole.
25    pub stability: Stability,
26    /// The executables, each already stamped with the module id.
27    pub capabilities: Vec<Executable>,
28}
29
30impl ModuleDescriptor {
31    /// A module over its executables; stamps the module id on each of them.
32    pub fn new(
33        id: &str,
34        title: &str,
35        description: &str,
36        stability: Stability,
37        mut capabilities: Vec<Executable>,
38    ) -> Self {
39        let id = ModuleId::unchecked(id);
40        for e in &mut capabilities {
41            e.capability.module = id.clone();
42        }
43        ModuleDescriptor {
44            id,
45            title: title.into(),
46            description: description.into(),
47            stability,
48            capabilities,
49        }
50    }
51}
52
53impl std::fmt::Debug for ModuleDescriptor {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        f.debug_struct("ModuleDescriptor")
56            .field("id", &self.id)
57            .field("capabilities", &self.capabilities.len())
58            .finish()
59    }
60}
61
62/// Build a [`ModuleDescriptor`]: the module's identity and metadata once, and the
63/// executables it composes, each a `capability!` value.
64///
65/// ```
66/// use majordomus_cli::{capability, module};
67/// use majordomus_cli::capability::{BenchmarkCases, CaseContext, Context, CapabilityError, Exposure, NamedCase, Stability};
68/// #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
69/// struct In {}
70/// impl BenchmarkCases for In {
71///     fn benchmark_cases(_: &CaseContext<'_>) -> Vec<NamedCase<Self>> { vec![NamedCase::new("default", In {})] }
72/// }
73/// #[derive(serde::Serialize, schemars::JsonSchema)]
74/// struct Out { ok: bool }
75/// fn ping(_: &Context, _: In) -> Result<Out, CapabilityError> { Ok(Out { ok: true }) }
76/// let m = module! {
77///     id: "demo", title: "Demo", description: "A module of one.", stability: Stability::Experimental,
78///     capabilities: [
79///         capability! { id: "demo.ping", title: "Ping", description: "Answers.", input: In, output: Out,
80///                       stability: Stability::Experimental, exposure: Exposure::default(), tags: [], handler: ping },
81///     ],
82/// };
83/// assert_eq!(m.capabilities[0].capability.module.as_str(), "demo");
84/// ```
85#[macro_export]
86macro_rules! module {
87    (
88        id: $id:expr,
89        title: $title:expr,
90        description: $description:expr,
91        stability: $stability:expr,
92        capabilities: [ $($cap:expr),* $(,)? ] $(,)?
93    ) => {
94        $crate::capability::ModuleDescriptor::new($id, $title, $description, $stability, vec![$($cap),*])
95    };
96}
97
98/// Compose the application from its modules: each name is a Rust module with a
99/// `module()` function. This list is the one composition a person maintains at the root;
100/// a capability added inside a module needs no edit here.
101///
102/// ```
103/// use majordomus_cli::compose_modules;
104/// use majordomus_cli::capability::builtin;
105/// let modules = compose_modules![builtin::repository, builtin::objects];
106/// assert_eq!(modules.len(), 2);
107/// assert_eq!(modules[0].id.as_str(), "repository");
108/// ```
109#[macro_export]
110macro_rules! compose_modules {
111    ( $( $($segment:ident)::+ ),* $(,)? ) => {
112        vec![$( $($segment)::+::module() ),*]
113    };
114}