use std::sync::Arc;
use crate::contract::{Handler, MethodTable, TableError};
use crate::server::{host_with_options, HostError, HostOptions, Hosted, ServiceConfig};
use crate::suite::Suite;
#[cfg(feature = "remote")]
use crate::remote::{Clock, Daemon, DaemonConfig, DeviceCalls, OAuthCallbackHandler, SystemClock};
#[derive(Debug)]
pub enum BuildError {
Missing(&'static str),
NoTransport,
Table(TableError),
Host(Box<HostError>),
Io(std::io::Error),
}
impl std::fmt::Display for BuildError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BuildError::Missing(what) => write!(f, "service builder: {what} not set"),
BuildError::NoTransport => f.write_str("service builder: no transport configured"),
BuildError::Table(e) => write!(f, "service builder: {e}"),
BuildError::Host(e) => write!(f, "service builder: local host: {e}"),
BuildError::Io(e) => write!(f, "service builder: remote state: {e}"),
}
}
}
impl std::error::Error for BuildError {}
struct LocalSpec {
suite: Suite,
cfg: ServiceConfig,
opts: HostOptions,
}
#[cfg(feature = "remote")]
struct RemoteSpec {
cfg: DaemonConfig,
clock: Arc<dyn Clock>,
oauth: Option<Arc<dyn OAuthCallbackHandler>>,
}
pub struct Service {
table: MethodTable,
hosted: Option<Hosted>,
#[cfg(feature = "remote")]
daemon: Option<Arc<Daemon>>,
}
impl Service {
pub fn builder() -> ServiceBuilder {
ServiceBuilder::default()
}
pub fn methods(&self) -> &MethodTable {
&self.table
}
pub fn local(&self) -> Option<&Hosted> {
self.hosted.as_ref()
}
#[cfg(feature = "remote")]
pub fn daemon(&self) -> Option<&Arc<Daemon>> {
self.daemon.as_ref()
}
pub fn shutdown(&self) {
if let Some(h) = &self.hosted {
h.shutdown();
}
}
}
#[derive(Default)]
pub struct ServiceBuilder {
table: Option<MethodTable>,
handler: Option<Arc<dyn Handler>>,
local: Option<LocalSpec>,
#[cfg(feature = "remote")]
remote: Option<RemoteSpec>,
}
impl ServiceBuilder {
pub fn methods(mut self, table: MethodTable) -> Self {
self.table = Some(table);
self
}
pub fn handler(mut self, handler: Arc<dyn Handler>) -> Self {
self.handler = Some(handler);
self
}
pub fn local(mut self, suite: &Suite, cfg: ServiceConfig, opts: HostOptions) -> Self {
self.local = Some(LocalSpec {
suite: suite.clone(),
cfg,
opts,
});
self
}
#[cfg(feature = "remote")]
pub fn remote(self, cfg: DaemonConfig) -> Self {
self.remote_with(cfg, Arc::new(SystemClock), None)
}
#[cfg(feature = "remote")]
pub fn remote_with(
mut self,
cfg: DaemonConfig,
clock: Arc<dyn Clock>,
oauth: Option<Arc<dyn OAuthCallbackHandler>>,
) -> Self {
self.remote = Some(RemoteSpec { cfg, clock, oauth });
self
}
pub fn start(self) -> Result<Service, BuildError> {
let table = self.table.ok_or(BuildError::Missing("methods"))?;
let handler = self.handler.ok_or(BuildError::Missing("handler"))?;
#[cfg(feature = "remote")]
let has_remote = self.remote.is_some();
#[cfg(not(feature = "remote"))]
let has_remote = false;
if self.local.is_none() && !has_remote {
return Err(BuildError::NoTransport);
}
#[cfg(feature = "remote")]
let daemon = match self.remote {
Some(r) => {
let d = Daemon::open(r.cfg, r.clock, r.oauth)
.map_err(BuildError::Io)?
.with_calls(DeviceCalls::new(table.clone(), handler.clone()));
Some(Arc::new(d))
}
None => None,
};
let hosted = match self.local {
Some(l) => {
let cfg = l.cfg.with_methods(&table);
Some(
host_with_options(&l.suite, cfg, handler, l.opts)
.map_err(|e| BuildError::Host(Box::new(e)))?,
)
}
None => None,
};
Ok(Service {
table,
hosted,
#[cfg(feature = "remote")]
daemon,
})
}
}