superdoc-sdk 2.25.0

Beta Rust SDK for the SuperDoc document runtime
Documentation
//! Invocation boundary used by generated methods and recording transports.

use serde_json::Value;
use std::{future::Future, time::Duration};

/// A structured error from the client, host, or document operation.
#[derive(Clone)]
pub struct Error {
    pub code: String,
    pub message: String,
    /// Original host text is retained for explicit diagnostics. It is omitted
    /// from Debug/Display because it can contain document data or credentials.
    pub host_message: Option<Box<str>>,
    pub details: Option<Box<Value>>,
    pub operation_id: Option<Box<str>>,
    pub rpc_code: Option<i64>,
    pub exit_code: Option<Box<i64>>,
    /// A sent mutation may have completed even when its response was lost.
    pub outcome_unknown: bool,
}

impl Error {
    pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
        Self {
            code: code.into(),
            message: message.into(),
            host_message: None,
            details: None,
            operation_id: None,
            rpc_code: None,
            exit_code: None,
            outcome_unknown: false,
        }
    }

    pub fn decode(cause: serde_json::Error) -> Self {
        let mut error = Self::new(
            "HOST_PROTOCOL_ERROR",
            "Host result does not match its declared response type",
        );
        error.details = Some(Box::new(
            serde_json::json!({"decodeError":cause.to_string()}),
        ));
        error
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(formatter, "{}: {}", self.code, self.message)
    }
}
impl std::error::Error for Error {}

impl std::fmt::Debug for Error {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("Error")
            .field("code", &self.code)
            .field("operation_id", &self.operation_id)
            .field("rpc_code", &self.rpc_code)
            .field("exit_code", &self.exit_code)
            .field("outcome_unknown", &self.outcome_unknown)
            .finish_non_exhaustive()
    }
}

pub type Result<T> = std::result::Result<T, Error>;

/// Invocation options are separate from operation parameters and do not become
/// extra document fields when sent through structured document RPC.
#[derive(Clone, Default)]
pub struct InvokeOptions {
    pub timeout: Option<Duration>,
    pub expected_revision: Option<String>,
    pub change_mode: Option<String>,
    pub dry_run: Option<bool>,
    pub stdin_bytes: Option<Vec<u8>>,
}

impl std::fmt::Debug for InvokeOptions {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("InvokeOptions")
            .field("timeout", &self.timeout)
            .field("has_stdin", &self.stdin_bytes.is_some())
            .finish_non_exhaustive()
    }
}

/// A generated method calls exactly one canonical operation through this seam.
/// Implementations own routing, authentication, session binding and I/O.
pub trait Invoker: Sync {
    fn handle_decode_error(&self, error: Error) -> Error {
        error
    }

    fn invoke(
        &self,
        operation_id: &str,
        params: Value,
        options: InvokeOptions,
    ) -> impl Future<Output = Result<Value>> + Send;
}