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}