stdiobus-client 1.1.1

Async client for stdio_bus - AI agent transport layer for MCP/ACP protocols
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026-present Raman Marozau <raman@worktif.com>
// Copyright (c) 2026-present stdiobus contributors

//! Builder pattern for StdioBus client

use crate::client::StdioBus;
use stdiobus_core::{BackendMode, BusConfig, ConfigSource, DockerOptions, Error, Result};
use std::time::Duration;

/// Builder for creating StdioBus instances
#[derive(Debug, Clone)]
pub struct StdioBusBuilder {
    config_source: Option<ConfigSource>,
    backend: BackendMode,
    timeout: Duration,
    docker_options: Option<DockerOptions>,
}

impl Default for StdioBusBuilder {
    fn default() -> Self {
        Self {
            config_source: None,
            backend: BackendMode::Auto,
            timeout: Duration::from_secs(30),
            docker_options: None,
        }
    }
}

impl StdioBusBuilder {
    /// Create a new builder
    pub fn new() -> Self {
        Self::default()
    }

    /// Set programmatic configuration (primary, recommended).
    ///
    /// Mutually exclusive with `config_path`.
    pub fn config(mut self, config: BusConfig) -> Self {
        self.config_source = Some(ConfigSource::Config(config));
        self
    }

    /// Set the configuration file path.
    ///
    /// Mutually exclusive with `config`.
    pub fn config_path(mut self, path: impl Into<String>) -> Self {
        self.config_source = Some(ConfigSource::Path(path.into()));
        self
    }

    /// Set the backend mode
    pub fn backend(mut self, mode: BackendMode) -> Self {
        self.backend = mode;
        self
    }

    /// Use auto backend selection (default)
    pub fn backend_auto(mut self) -> Self {
        self.backend = BackendMode::Auto;
        self
    }

    /// Use native backend (requires libstdio_bus)
    pub fn backend_native(mut self) -> Self {
        self.backend = BackendMode::Native;
        self
    }

    /// Use Docker backend
    pub fn backend_docker(mut self) -> Self {
        self.backend = BackendMode::Docker;
        self
    }

    /// Set default request timeout
    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.timeout = timeout;
        self
    }

    /// Set Docker options
    pub fn docker_options(mut self, options: DockerOptions) -> Self {
        self.docker_options = Some(options);
        self
    }

    /// Set Docker image
    pub fn docker_image(mut self, image: impl Into<String>) -> Self {
        let opts = self.docker_options.get_or_insert_with(DockerOptions::default);
        opts.image = image.into();
        self
    }

    /// Set Docker pull policy: "never", "if-missing", "always"
    pub fn docker_pull_policy(mut self, policy: impl Into<String>) -> Self {
        let opts = self.docker_options.get_or_insert_with(DockerOptions::default);
        opts.pull_policy = policy.into();
        self
    }

    /// Build the StdioBus instance
    pub fn build(self) -> Result<StdioBus> {
        let config_source = self.config_source.ok_or_else(|| Error::InvalidArgument {
            message: "config or config_path is required".to_string(),
        })?;

        // Validate if programmatic config
        if let ConfigSource::Config(ref cfg) = config_source {
            cfg.validate().map_err(|msg| Error::InvalidArgument { message: msg })?;
        }

        StdioBus::new(config_source, self.backend, self.timeout, self.docker_options)
    }
}