lenso-platform-provider 0.1.23

Provider Service host transport for the Lenso backend framework.
Documentation
use crate::ProviderHostEffectCoordinator;
use crate::config::ProviderConfig;
use crate::invocation::{self, InvocationContext};
use crate::protocol::{
    ProviderActionInvokeResponse, ProviderAdminActionInvokeRequest, ProviderInvocationMode,
    ProviderOperationKind,
};
use platform_core::{ActorContext, AppError, AppResult, ErrorCode, TraceContext};
use platform_module::AdminActionSource;
use serde_json::Value;
use std::time::Duration;

#[derive(Debug, Clone)]
pub struct ProviderAdminActionSource {
    client: reqwest::Client,
    config: ProviderConfig,
    effects: ProviderHostEffectCoordinator,
}

impl ProviderAdminActionSource {
    pub fn new(config: ProviderConfig) -> AppResult<Self> {
        let client = reqwest::Client::builder()
            .timeout(Duration::from_millis(config.timeout_ms))
            .build()
            .map_err(|error| {
                AppError::new(
                    ErrorCode::Internal,
                    format!("failed to build Provider Service client: {error}"),
                )
            })?;
        Ok(Self {
            client,
            config,
            effects: ProviderHostEffectCoordinator::rejecting(),
        })
    }

    #[must_use]
    pub fn with_effect_coordinator(mut self, effects: ProviderHostEffectCoordinator) -> Self {
        self.effects = effects;
        self
    }
}

#[async_trait::async_trait]
impl AdminActionSource for ProviderAdminActionSource {
    async fn invoke(&self, action: &str, input: Value) -> AppResult<Value> {
        validate_action_name(action)?;
        let invocation_id = uuid::Uuid::now_v7().to_string();
        let invocation = invocation::build(
            &self.config,
            ProviderOperationKind::AdminAction,
            action,
            "1",
            ProviderInvocationMode::Durable,
            InvocationContext {
                request_id: invocation_id.clone(),
                invocation_id,
                attempt: 1,
                actor: ActorContext::System,
                correlation_id: uuid::Uuid::now_v7().to_string(),
                causation_id: None,
                trace: TraceContext::default(),
            },
            serde_json::to_value(ProviderAdminActionInvokeRequest {
                action: action.to_owned(),
                input,
            })
            .map_err(|error| AppError::new(ErrorCode::Internal, error.to_string()))?,
        )?;
        let outcome = invocation::send(
            &self.client,
            &self.config,
            &self.effects,
            "admin:act",
            &invocation,
        )
        .await?;
        let envelope: ProviderActionInvokeResponse =
            serde_json::from_value(invocation::result(&invocation, outcome)?).map_err(|error| {
                AppError::new(
                    ErrorCode::ExternalDependency,
                    format!("Provider admin action result violated its contract: {error}"),
                )
            })?;
        Ok(envelope.result)
    }
}

fn validate_action_name(action: &str) -> AppResult<()> {
    let valid = !action.is_empty()
        && action.chars().all(|character| {
            character.is_ascii_alphanumeric()
                || character == '.'
                || character == '_'
                || character == '-'
        });
    if valid {
        return Ok(());
    }

    Err(AppError::new(
        ErrorCode::Validation,
        "provider admin action name must be a stable path segment",
    ))
}