corsa_client 0.7.0

Typed stdio API client bindings for typescript-go
Documentation
//! Process-spawning helpers for the stdio API transports.
//!
//! The public API exposes higher-level configuration types such as
//! [`crate::ApiSpawnConfig`]. This module is responsible for translating those
//! settings into the exact command-line arguments and transport objects used by
//! the JSON-RPC and msgpack clients.

use super::{
    callbacks::{callback_flag, jsonrpc_handlers},
    driver::ClientDriver,
};
use crate::{
    Result, TsgoError,
    jsonrpc::{JsonRpcConnection, JsonRpcConnectionOptions},
    process::{AsyncChildGuard, TsgoCommand},
};
use corsa_core::fast::{CompactString, SmallVec};
use std::{
    io::{BufReader, BufWriter},
    sync::Arc,
};

pub(super) async fn spawn_jsonrpc_stdio(
    command: &TsgoCommand,
    filesystem: Option<Arc<dyn super::ApiFileSystem>>,
    request_timeout: Option<std::time::Duration>,
    shutdown_timeout: std::time::Duration,
    outbound_capacity: usize,
    observer: Option<corsa_core::SharedObserver>,
) -> Result<ClientDriver> {
    // JSON-RPC mode is used for callback-capable, async request/response
    // flows. The worker process is wrapped in `AsyncChildGuard` so shutdown
    // always reaps the child.
    let args = stdio_args(command, filesystem.as_deref(), true);
    let mut child = command.spawn_async(args.iter().map(CompactString::as_str))?;
    let stdin = child.stdin.take().ok_or(TsgoError::Closed("api stdin"))?;
    let stdout = child.stdout.take().ok_or(TsgoError::Closed("api stdout"))?;
    let handlers = filesystem.map(jsonrpc_handlers).unwrap_or_default();
    let rpc = JsonRpcConnection::spawn_with_options(
        BufReader::new(stdout),
        BufWriter::new(stdin),
        handlers,
        JsonRpcConnectionOptions::new()
            .with_request_timeout(request_timeout)
            .with_outbound_capacity(outbound_capacity)
            .with_observer_if_some(observer),
    );
    Ok(ClientDriver::JsonRpc {
        rpc,
        process: Some(Arc::new(AsyncChildGuard::new(child))),
        shutdown_timeout,
    })
}

pub(super) fn spawn_msgpack_stdio(
    command: &TsgoCommand,
    filesystem: Option<Arc<dyn super::ApiFileSystem>>,
    request_timeout: Option<std::time::Duration>,
    outbound_capacity: usize,
    observer: Option<corsa_core::SharedObserver>,
) -> Result<ClientDriver> {
    // Msgpack mode keeps a dedicated worker thread around the blocking stdio
    // pipes. This avoids async framing overhead on the hot path.
    let args = stdio_args(command, filesystem.as_deref(), false);
    let child = command.spawn_blocking(args.iter().map(CompactString::as_str))?;
    let worker = super::msgpack_worker::MsgpackWorker::spawn(
        child,
        filesystem,
        request_timeout,
        outbound_capacity,
        observer,
    )?;
    Ok(ClientDriver::Msgpack {
        worker: Arc::new(worker),
    })
}

fn stdio_args(
    command: &TsgoCommand,
    filesystem: Option<&dyn super::ApiFileSystem>,
    async_mode: bool,
) -> SmallVec<[CompactString; 6]> {
    let mut args = SmallVec::<[CompactString; 6]>::new();
    args.push(CompactString::from("--api"));
    if async_mode {
        args.push(CompactString::from("--async"));
    }
    // Pass the resolved working directory explicitly so downstream tools and
    // diagnostics see the same root the Rust side expects.
    args.push(CompactString::from("--cwd"));
    args.push(CompactString::from(command.cwd().display().to_string()));
    if let Some(filesystem) = filesystem.and_then(callback_flag) {
        args.push(filesystem);
    }
    args
}