torc 0.22.0

Workflow management system
/*
 * torc
 *
 * Defines the API for the torc service.
 *
 * The version of the OpenAPI document: v0.7.0
 *
 * Generated by: https://openapi-generator.tech
 */

#![allow(dead_code)]

use std::path::PathBuf;

/// TLS configuration for client connections.
#[derive(Debug, Clone, Default)]
pub struct TlsConfig {
    /// Path to a PEM-encoded CA certificate to trust.
    pub ca_cert_path: Option<PathBuf>,
    /// Skip certificate verification (for testing only).
    pub insecure: bool,
}

impl TlsConfig {
    /// Apply TLS settings to a blocking client builder and return it.
    ///
    /// This allows callers (like the SSE client) to customize the builder further
    /// before calling `.build()`.
    ///
    /// When both `insecure` and `ca_cert_path` are set, `insecure` takes precedence
    /// and certificate verification is disabled regardless of the CA certificate.
    pub fn configure_blocking_builder(
        &self,
        mut builder: reqwest::blocking::ClientBuilder,
    ) -> reqwest::blocking::ClientBuilder {
        if self.insecure {
            builder = builder.danger_accept_invalid_certs(true);
        }
        if let Some(ref ca_path) = self.ca_cert_path
            && let Ok(pem) = std::fs::read(ca_path)
            && let Ok(cert) = reqwest::Certificate::from_pem(&pem)
        {
            builder = builder.add_root_certificate(cert);
        }
        builder
    }

    /// Build a blocking client with TLS settings applied.
    pub fn build_blocking_client(&self) -> Result<reqwest::blocking::Client, reqwest::Error> {
        self.configure_blocking_builder(reqwest::blocking::Client::builder())
            .build()
    }

    /// Build an async client with TLS settings applied.
    pub fn build_async_client(&self) -> Result<reqwest::Client, reqwest::Error> {
        let mut builder = reqwest::Client::builder();
        if self.insecure {
            builder = builder.danger_accept_invalid_certs(true);
        }
        if let Some(ref ca_path) = self.ca_cert_path
            && let Ok(pem) = std::fs::read(ca_path)
            && let Ok(cert) = reqwest::Certificate::from_pem(&pem)
        {
            builder = builder.add_root_certificate(cert);
        }
        builder.build()
    }
}

#[derive(Clone)]
pub struct Configuration {
    pub base_path: String,
    pub user_agent: Option<String>,
    pub client: reqwest::blocking::Client,
    pub basic_auth: Option<BasicAuth>,
    pub oauth_access_token: Option<String>,
    pub bearer_access_token: Option<String>,
    pub api_key: Option<ApiKey>,
    pub tls: TlsConfig,
    pub cookie_header: Option<String>,
}

impl std::fmt::Debug for Configuration {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Configuration")
            .field("base_path", &self.base_path)
            .field("user_agent", &self.user_agent)
            .field(
                "basic_auth",
                &self
                    .basic_auth
                    .as_ref()
                    .map(|(u, _)| (u, Some("[REDACTED]"))),
            )
            .field(
                "oauth_access_token",
                &self.oauth_access_token.as_ref().map(|_| "[REDACTED]"),
            )
            .field(
                "bearer_access_token",
                &self.bearer_access_token.as_ref().map(|_| "[REDACTED]"),
            )
            .field("api_key", &self.api_key.as_ref().map(|_| "[REDACTED]"))
            .field("tls", &self.tls)
            .field(
                "cookie_header",
                &self.cookie_header.as_ref().map(|_| "[REDACTED]"),
            )
            .finish()
    }
}

pub type BasicAuth = (String, Option<String>);

#[derive(Debug, Clone)]
pub struct ApiKey {
    pub prefix: Option<String>,
    pub key: String,
}

impl Configuration {
    pub fn new() -> Configuration {
        Configuration::default()
    }

    /// Create a new Configuration with the given TLS settings.
    ///
    /// # Panics
    /// Panics if the HTTP client cannot be built (e.g., system TLS backend failure).
    pub fn with_tls(tls: TlsConfig) -> Configuration {
        let client = tls
            .build_blocking_client()
            .expect("Failed to build HTTP client with TLS config");
        Configuration {
            base_path: "http://localhost/torc-service/v1".to_owned(),
            user_agent: Some("OpenAPI-Generator/v0.7.0/rust".to_owned()),
            client,
            basic_auth: None,
            oauth_access_token: None,
            bearer_access_token: None,
            api_key: None,
            tls,
            cookie_header: None,
        }
    }

    /// Rebuild the HTTP client with the cookie header set as a default header.
    /// This must be called after setting `cookie_header` for it to take effect.
    ///
    /// # Errors
    /// Returns an error if the cookie header value is invalid or if the HTTP client
    /// cannot be rebuilt.
    pub fn apply_cookie_header(&mut self) -> Result<(), String> {
        if let Some(ref cookie) = self.cookie_header {
            let mut headers = reqwest::header::HeaderMap::new();
            headers.insert(
                reqwest::header::COOKIE,
                reqwest::header::HeaderValue::from_str(cookie)
                    .map_err(|e| format!("Invalid cookie header value: {e}"))?,
            );
            self.client = self
                .tls
                .configure_blocking_builder(
                    reqwest::blocking::Client::builder().default_headers(headers),
                )
                .build()
                .map_err(|e| format!("Failed to rebuild HTTP client with cookie header: {e}"))?;
        }
        Ok(())
    }

    /// Check the `TORC_COOKIE_HEADER` environment variable and, if set, apply it
    /// as the default cookie header on this configuration's HTTP client.
    ///
    /// This centralizes the env-var lookup + apply pattern used across multiple
    /// call sites (TUI, MCP server, slurm job runner, etc.).
    ///
    /// # Errors
    /// Returns an error if the env var is set but contains an invalid header value
    /// or if the HTTP client cannot be rebuilt.
    pub fn apply_cookie_header_from_env(&mut self) -> Result<(), String> {
        if let Ok(cookie) = std::env::var("TORC_COOKIE_HEADER") {
            self.cookie_header = Some(cookie);
            if let Err(e) = self.apply_cookie_header() {
                self.cookie_header = None;
                return Err(e);
            }
        }
        Ok(())
    }

    /// Apply configured authentication to a blocking request builder.
    pub fn apply_auth(
        &self,
        mut req_builder: reqwest::blocking::RequestBuilder,
    ) -> reqwest::blocking::RequestBuilder {
        if let Some((ref username, ref password)) = self.basic_auth {
            req_builder = req_builder.basic_auth(username, password.clone());
        }
        if let Some(ref token) = self.bearer_access_token {
            req_builder = req_builder.bearer_auth(token);
        } else if let Some(ref token) = self.oauth_access_token {
            req_builder = req_builder.bearer_auth(token);
        }
        if let Some(ref api_key) = self.api_key {
            let value = if let Some(ref prefix) = api_key.prefix {
                format!("{prefix} {}", api_key.key)
            } else {
                api_key.key.clone()
            };
            req_builder = req_builder.header("X-API-Key", value);
        }
        req_builder
    }
}

impl Default for Configuration {
    fn default() -> Self {
        Configuration {
            base_path: "http://localhost/torc-service/v1".to_owned(),
            user_agent: Some("OpenAPI-Generator/v0.7.0/rust".to_owned()),
            client: reqwest::blocking::Client::new(),
            basic_auth: None,
            oauth_access_token: None,
            bearer_access_token: None,
            api_key: None,
            tls: TlsConfig::default(),
            cookie_header: None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_tls_config_default() {
        let tls = TlsConfig::default();
        assert!(tls.ca_cert_path.is_none());
        assert!(!tls.insecure);
    }

    #[test]
    fn test_tls_config_insecure_builds_client() {
        let tls = TlsConfig {
            ca_cert_path: None,
            insecure: true,
        };
        let client = tls.build_blocking_client();
        assert!(client.is_ok());
    }

    #[test]
    fn test_tls_config_nonexistent_cert_builds_client() {
        // A non-existent CA cert path should not prevent building the client
        let tls = TlsConfig {
            ca_cert_path: Some(PathBuf::from("/nonexistent/ca.pem")),
            insecure: false,
        };
        let client = tls.build_blocking_client();
        assert!(client.is_ok());
    }

    #[test]
    fn test_configuration_with_tls() {
        let tls = TlsConfig {
            ca_cert_path: None,
            insecure: true,
        };
        let config = Configuration::with_tls(tls);
        assert!(config.tls.insecure);
    }

    #[test]
    fn test_tls_config_async_client() {
        let tls = TlsConfig {
            ca_cert_path: None,
            insecure: true,
        };
        let client = tls.build_async_client();
        assert!(client.is_ok());
    }

    #[test]
    fn test_apply_cookie_header_valid() {
        let mut config = Configuration {
            cookie_header: Some("session=abc123".to_string()),
            ..Default::default()
        };
        assert!(config.apply_cookie_header().is_ok());
    }

    #[test]
    fn test_apply_cookie_header_invalid() {
        // Header values cannot contain non-visible ASCII characters
        let mut config = Configuration {
            cookie_header: Some("session=abc\x00123".to_string()),
            ..Default::default()
        };
        assert!(config.apply_cookie_header().is_err());
    }

    #[test]
    fn test_apply_cookie_header_none() {
        let mut config = Configuration::default();
        assert!(config.apply_cookie_header().is_ok());
    }

    #[test]
    fn test_apply_cookie_header_from_env_unset() {
        // When TORC_COOKIE_HEADER is not set, should be a no-op
        // SAFETY: This test runs single-threaded via serial_test or cargo test -- --test-threads 1
        unsafe { std::env::remove_var("TORC_COOKIE_HEADER") };
        let mut config = Configuration::default();
        assert!(config.apply_cookie_header_from_env().is_ok());
        assert!(config.cookie_header.is_none());
    }

    #[test]
    fn test_debug_redacts_sensitive_fields() {
        let config = Configuration {
            cookie_header: Some("session=secret".to_string()),
            bearer_access_token: Some("my-token".to_string()),
            ..Default::default()
        };
        let debug_output = format!("{:?}", config);
        assert!(!debug_output.contains("secret"));
        assert!(!debug_output.contains("my-token"));
        assert!(debug_output.contains("[REDACTED]"));
    }
}