1use std::sync::Arc;
20
21use crate::contract::{Handler, MethodTable, TableError};
22use crate::registration::{Registration, RegistrationError, TransportProfile};
23use crate::server::{host_with_options, HostError, HostOptions, Hosted, ServiceConfig};
24use crate::suite::Suite;
25
26#[cfg(feature = "remote")]
27use crate::remote::{Clock, Daemon, DaemonConfig, DeviceCalls, OAuthCallbackHandler, SystemClock};
28
29#[derive(Debug)]
31pub enum BuildError {
32 Missing(&'static str),
34 NoTransport,
36 Table(TableError),
38 Host(Box<HostError>),
40 Io(std::io::Error),
42 Registration(RegistrationError),
44}
45
46impl std::fmt::Display for BuildError {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 match self {
49 BuildError::Missing(what) => write!(f, "service builder: {what} not set"),
50 BuildError::NoTransport => f.write_str("service builder: no transport configured"),
51 BuildError::Table(e) => write!(f, "service builder: {e}"),
52 BuildError::Host(e) => write!(f, "service builder: local host: {e}"),
53 BuildError::Io(e) => write!(f, "service builder: remote state: {e}"),
54 BuildError::Registration(e) => write!(f, "service builder: {e}"),
55 }
56 }
57}
58
59impl std::error::Error for BuildError {}
60
61struct LocalSpec {
62 suite: Suite,
63 cfg: ServiceConfig,
64 opts: HostOptions,
65}
66
67#[cfg(feature = "remote")]
68struct RemoteSpec {
69 cfg: DaemonConfig,
70 clock: Arc<dyn Clock>,
71 oauth: Option<Arc<dyn OAuthCallbackHandler>>,
72}
73
74pub struct Service {
76 table: MethodTable,
77 hosted: Option<Hosted>,
78 #[cfg(feature = "remote")]
79 daemon: Option<Arc<Daemon>>,
80}
81
82impl Service {
83 pub fn builder() -> ServiceBuilder {
84 ServiceBuilder::default()
85 }
86
87 pub fn methods(&self) -> &MethodTable {
88 &self.table
89 }
90
91 pub fn local(&self) -> Option<&Hosted> {
93 self.hosted.as_ref()
94 }
95
96 #[cfg(feature = "remote")]
99 pub fn daemon(&self) -> Option<&Arc<Daemon>> {
100 self.daemon.as_ref()
101 }
102
103 pub fn shutdown(&self) {
105 if let Some(h) = &self.hosted {
106 h.shutdown();
107 }
108 }
109}
110
111#[derive(Default)]
112pub struct ServiceBuilder {
113 table: Option<MethodTable>,
114 handler: Option<Arc<dyn Handler>>,
115 local: Option<LocalSpec>,
116 profile: Option<TransportProfile>,
117 #[cfg(feature = "remote")]
118 remote: Option<RemoteSpec>,
119}
120
121impl ServiceBuilder {
122 pub fn register(
128 mut self,
129 suite: &Suite,
130 registration: Registration,
131 opts: HostOptions,
132 ) -> Result<Self, BuildError> {
133 let (identity, profile, table, handler) = registration
134 .into_parts()
135 .map_err(BuildError::Registration)?;
136 self.table = Some(table);
137 self.handler = Some(handler);
138 self.local = Some(LocalSpec {
139 suite: suite.clone(),
140 cfg: identity.to_config(),
141 opts,
142 });
143 self.profile = Some(profile);
144 Ok(self)
145 }
146
147 pub fn methods(mut self, table: MethodTable) -> Self {
149 self.table = Some(table);
150 self
151 }
152
153 pub fn handler(mut self, handler: Arc<dyn Handler>) -> Self {
155 self.handler = Some(handler);
156 self
157 }
158
159 pub fn local(mut self, suite: &Suite, cfg: ServiceConfig, opts: HostOptions) -> Self {
162 self.local = Some(LocalSpec {
163 suite: suite.clone(),
164 cfg,
165 opts,
166 });
167 self
168 }
169
170 #[cfg(feature = "remote")]
172 pub fn remote(self, cfg: DaemonConfig) -> Self {
173 self.remote_with(cfg, Arc::new(SystemClock), None)
174 }
175
176 #[cfg(feature = "remote")]
178 pub fn remote_with(
179 mut self,
180 cfg: DaemonConfig,
181 clock: Arc<dyn Clock>,
182 oauth: Option<Arc<dyn OAuthCallbackHandler>>,
183 ) -> Self {
184 self.remote = Some(RemoteSpec { cfg, clock, oauth });
185 self
186 }
187
188 pub fn start(self) -> Result<Service, BuildError> {
191 let table = self.table.ok_or(BuildError::Missing("methods"))?;
192 let handler = self.handler.ok_or(BuildError::Missing("handler"))?;
193 #[cfg(feature = "remote")]
194 let has_remote = self.remote.is_some();
195 #[cfg(not(feature = "remote"))]
196 let has_remote = false;
197 if has_remote && self.profile == Some(TransportProfile::Local) {
199 return Err(BuildError::Registration(
200 RegistrationError::RemoteNotDeclared,
201 ));
202 }
203 if self.local.is_none() && !has_remote {
204 return Err(BuildError::NoTransport);
205 }
206
207 #[cfg(feature = "remote")]
208 let daemon = match self.remote {
209 Some(r) => {
210 let d = Daemon::open(r.cfg, r.clock, r.oauth)
211 .map_err(BuildError::Io)?
212 .with_calls(DeviceCalls::new(table.clone(), handler.clone()));
213 Some(Arc::new(d))
214 }
215 None => None,
216 };
217
218 let hosted = match self.local {
219 Some(l) => {
220 let cfg = l.cfg.with_methods(&table);
221 Some(
222 host_with_options(&l.suite, cfg, handler, l.opts)
223 .map_err(|e| BuildError::Host(Box::new(e)))?,
224 )
225 }
226 None => None,
227 };
228 Ok(Service {
229 table,
230 hosted,
231 #[cfg(feature = "remote")]
232 daemon,
233 })
234 }
235}