lxmf-sdk 0.7.0

High-level Rust SDK for LXMF clients and RPC-backed LXMF workflows.
Documentation
use super::config::SdkConfig;
use crate::capability::EffectiveLimits;
use crate::error::{code, ErrorCategory, SdkError};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[non_exhaustive]
pub struct StartRequest {
    pub supported_contract_versions: Vec<u16>,
    pub requested_capabilities: Vec<String>,
    pub config: SdkConfig,
}

pub const DEFAULT_SUPPORTED_CONTRACT_VERSION: u16 = 2;

impl StartRequest {
    pub fn new(config: SdkConfig) -> Self {
        Self {
            supported_contract_versions: vec![DEFAULT_SUPPORTED_CONTRACT_VERSION],
            requested_capabilities: Vec::new(),
            config,
        }
    }

    pub fn with_supported_contract_versions(
        mut self,
        versions: impl IntoIterator<Item = u16>,
    ) -> Self {
        self.supported_contract_versions = versions.into_iter().collect();
        self
    }

    pub fn with_requested_capabilities(
        mut self,
        capabilities: impl IntoIterator<Item = impl Into<String>>,
    ) -> Self {
        self.requested_capabilities =
            capabilities.into_iter().map(Into::into).collect::<Vec<String>>();
        self
    }

    pub fn with_requested_capability(mut self, capability: impl Into<String>) -> Self {
        self.requested_capabilities.push(capability.into());
        self
    }

    pub fn validate(&self) -> Result<(), SdkError> {
        if self.supported_contract_versions.is_empty() {
            return Err(SdkError::new(
                code::CAPABILITY_CONTRACT_INCOMPATIBLE,
                ErrorCategory::Capability,
                "supported_contract_versions must not be empty",
            )
            .with_user_actionable(true));
        }

        let mut seen_versions = BTreeSet::new();
        for version in &self.supported_contract_versions {
            if !seen_versions.insert(*version) {
                return Err(SdkError::new(
                    code::VALIDATION_INVALID_ARGUMENT,
                    ErrorCategory::Validation,
                    "supported_contract_versions must be unique",
                )
                .with_user_actionable(true));
            }
        }

        let mut seen_caps = BTreeSet::new();
        for capability in &self.requested_capabilities {
            let trimmed = capability.trim();
            if trimmed.is_empty() {
                return Err(SdkError::new(
                    code::VALIDATION_INVALID_ARGUMENT,
                    ErrorCategory::Validation,
                    "requested capability IDs must not be empty",
                )
                .with_user_actionable(true));
            }
            if !seen_caps.insert(trimmed.to_owned()) {
                return Err(SdkError::new(
                    code::VALIDATION_INVALID_ARGUMENT,
                    ErrorCategory::Validation,
                    "requested capability IDs must be unique",
                )
                .with_user_actionable(true));
            }
        }

        self.config.validate()
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[non_exhaustive]
pub struct ClientHandle {
    pub runtime_id: String,
    pub active_contract_version: u16,
    pub effective_capabilities: Vec<String>,
    pub effective_limits: EffectiveLimits,
}