liminal-server 0.25.4

Standalone server for the liminal messaging bus
Documentation
//! The production [`ServiceAction`] for alarms and launches (ACCESS-003 R1).
//!
//! An alarm or a launch request is a message: once the caller's grant on
//! `alarms` or `launches` allowed it, its payload is published on that
//! subject through the server's own publish path, once per operation id, for
//! whoever serves it. `context-watch` is not acted on by a publish: a granted
//! request on it is a context reading, reported to Lys through the
//! [`ContextReporter`](super::context_watch::ContextReporter), and Lys
//! decides what a crossed budget does.

use super::context_watch::ContextReporter;
use super::keys::{ServicePublisher, service_envelope};
use super::{ServiceAction, ServiceEffectError, ServiceKind};
use std::sync::Arc;

/// The schema bytes an alarm is published under.
pub const ALARMS_SCHEMA: &[u8] = b"liminal.services.alarms/1";
/// The schema bytes a launch request is published under.
pub const LAUNCHES_SCHEMA: &[u8] = b"liminal.services.launches/1";

/// Alarms and launches published, context readings reported to Lys.
#[derive(Debug)]
pub struct PublishingActions {
    publisher: Arc<dyn ServicePublisher>,
    context: Arc<ContextReporter>,
}

impl PublishingActions {
    /// Publishes through `publisher`; reports context readings through
    /// `context`.
    #[must_use]
    pub const fn new(publisher: Arc<dyn ServicePublisher>, context: Arc<ContextReporter>) -> Self {
        Self { publisher, context }
    }
}

impl ServiceAction for PublishingActions {
    fn act(
        &self,
        service: ServiceKind,
        payload: &[u8],
        operation: &str,
    ) -> Result<(), ServiceEffectError> {
        let schema = match service {
            ServiceKind::Alarms => ALARMS_SCHEMA,
            ServiceKind::Launches => LAUNCHES_SCHEMA,
            ServiceKind::ContextWatch => return self.context.report(payload, operation),
            ServiceKind::Seats | ServiceKind::Budgets | ServiceKind::SessionKeys => {
                return Err(ServiceEffectError(format!(
                    "{} is not acted on as a bare service",
                    service.name()
                )));
            }
        };
        if payload.is_empty() {
            return Err(ServiceEffectError(format!(
                "an empty {} request carries nothing to publish",
                service.name()
            )));
        }
        self.publisher.publish(
            service.subject_shape(),
            &service_envelope(schema, payload),
            operation,
        )
    }
}