AkurAI Build
Menu

Public repository / master

AkurAI-Framework / crates/http/src/endpoint.rs

Latest change c6179dcf3abbf53272e4279e6740397ad2e75726 - AKURAI-FRAMEWORK-1..9 consolidate framework APIs by Ólafur Búi Ólafsson

//! Shared parsing for plain-HTTP service endpoints.

/// Return whether an endpoint uses the unsupported HTTPS scheme.
pub fn is_https_endpoint(endpoint: &str) -> bool {
    let trimmed = endpoint.trim();
    trimmed.len() >= 8 && trimmed[..8].eq_ignore_ascii_case("https://")
}

/// Parse `http://host[:port]` into an authority and port.
///
/// Paths, queries, and fragments are ignored. Callers retain ownership of
/// scheme-specific errors while this helper owns trimming, authority parsing,
/// defaulting, and invalid-host/port behavior.
pub fn parse_plain_endpoint(endpoint: &str) -> Result<(String, u16), String> {
    let trimmed = endpoint.trim();
    let rest = strip_ci_prefix(trimmed, "http://")
        .ok_or_else(|| format!("must start with http://: {trimmed}"))?;
    let authority = rest
        .split(['/', '?', '#'])
        .next()
        .unwrap_or("")
        .trim_end_matches('/');
    if authority.is_empty() {
        return Err(format!("missing host: {trimmed}"));
    }

    let (host, port) = match authority.rsplit_once(':') {
        Some((host, port)) => {
            let port = port
                .parse::<u16>()
                .map_err(|_| format!("bad port: {port}"))?;
            (host, port)
        }
        None => (authority, 80),
    };
    if host.is_empty() {
        return Err(format!("missing host: {trimmed}"));
    }
    Ok((host.to_string(), port))
}

fn strip_ci_prefix<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
    if s.len() >= prefix.len() && s[..prefix.len()].eq_ignore_ascii_case(prefix) {
        Some(&s[prefix.len()..])
    } else {
        None
    }
}

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

    #[test]
    fn parses_authority_and_ignores_path() {
        assert_eq!(
            parse_plain_endpoint(" HTTP://localhost:8080/v1?q#f "),
            Ok(("localhost".to_string(), 8080))
        );
        assert_eq!(
            parse_plain_endpoint("http://localhost"),
            Ok(("localhost".to_string(), 80))
        );
    }

    #[test]
    fn preserves_invalid_endpoint_errors() {
        assert_eq!(
            parse_plain_endpoint("https://localhost"),
            Err("must start with http://: https://localhost".to_string())
        );
        assert_eq!(
            parse_plain_endpoint("http://localhost:nope"),
            Err("bad port: nope".to_string())
        );
    }
}