Skip to main content

origin_app/
platform.rs

1use origin_accounts::AccountService;
2use origin_connector::ConnectorRegistry;
3use origin_domain::{AppError, Clock, Result};
4use origin_events::EventBus;
5use origin_http::HttpClient;
6use origin_jobs::Jobs;
7use origin_platform::{
8    ConfirmationService, GlobalShortcutService, NotificationService, Opener, ProcessRunner,
9    TrayService, WorkspaceFs, WorkspaceWatcher,
10};
11use origin_secrets::SecretStore;
12use origin_settings::Settings;
13use origin_storage::{Cache, Storage};
14use origin_sync::SyncEngine;
15use std::sync::Arc;
16
17/// The platform services every module may rely on.
18///
19/// Cloning is cheap and shares the same instances.
20///
21/// Optional fields are capabilities the product did not grant. They are `None` because
22/// the composition root left them out, not because they are switched off at runtime —
23/// a build that cannot reach the network is a build that cannot reach the network.
24#[derive(Debug, Clone)]
25pub struct Platform {
26    pub clock: Arc<dyn Clock>,
27    pub events: EventBus,
28    pub storage: Arc<dyn Storage>,
29    pub cache: Cache,
30    pub secrets: Arc<dyn SecretStore>,
31    pub settings: Settings,
32    pub notifications: Arc<dyn NotificationService>,
33    /// Human confirmation for mutating operations. The deny-all default keeps MCP
34    /// safe by construction; only products with a real prompt override it.
35    pub confirmation: Arc<dyn ConfirmationService>,
36    /// The system tray handle, present only when the product declared it.
37    pub tray: Option<Arc<dyn TrayService>>,
38    /// Background jobs: progress, cancellation, uniform lifecycle.
39    pub jobs: Jobs,
40    /// Decides when registered sync targets run.
41    pub sync: SyncEngine,
42    /// Connected accounts across all connectors.
43    pub accounts: AccountService,
44    /// The connectors this build was compiled with.
45    pub connectors: ConnectorRegistry,
46    /// Present only when the product declared the capability to open external URLs.
47    pub opener: Option<Arc<dyn Opener>>,
48    /// Present only when the product talks to external services.
49    pub http: Option<Arc<dyn HttpClient>>,
50    /// Present only when the product grants workspace filesystem access (B2).
51    pub workspace_fs: Option<Arc<dyn WorkspaceFs>>,
52    /// Present only when the product watches workspace filesystem changes (B3).
53    pub workspace_watcher: Option<Arc<dyn WorkspaceWatcher>>,
54    /// Present only when the product allows executing external processes (B1).
55    pub process_runner: Option<Arc<dyn ProcessRunner>>,
56    /// Present only when the product registers global shortcuts (B5).
57    pub global_shortcuts: Option<Arc<dyn GlobalShortcutService>>,
58}
59
60impl Platform {
61    /// The HTTP client, or a configuration error naming what is missing.
62    ///
63    /// Modules call this instead of unwrapping the field, so a product that forgot to
64    /// wire a client gets an actionable message rather than a panic.
65    pub fn http(&self) -> Result<Arc<dyn HttpClient>> {
66        self.http.clone().ok_or_else(|| {
67            AppError::configuration(
68                "this application has no http client — add `.http_client(...)` to its \
69                 composition root",
70            )
71        })
72    }
73
74    /// The URL opener, or a permission error.
75    pub fn opener(&self) -> Result<Arc<dyn Opener>> {
76        self.opener.clone().ok_or_else(|| {
77            AppError::Permission("this application cannot open external urls".to_owned())
78        })
79    }
80
81    /// The workspace filesystem, or a configuration error naming what is missing.
82    pub fn workspace_fs(&self) -> Result<Arc<dyn WorkspaceFs>> {
83        self.workspace_fs.clone().ok_or_else(|| {
84            AppError::configuration(
85                "this application has no workspace filesystem — add `.workspace_fs(...)` to its \
86                 composition root",
87            )
88        })
89    }
90
91    /// The workspace watcher, or a configuration error naming what is missing.
92    pub fn workspace_watcher(&self) -> Result<Arc<dyn WorkspaceWatcher>> {
93        self.workspace_watcher.clone().ok_or_else(|| {
94            AppError::configuration(
95                "this application has no workspace watcher — add `.workspace_watcher(...)` to its \
96                 composition root",
97            )
98        })
99    }
100
101    /// The process runner, or a configuration error naming what is missing.
102    pub fn process_runner(&self) -> Result<Arc<dyn ProcessRunner>> {
103        self.process_runner.clone().ok_or_else(|| {
104            AppError::configuration(
105                "this application has no process runner — add `.process_runner(...)` to its \
106                 composition root",
107            )
108        })
109    }
110
111    /// The global shortcut service, or a configuration error naming what is missing.
112    pub fn global_shortcuts(&self) -> Result<Arc<dyn GlobalShortcutService>> {
113        self.global_shortcuts.clone().ok_or_else(|| {
114            AppError::configuration(
115                "this application has no global shortcut service — add `.global_shortcuts(...)` to its \
116                 composition root",
117            )
118        })
119    }
120}