Skip to main content

runlatch_core/
registry.rs

1//! The provider registry: the single entry point callers use to work with every
2//! available autostart backend at once.
3
4use crate::model::AutostartEntry;
5use crate::provider::AutostartProvider;
6use crate::providers::{SystemdProvider, XdgAutostartProvider};
7
8/// An error raised by a single provider during aggregation, tagged with its source.
9///
10/// Aggregation never fails as a whole: a provider that errors contributes a
11/// `ProviderError` here while every other provider's good results are still
12/// returned.
13#[derive(Debug)]
14pub struct ProviderError {
15    /// The id of the provider that failed.
16    pub source: &'static str,
17    /// The underlying error.
18    pub error: anyhow::Error,
19}
20
21/// The result of aggregating entries across all available providers.
22#[derive(Debug, Default)]
23pub struct AggregateResult {
24    /// All entries successfully gathered, across providers.
25    pub entries: Vec<AutostartEntry>,
26    /// Per-provider failures, if any. Non-empty does not mean `entries` is empty.
27    pub errors: Vec<ProviderError>,
28}
29
30/// A collection of autostart providers.
31///
32/// Construct with [`Registry::with_defaults`] for the built-in providers, or
33/// [`Registry::new`] to inject a caller-supplied set (the extension point for
34/// external crates that ship their own providers).
35///
36/// ```no_run
37/// use runlatch_core::Registry;
38///
39/// # async fn run() {
40/// let registry = Registry::with_defaults();
41/// let result = registry.all_entries().await;
42/// for entry in &result.entries {
43///     println!("{}:{} (enabled={})", entry.source, entry.id, entry.enabled);
44/// }
45/// # }
46/// ```
47pub struct Registry {
48    providers: Vec<Box<dyn AutostartProvider>>,
49}
50
51impl Registry {
52    /// Build a registry from an explicit list of providers.
53    pub fn new(providers: Vec<Box<dyn AutostartProvider>>) -> Self {
54        Self { providers }
55    }
56
57    /// Build a registry with the crate's built-in providers: XDG autostart (user
58    /// and system) and systemd units (user and system).
59    pub fn with_defaults() -> Self {
60        Self::new(vec![
61            Box::new(XdgAutostartProvider::user()),
62            Box::new(XdgAutostartProvider::system()),
63            Box::new(SystemdProvider::user()),
64            Box::new(SystemdProvider::system()),
65        ])
66    }
67
68    /// All registered providers, regardless of availability.
69    pub fn providers(&self) -> &[Box<dyn AutostartProvider>] {
70        &self.providers
71    }
72
73    /// The providers whose [`is_available`](AutostartProvider::is_available) probe
74    /// currently returns `true`.
75    pub async fn available(&self) -> Vec<&dyn AutostartProvider> {
76        let mut out = Vec::new();
77        for provider in &self.providers {
78            if provider.is_available().await {
79                out.push(provider.as_ref());
80            }
81        }
82        out
83    }
84
85    /// Look up a registered provider by its id.
86    pub fn find_provider(&self, id: &str) -> Option<&dyn AutostartProvider> {
87        self.providers
88            .iter()
89            .map(|p| p.as_ref())
90            .find(|p| p.id() == id)
91    }
92
93    /// Aggregate entries across every available provider.
94    ///
95    /// A failure from one provider is collected into
96    /// [`AggregateResult::errors`] and never aborts aggregation of the others.
97    pub async fn all_entries(&self) -> AggregateResult {
98        let mut result = AggregateResult::default();
99        for provider in self.available().await {
100            match provider.entries().await {
101                Ok(entries) => result.entries.extend(entries),
102                Err(error) => result.errors.push(ProviderError {
103                    source: provider.id(),
104                    error,
105                }),
106            }
107        }
108        result
109    }
110}
111
112impl Default for Registry {
113    fn default() -> Self {
114        Self::with_defaults()
115    }
116}