rconsole 1.1.0

A WebSocket-based logging library for Rust - send structured logs to NConsole desktop app
Documentation
//! URI normalization for WebSocket connections.
//!
//! Ensures URIs have proper `ws://` scheme and default port `9090`
//! when connecting to IP addresses or localhost.

const DEFAULT_URI: &str = "ws://localhost:9090";
const DEFAULT_PORT: &str = "9090";

/// Normalize a WebSocket URI string.
///
/// Applies the following rules:
/// 1. If `uri` is `None`, returns the default URI (`ws://localhost:9090`).
/// 2. Trims whitespace.
/// 3. Adds `ws://` prefix if no scheme is present.
/// 4. Appends `:9090` if the host is an IP address (x.x.x.x) or `localhost`
///    and no port is specified.
///
/// # Examples
/// ```
/// use rconsole::console::normalize_uri;
///
/// assert_eq!(normalize_uri(None), "ws://localhost:9090");
/// assert_eq!(normalize_uri(Some("192.168.1.1")), "ws://192.168.1.1:9090");
/// assert_eq!(normalize_uri(Some("ws://example.com:8080")), "ws://example.com:8080");
/// ```
pub fn normalize_uri(uri: Option<&str>) -> String {
    let uri = match uri {
        None => return DEFAULT_URI.to_string(),
        Some(u) => u.trim(),
    };

    if uri.is_empty() {
        return DEFAULT_URI.to_string();
    }

    let mut uri_new = String::from(uri);

    // Add ws:// prefix if no scheme is present
    if !uri_new.starts_with("ws://") && !uri_new.starts_with("wss://") {
        uri_new = format!("ws://{}", uri_new);
    }

    let uri_parts: Vec<&str> = uri_new.split(':').collect();

    match uri_parts.len() {
        // Already has scheme + host + port (e.g. "ws://host:port")
        3 => uri_new,

        // Has scheme + host but no port (e.g. "ws://host")
        2 => {
            // Extract the host part after "ws://" or "wss://"
            let host = uri_parts[1].trim_start_matches("//");

            let ip_parts: Vec<&str> = host.split('.').collect();
            let is_ip_address = ip_parts.len() == 4;
            let is_localhost = host == "localhost";

            if is_ip_address || is_localhost {
                format!("{}:{}", uri_new, DEFAULT_PORT)
            } else {
                uri_new
            }
        }

        // Other cases (malformed)
        _ => uri_new,
    }
}

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

    #[test]
    fn test_default_uri() {
        assert_eq!(normalize_uri(None), "ws://localhost:9090");
    }

    #[test]
    fn test_empty_string() {
        assert_eq!(normalize_uri(Some("")), "ws://localhost:9090");
    }

    #[test]
    fn test_whitespace_only() {
        assert_eq!(normalize_uri(Some("   ")), "ws://localhost:9090");
    }

    #[test]
    fn test_ip_without_port() {
        assert_eq!(
            normalize_uri(Some("192.168.1.1")),
            "ws://192.168.1.1:9090"
        );
    }

    #[test]
    fn test_ip_with_scheme_without_port() {
        assert_eq!(
            normalize_uri(Some("ws://192.168.1.100")),
            "ws://192.168.1.100:9090"
        );
    }

    #[test]
    fn test_ip_with_port() {
        assert_eq!(
            normalize_uri(Some("192.168.1.1:8080")),
            "ws://192.168.1.1:8080"
        );
    }

    #[test]
    fn test_full_uri_with_scheme_and_port() {
        assert_eq!(
            normalize_uri(Some("ws://example.com:8080")),
            "ws://example.com:8080"
        );
    }

    #[test]
    fn test_wss_scheme() {
        assert_eq!(
            normalize_uri(Some("wss://secure.example.com:443")),
            "wss://secure.example.com:443"
        );
    }

    #[test]
    fn test_domain_without_port() {
        // Regular domain names should NOT get :9090 appended
        assert_eq!(normalize_uri(Some("example.com")), "ws://example.com");
    }

    #[test]
    fn test_localhost_without_port() {
        assert_eq!(normalize_uri(Some("localhost")), "ws://localhost:9090");
    }

    #[test]
    fn test_localhost_with_port() {
        assert_eq!(
            normalize_uri(Some("localhost:3000")),
            "ws://localhost:3000"
        );
    }

    #[test]
    fn test_trimming_whitespace() {
        assert_eq!(
            normalize_uri(Some(" 192.168.1.1 ")),
            "ws://192.168.1.1:9090"
        );
    }

    #[test]
    fn test_trimming_localhost() {
        assert_eq!(
            normalize_uri(Some(" localhost ")),
            "ws://localhost:9090"
        );
    }

    #[test]
    fn test_ws_prefix_localhost() {
        assert_eq!(
            normalize_uri(Some("ws://localhost")),
            "ws://localhost:9090"
        );
    }
}