mosaik 0.4.2

A Rust runtime for building self-organizing, leaderless distributed systems.
Documentation
use {
	super::{
		super::{
			FunctionId,
			Functions,
			derived_function_id,
			effective_function_id,
			status::HandlerConditions,
		},
		Caller,
		worker::CallerWorker,
	},
	crate::{discovery::PeerInfo, primitives::Datum, tickets::TicketValidator},
	core::{marker::PhantomData, time::Duration},
	std::sync::Arc,
};

/// Configuration options for a function caller.
pub struct CallerConfig {
	/// The effective wire id of the function this caller invokes (base id
	/// with all ticket validator signatures folded in).
	pub function_id: FunctionId,

	/// Predicate determining whether a handler peer is eligible to serve
	/// calls from this caller.
	pub require: Box<dyn Fn(&PeerInfo) -> bool + Send + Sync>,

	/// Ticket validators that handler peers must satisfy (enforced by this
	/// caller). Their signatures fold into the effective wire id.
	pub handler_auth: Vec<Arc<dyn TicketValidator>>,

	/// Ticket validators that callers must satisfy (enforced by handlers).
	/// Not enforced locally — declared only so their signatures fold into
	/// the effective wire id, keeping it in sync with handlers.
	pub caller_auth: Vec<Arc<dyn TicketValidator>>,

	/// The default end-to-end timeout for calls made through this caller.
	pub call_timeout: Duration,

	/// A function that specifies conditions under which the caller is
	/// considered online and ready to invoke the function. By default the
	/// caller is online when at least one eligible handler is known.
	pub online_when:
		Box<dyn Fn(HandlerConditions) -> HandlerConditions + Send + Sync>,
}

/// Configurable builder for assembling a new caller instance for a
/// specific request type `Req`, response type `Res` and application error
/// type `E`.
pub struct Builder<'f, Req: Datum, Res: Datum, E: Datum = ()> {
	function_id: Option<FunctionId>,
	require: Box<dyn Fn(&PeerInfo) -> bool + Send + Sync>,
	handler_auth: Vec<Arc<dyn TicketValidator>>,
	caller_auth: Vec<Arc<dyn TicketValidator>>,
	call_timeout: Duration,
	online_when:
		Box<dyn Fn(HandlerConditions) -> HandlerConditions + Send + Sync>,
	functions: &'f Functions,
	_marker: PhantomData<fn(&Req, &Res, &E)>,
}

/// Public API
impl<Req: Datum, Res: Datum, E: Datum> Builder<'_, Req, Res, E> {
	/// Sets the base function id this caller invokes.
	///
	/// If not set, the base id is derived from the request/response type
	/// names, matching the handler-side default for closure registrations.
	/// The signatures of any configured ticket validators are folded on top
	/// of the base id to produce the effective wire id.
	#[must_use]
	pub fn with_function_id(
		mut self,
		function_id: impl Into<FunctionId>,
	) -> Self {
		self.function_id = Some(function_id.into());
		self
	}

	/// Adds a handler eligibility requirement.
	///
	/// The predicate receives a [`PeerInfo`] combining the handler's
	/// self-reported [`PeerEntry`](crate::discovery::PeerEntry) with
	/// locally-observed metrics like RTT. Only handler peers satisfying
	/// every requirement are considered as call candidates.
	///
	/// When called multiple times, all predicates must pass — AND
	/// composition.
	///
	/// The default requirement accepts all handlers.
	#[must_use]
	pub fn require<F>(mut self, pred: F) -> Self
	where
		F: Fn(&PeerInfo) -> bool + Send + Sync + 'static,
	{
		let prev = self.require;
		self.require = Box::new(move |peer| prev(peer) && pred(peer));
		self
	}

	/// Adds a ticket validator that authorizes handlers of this function.
	///
	/// This is counterparty sugar for
	/// [`require_handler_ticket`](Self::require_handler_ticket).
	#[must_use]
	pub fn require_ticket(self, validator: impl TicketValidator) -> Self {
		self.require_handler_ticket(validator)
	}

	/// Adds a ticket validator that handlers of this function must satisfy.
	///
	/// Only handler peers presenting valid tickets that pass all configured
	/// handler validators are considered as call candidates, and tickets
	/// are re-validated before every call attempt. The validator's
	/// signature is folded into the effective wire id, so functions with
	/// different authorization rules have different identities on the
	/// network. Can be called multiple times to require multiple types of
	/// tickets.
	#[must_use]
	pub fn require_handler_ticket(
		mut self,
		validator: impl TicketValidator,
	) -> Self {
		self.handler_auth.push(Arc::new(validator));
		self
	}

	/// Declares a ticket validator that callers of this function must
	/// satisfy.
	///
	/// Caller tickets are enforced by handlers, not by this caller — the
	/// declaration only folds the validator's signature into the effective
	/// wire id so it matches the id derived by handlers enforcing the same
	/// contract. Can be called multiple times.
	#[must_use]
	pub fn require_caller_ticket(
		mut self,
		validator: impl TicketValidator,
	) -> Self {
		self.caller_auth.push(Arc::new(validator));
		self
	}

	/// Sets the default end-to-end timeout for calls made through this
	/// caller. The timeout bounds the entire call: waiting for an eligible
	/// handler to appear, connecting, invoking, and receiving the reply —
	/// including any failover attempts.
	///
	/// Defaults to the subsystem-wide
	/// [`Config::call_timeout`](crate::functions::Config::call_timeout).
	/// Can be overridden per call with
	/// [`Caller::call_with_timeout`].
	#[must_use]
	pub const fn with_call_timeout(mut self, timeout: Duration) -> Self {
		self.call_timeout = timeout;
		self
	}

	/// A function that produces the availability conditions under which the
	/// caller is considered online. By default the caller is online when at
	/// least one eligible handler is known.
	///
	/// This follows the same API as the `caller.when().available()` method.
	#[must_use]
	pub fn online_when<F>(mut self, f: F) -> Self
	where
		F: Fn(HandlerConditions) -> HandlerConditions + Send + Sync + 'static,
	{
		self.online_when = Box::new(f);
		self
	}

	/// Builds a new caller with the given configuration.
	///
	/// The caller immediately starts watching the discovery catalog for
	/// eligible handler peers.
	pub fn build(self) -> Caller<Req, Res, E> {
		let base = self
			.function_id
			.unwrap_or_else(derived_function_id::<Req, Res>);
		let function_id =
			effective_function_id(base, &self.caller_auth, &self.handler_auth);

		let config = CallerConfig {
			function_id,
			require: self.require,
			handler_auth: self.handler_auth,
			caller_auth: self.caller_auth,
			call_timeout: self.call_timeout,
			online_when: self.online_when,
		};

		CallerWorker::spawn(config, self.functions)
	}
}

impl<'f, Req: Datum, Res: Datum, E: Datum> Builder<'f, Req, Res, E> {
	pub(in crate::functions) fn new(functions: &'f Functions) -> Self {
		Self {
			functions,
			function_id: None,
			require: Box::new(|_| true),
			handler_auth: Vec::new(),
			caller_auth: Vec::new(),
			call_timeout: functions.config.call_timeout,
			online_when: Box::new(|c| c.minimum_of(1)),
			_marker: PhantomData,
		}
	}
}