use std::collections::BTreeMap;
use std::fmt;
use std::sync::Arc;
use serde_json::Value;
use crate::contract::{validate_method_id, CallContext, Emit, Handler, MethodPolicy, MethodTable};
use crate::contract::{TableError, Transport};
use crate::error::ServiceError;
use crate::server::ServiceConfig;
use crate::suite::valid_service_name;
use crate::Allowlist;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum TransportProfile {
Local,
LocalAndRemote,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceIdentity {
service: String,
role: String,
rank: u8,
version: String,
}
fn plain_text(text: &str) -> bool {
!text.is_empty() && text.len() <= 64 && text.chars().all(|c| !c.is_control())
}
impl ServiceIdentity {
pub fn new(
service: &str,
role: &str,
rank: u8,
version: &str,
) -> Result<Self, RegistrationError> {
if !valid_service_name(service) {
return Err(RegistrationError::BadIdentity("service name"));
}
if !plain_text(role) {
return Err(RegistrationError::BadIdentity("role"));
}
if !plain_text(version) {
return Err(RegistrationError::BadIdentity("version"));
}
Ok(Self {
service: service.to_string(),
role: role.to_string(),
rank,
version: version.to_string(),
})
}
pub fn service(&self) -> &str {
&self.service
}
pub fn role(&self) -> &str {
&self.role
}
pub fn rank(&self) -> u8 {
self.rank
}
pub fn version(&self) -> &str {
&self.version
}
pub fn to_config(&self) -> ServiceConfig {
ServiceConfig::new(
&self.service,
&self.role,
self.rank,
&self.version,
Allowlist::new(),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MethodDescriptor {
id: String,
policy: MethodPolicy,
}
impl MethodDescriptor {
pub fn new(id: impl Into<String>, policy: MethodPolicy) -> Self {
Self {
id: id.into(),
policy,
}
}
pub fn local<I, S>(id: impl Into<String>, apps: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self::new(id, MethodPolicy::local(apps))
}
pub fn id(&self) -> &str {
&self.id
}
pub fn policy(&self) -> &MethodPolicy {
&self.policy
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum RegistrationError {
BadIdentity(&'static str),
Table(TableError),
Reserved(String),
Duplicate(String),
RemoteMethodInLocalProfile(String),
RemoteNotDeclared,
NoMethods,
}
impl fmt::Display for RegistrationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RegistrationError::BadIdentity(part) => {
write!(f, "service registration: the {part} is not acceptable")
}
RegistrationError::Table(e) => write!(f, "service registration: {e}"),
RegistrationError::Reserved(id) => {
write!(f, "service registration: {id:?} is answered by the host")
}
RegistrationError::Duplicate(id) => {
write!(f, "service registration: method {id:?} is registered twice")
}
RegistrationError::RemoteMethodInLocalProfile(id) => write!(
f,
"service registration: method {id:?} is reachable by remote devices but the profile is local"
),
RegistrationError::RemoteNotDeclared => f.write_str(
"service registration: a remote daemon was configured for a local-only registration",
),
RegistrationError::NoMethods => {
f.write_str("service registration: no method was registered")
}
}
}
}
impl std::error::Error for RegistrationError {}
impl From<TableError> for RegistrationError {
fn from(e: TableError) -> Self {
RegistrationError::Table(e)
}
}
type MethodFn =
Box<dyn Fn(&CallContext, &Value, &Emit) -> Result<Value, ServiceError> + Send + Sync>;
type Parts = (
ServiceIdentity,
TransportProfile,
MethodTable,
Arc<dyn Handler>,
);
pub struct Registration {
identity: ServiceIdentity,
transport: TransportProfile,
table: MethodTable,
handlers: BTreeMap<String, MethodFn>,
on_handoff: Option<Box<dyn Fn() + Send + Sync>>,
on_connection_closed: Option<Box<dyn Fn(u64) + Send + Sync>>,
}
impl Registration {
pub fn new(identity: ServiceIdentity) -> Self {
Self {
identity,
transport: TransportProfile::Local,
table: MethodTable::new(),
handlers: BTreeMap::new(),
on_handoff: None,
on_connection_closed: None,
}
}
pub fn transport(mut self, transport: TransportProfile) -> Self {
self.transport = transport;
self
}
pub fn method(
self,
descriptor: MethodDescriptor,
handler: impl Fn(&CallContext, &Value, &Emit) -> Result<Value, ServiceError>
+ Send
+ Sync
+ 'static,
) -> Result<Self, RegistrationError> {
self.method_boxed(descriptor, Box::new(handler))
}
fn method_boxed(
mut self,
descriptor: MethodDescriptor,
handler: MethodFn,
) -> Result<Self, RegistrationError> {
let MethodDescriptor { id, policy } = descriptor;
validate_method_id(&id)?;
if id == "ping" {
return Err(RegistrationError::Reserved(id));
}
if self.handlers.contains_key(&id) {
return Err(RegistrationError::Duplicate(id));
}
if self.transport == TransportProfile::Local && policy.transport() != Transport::Local {
return Err(RegistrationError::RemoteMethodInLocalProfile(id));
}
self.table = self.table.clone().try_method(id.clone(), policy)?;
self.handlers.insert(id, handler);
Ok(self)
}
pub fn on_handoff(mut self, hook: impl Fn() + Send + Sync + 'static) -> Self {
self.on_handoff = Some(Box::new(hook));
self
}
pub fn on_connection_closed(mut self, hook: impl Fn(u64) + Send + Sync + 'static) -> Self {
self.on_connection_closed = Some(Box::new(hook));
self
}
pub fn identity(&self) -> &ServiceIdentity {
&self.identity
}
pub fn profile(&self) -> TransportProfile {
self.transport
}
pub fn methods(&self) -> &MethodTable {
&self.table
}
pub fn into_parts(self) -> Result<Parts, RegistrationError> {
if self.handlers.is_empty() {
return Err(RegistrationError::NoMethods);
}
let handler: Arc<dyn Handler> = Arc::new(Registered {
handlers: self.handlers,
on_handoff: self.on_handoff,
on_connection_closed: self.on_connection_closed,
});
Ok((self.identity, self.transport, self.table, handler))
}
}
struct Registered {
handlers: BTreeMap<String, MethodFn>,
on_handoff: Option<Box<dyn Fn() + Send + Sync>>,
on_connection_closed: Option<Box<dyn Fn(u64) + Send + Sync>>,
}
impl Handler for Registered {
fn call(
&self,
cx: &CallContext,
method: &str,
params: &Value,
emit: &Emit,
) -> Result<Value, ServiceError> {
match self.handlers.get(method) {
Some(handler) => handler(cx, params, emit),
None => Err(ServiceError::new(
"unknown_method",
format!("{method} is not registered"),
)),
}
}
fn on_handoff(&self) {
if let Some(hook) = &self.on_handoff {
hook();
}
}
fn connection_closed(&self, conn: u64) {
if let Some(hook) = &self.on_connection_closed {
hook(conn);
}
}
}