stasis-rs 0.1.0

Durable AI orchestration framework with runtime jobs, lineage, and memory integration
Documentation
use chrono::Utc;

use crate::application::dto::{
    RegisterDeliveryEndpointRequest, RegisterDeliveryEndpointResponse,
    SetDeliveryEndpointEnabledRequest,
};
use crate::domain::errors::{Result, StasisError};
use crate::domain::runtime::delivery_endpoint::{DeliveryEndpoint, NewDeliveryEndpoint};
use crate::ports::outbound::runtime::delivery_endpoint_store::DeliveryEndpointStore;

#[derive(Clone)]
pub struct RegisterDeliveryEndpoint<S>
where
    S: DeliveryEndpointStore,
{
    store: S,
}

impl<S> RegisterDeliveryEndpoint<S>
where
    S: DeliveryEndpointStore,
{
    pub fn new(store: S) -> Self {
        Self { store }
    }

    pub async fn execute(
        &self,
        request: RegisterDeliveryEndpointRequest,
    ) -> Result<RegisterDeliveryEndpointResponse> {
        if request.endpoint_id.trim().is_empty() {
            return Err(StasisError::PortFailure(
                "endpoint_id must not be empty".to_string(),
            ));
        }
        if request.name.trim().is_empty() {
            return Err(StasisError::PortFailure(
                "name must not be empty".to_string(),
            ));
        }
        if request.target.trim().is_empty() {
            return Err(StasisError::PortFailure(
                "target must not be empty".to_string(),
            ));
        }

        let record = self
            .store
            .insert(NewDeliveryEndpoint {
                endpoint_id: request.endpoint_id,
                name: request.name,
                protocol: request.protocol,
                target: request.target,
                metadata: request.metadata,
                created_at: Utc::now(),
            })
            .await?;

        Ok(RegisterDeliveryEndpointResponse {
            endpoint_id: record.endpoint_id,
            enabled: record.enabled,
        })
    }
}

#[derive(Clone)]
pub struct SetDeliveryEndpointEnabled<S>
where
    S: DeliveryEndpointStore,
{
    store: S,
}

impl<S> SetDeliveryEndpointEnabled<S>
where
    S: DeliveryEndpointStore,
{
    pub fn new(store: S) -> Self {
        Self { store }
    }

    pub async fn execute(&self, request: SetDeliveryEndpointEnabledRequest) -> Result<()> {
        if request.endpoint_id.trim().is_empty() {
            return Err(StasisError::PortFailure(
                "endpoint_id must not be empty".to_string(),
            ));
        }

        let updated = self
            .store
            .set_enabled(&request.endpoint_id, request.enabled)
            .await?;

        if !updated {
            return Err(StasisError::PortFailure(format!(
                "delivery endpoint not found: {}",
                request.endpoint_id
            )));
        }

        Ok(())
    }
}

#[derive(Clone)]
pub struct ListDeliveryEndpoints<S>
where
    S: DeliveryEndpointStore,
{
    store: S,
}

impl<S> ListDeliveryEndpoints<S>
where
    S: DeliveryEndpointStore,
{
    pub fn new(store: S) -> Self {
        Self { store }
    }

    pub async fn execute(&self) -> Result<Vec<DeliveryEndpoint>> {
        self.store.list().await
    }
}

#[cfg(test)]
mod tests {
    use super::{ListDeliveryEndpoints, RegisterDeliveryEndpoint, SetDeliveryEndpointEnabled};
    use crate::application::dto::{
        RegisterDeliveryEndpointRequest, SetDeliveryEndpointEnabledRequest,
    };
    use crate::domain::runtime::delivery_endpoint::DeliveryProtocol;
    use crate::infrastructure::runtime::in_memory_delivery_endpoint_store::InMemoryDeliveryEndpointStore;

    #[tokio::test]
    async fn register_list_and_toggle_delivery_endpoint_flow_works() {
        let store = InMemoryDeliveryEndpointStore::default();
        let register = RegisterDeliveryEndpoint::new(store.clone());
        let list = ListDeliveryEndpoints::new(store.clone());
        let set_enabled = SetDeliveryEndpointEnabled::new(store);

        let registered = register
            .execute(RegisterDeliveryEndpointRequest {
                endpoint_id: "endpoint.ops.webhook".to_string(),
                name: "Ops webhook".to_string(),
                protocol: DeliveryProtocol::HttpWebhook,
                target: "https://ops.example.com/hooks/stasis".to_string(),
                metadata: Some("priority=high".to_string()),
            })
            .await
            .expect("endpoint should register");

        assert_eq!(registered.endpoint_id, "endpoint.ops.webhook");
        assert!(registered.enabled);

        set_enabled
            .execute(SetDeliveryEndpointEnabledRequest {
                endpoint_id: "endpoint.ops.webhook".to_string(),
                enabled: false,
            })
            .await
            .expect("endpoint should toggle");

        let endpoints = list.execute().await.expect("list should succeed");
        assert_eq!(endpoints.len(), 1);
        assert!(!endpoints[0].enabled);
    }
}