rightkit_daemon/
service.rs1use std::sync::Arc;
20
21use crate::contract::{Handler, MethodTable, TableError};
22use crate::server::{host_with_options, HostError, HostOptions, Hosted, ServiceConfig};
23use crate::suite::Suite;
24
25#[cfg(feature = "remote")]
26use crate::remote::{Clock, Daemon, DaemonConfig, DeviceCalls, OAuthCallbackHandler, SystemClock};
27
28#[derive(Debug)]
30pub enum BuildError {
31 Missing(&'static str),
33 NoTransport,
35 Table(TableError),
37 Host(Box<HostError>),
39 Io(std::io::Error),
41}
42
43impl std::fmt::Display for BuildError {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self {
46 BuildError::Missing(what) => write!(f, "service builder: {what} not set"),
47 BuildError::NoTransport => f.write_str("service builder: no transport configured"),
48 BuildError::Table(e) => write!(f, "service builder: {e}"),
49 BuildError::Host(e) => write!(f, "service builder: local host: {e}"),
50 BuildError::Io(e) => write!(f, "service builder: remote state: {e}"),
51 }
52 }
53}
54
55impl std::error::Error for BuildError {}
56
57struct LocalSpec {
58 suite: Suite,
59 cfg: ServiceConfig,
60 opts: HostOptions,
61}
62
63#[cfg(feature = "remote")]
64struct RemoteSpec {
65 cfg: DaemonConfig,
66 clock: Arc<dyn Clock>,
67 oauth: Option<Arc<dyn OAuthCallbackHandler>>,
68}
69
70pub struct Service {
72 table: MethodTable,
73 hosted: Option<Hosted>,
74 #[cfg(feature = "remote")]
75 daemon: Option<Arc<Daemon>>,
76}
77
78impl Service {
79 pub fn builder() -> ServiceBuilder {
80 ServiceBuilder::default()
81 }
82
83 pub fn methods(&self) -> &MethodTable {
84 &self.table
85 }
86
87 pub fn local(&self) -> Option<&Hosted> {
89 self.hosted.as_ref()
90 }
91
92 #[cfg(feature = "remote")]
95 pub fn daemon(&self) -> Option<&Arc<Daemon>> {
96 self.daemon.as_ref()
97 }
98
99 pub fn shutdown(&self) {
101 if let Some(h) = &self.hosted {
102 h.shutdown();
103 }
104 }
105}
106
107#[derive(Default)]
108pub struct ServiceBuilder {
109 table: Option<MethodTable>,
110 handler: Option<Arc<dyn Handler>>,
111 local: Option<LocalSpec>,
112 #[cfg(feature = "remote")]
113 remote: Option<RemoteSpec>,
114}
115
116impl ServiceBuilder {
117 pub fn methods(mut self, table: MethodTable) -> Self {
119 self.table = Some(table);
120 self
121 }
122
123 pub fn handler(mut self, handler: Arc<dyn Handler>) -> Self {
125 self.handler = Some(handler);
126 self
127 }
128
129 pub fn local(mut self, suite: &Suite, cfg: ServiceConfig, opts: HostOptions) -> Self {
132 self.local = Some(LocalSpec {
133 suite: suite.clone(),
134 cfg,
135 opts,
136 });
137 self
138 }
139
140 #[cfg(feature = "remote")]
142 pub fn remote(self, cfg: DaemonConfig) -> Self {
143 self.remote_with(cfg, Arc::new(SystemClock), None)
144 }
145
146 #[cfg(feature = "remote")]
148 pub fn remote_with(
149 mut self,
150 cfg: DaemonConfig,
151 clock: Arc<dyn Clock>,
152 oauth: Option<Arc<dyn OAuthCallbackHandler>>,
153 ) -> Self {
154 self.remote = Some(RemoteSpec { cfg, clock, oauth });
155 self
156 }
157
158 pub fn start(self) -> Result<Service, BuildError> {
161 let table = self.table.ok_or(BuildError::Missing("methods"))?;
162 let handler = self.handler.ok_or(BuildError::Missing("handler"))?;
163 #[cfg(feature = "remote")]
164 let has_remote = self.remote.is_some();
165 #[cfg(not(feature = "remote"))]
166 let has_remote = false;
167 if self.local.is_none() && !has_remote {
168 return Err(BuildError::NoTransport);
169 }
170
171 #[cfg(feature = "remote")]
172 let daemon = match self.remote {
173 Some(r) => {
174 let d = Daemon::open(r.cfg, r.clock, r.oauth)
175 .map_err(BuildError::Io)?
176 .with_calls(DeviceCalls::new(table.clone(), handler.clone()));
177 Some(Arc::new(d))
178 }
179 None => None,
180 };
181
182 let hosted = match self.local {
183 Some(l) => {
184 let cfg = l.cfg.with_methods(&table);
185 Some(
186 host_with_options(&l.suite, cfg, handler, l.opts)
187 .map_err(|e| BuildError::Host(Box::new(e)))?,
188 )
189 }
190 None => None,
191 };
192 Ok(Service {
193 table,
194 hosted,
195 #[cfg(feature = "remote")]
196 daemon,
197 })
198 }
199}