rconsole 1.1.0

A WebSocket-based logging library for Rust - send structured logs to NConsole desktop app
Documentation
//! Client information collected at runtime.
//!
//! Mirrors the Flutter `ClientInfo` model with fields relevant to Rust.

use serde::Serialize;

/// Information about the client environment.
///
/// Sent with every log message so the Server Log app can identify
/// and display context about the source.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientInfo {
    /// Unique identifier for this client (e.g. "Rust/1.78.0 (macos)")
    pub id: String,
    /// Human-readable client name
    pub name: String,
    /// Platform identifier (always "rust")
    pub platform: String,
    /// Library version (from Cargo.toml)
    pub version: String,
    /// Operating system name
    pub os: String,
    /// Operating system version
    pub os_version: String,
    /// System language/locale
    pub language: String,
    /// Timezone offset string
    pub time_zone: String,
    /// User agent string
    pub user_agent: String,
    /// Whether this is a debug build
    pub debug: bool,
}

impl ClientInfo {
    /// Create a new `ClientInfo` by collecting system information.
    pub fn new() -> Self {
        let rust_version = rustc_version::version()
            .map(|v| v.to_string())
            .unwrap_or_else(|_| "unknown".to_string());

        let os = std::env::consts::OS;
        let os_version = os_info::get().version().to_string();

        let id = format!("Rust/{} ({})", rust_version, os);

        ClientInfo {
            id: id.clone(),
            name: "Rust Client".to_string(),
            platform: "rust".to_string(),
            version: env!("CARGO_PKG_VERSION").to_string(),
            os: os.to_string(),
            os_version,
            language: std::env::var("LANG").unwrap_or_else(|_| "en-US".to_string()),
            time_zone: chrono::Local::now().offset().to_string(),
            user_agent: id,
            debug: cfg!(debug_assertions),
        }
    }
}

impl Default for ClientInfo {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_client_info_creation() {
        let info = ClientInfo::new();

        assert_eq!(info.platform, "rust");
        assert_eq!(info.name, "Rust Client");
        assert!(!info.version.is_empty());
        assert!(!info.os.is_empty());
        assert!(!info.id.is_empty());
        assert!(info.id.starts_with("Rust/"));
    }

    #[test]
    fn test_client_info_debug_flag() {
        let info = ClientInfo::new();
        // In test mode, debug_assertions is true
        assert!(info.debug);
    }

    #[test]
    fn test_client_info_serialization() {
        let info = ClientInfo::new();
        let json = serde_json::to_value(&info).unwrap();

        // Verify camelCase field names
        assert!(json.get("id").is_some());
        assert!(json.get("name").is_some());
        assert!(json.get("platform").is_some());
        assert!(json.get("osVersion").is_some());
        assert!(json.get("timeZone").is_some());
        assert!(json.get("userAgent").is_some());
    }
}