Skip to main content

rust_mc_status/core/
address.rs

1//! Address string parsing for Minecraft server addresses.
2//!
3//! Handles all common address formats:
4//!
5//! | Input | Host | Port | Explicit port |
6//! |-------|------|------|---------------|
7//! | `"mc.hypixel.net"` | `"mc.hypixel.net"` | default | `false` |
8//! | `"mc.hypixel.net:19132"` | `"mc.hypixel.net"` | `19132` | `true` |
9//! | `"192.168.1.1"` | `"192.168.1.1"` | default | `false` |
10//! | `"192.168.1.1:25566"` | `"192.168.1.1"` | `25566` | `true` |
11//! | `"[::1]"` | `"::1"` | default | `false` |
12//! | `"[::1]:25565"` | `"::1"` | `25565` | `true` |
13//! | `"::1"` (bare) | `"::1"` | default | `false` |
14
15use crate::error::McError;
16
17/// Parse an address string into `(host, port, explicit_port)`.
18///
19/// `explicit_port` is `true` when a port was present in the string, `false`
20/// when `default_port` was used as a fallback.  This flag is used by
21/// [`McClient`](crate::McClient) to decide whether to attempt an SRV lookup —
22/// SRV is skipped when the caller already specified an explicit port.
23///
24/// # Supported formats
25///
26/// - `"hostname"` — uses `default_port`
27/// - `"hostname:port"` — uses the given port
28/// - `"[::1]"` — IPv6 without port, uses `default_port`
29/// - `"[::1]:port"` — IPv6 with port
30/// - `"::1"` — bare IPv6 (detected by multiple colons), uses `default_port`
31///
32/// # Errors
33///
34/// - [`McError::Config(InvalidAddress)`](crate::error::ConfigError::InvalidAddress)
35///   — IPv6 address is missing the closing `]`.
36/// - [`McError::Config(InvalidPort)`](crate::error::ConfigError::InvalidPort)
37///   — port string cannot be parsed as `u16`.
38pub fn parse(addr: &str, default_port: u16) -> Result<(&str, u16, bool), McError> {
39    // IPv6 literal: [::1] or [::1]:port
40    if let Some(rest) = addr.strip_prefix('[') {
41        let end = rest
42            .find(']')
43            .ok_or_else(|| McError::invalid_address("missing ']' in IPv6 address"))?;
44        let host  = &rest[..end];
45        let after = &rest[end + 1..];
46        if let Some(p) = after.strip_prefix(':') {
47            let port = p
48                .parse::<u16>()
49                .map_err(|e| McError::invalid_port(e.to_string()))?;
50            return Ok((host, port, true));
51        }
52        return Ok((host, default_port, false));
53    }
54    // Bare IPv6: more than one colon and no brackets — treat as host only
55    if addr.chars().filter(|&c| c == ':').count() > 1 {
56        return Ok((addr, default_port, false));
57    }
58    // hostname:port or plain hostname
59    match addr.split_once(':') {
60        Some((h, p)) => {
61            let port = p
62                .parse::<u16>()
63                .map_err(|e| McError::invalid_port(e.to_string()))?;
64            Ok((h, port, true))
65        }
66        None => Ok((addr, default_port, false)),
67    }
68}