rust-mc-status 3.0.0

High-performance asynchronous Rust library for querying Minecraft server status (Java & Bedrock)
Documentation
//! [`JavaServerStatus`] — typed wrapper around a Java Edition ping result.
//!
//! Returned by [`McClient::java`](crate::McClient::java) and
//! [`ping_java`](crate::ping_java). Wraps a [`ServerStatus`] and exposes
//! Java-specific accessors so callers don't need to match on [`ServerData`].

use std::fmt;

use serde::Serialize;

use crate::models::{ServerStatus, ServerData};
use crate::core::fmt::strip_formatting;
use super::ext::StatusExt;

/// Typed wrapper around a Java Edition [`ServerStatus`].
///
/// Implements [`StatusExt`] for common fields and [`serde::Serialize`] via
/// `#[serde(transparent)]` — serialises identically to the inner
/// [`ServerStatus`].
///
/// # Accessing raw fields
///
/// Use `.raw()` to get the underlying [`ServerStatus`] directly, or match on
/// [`ServerData::Java`](crate::ServerData::Java) for the raw [`JavaStatus`](crate::JavaStatus).
///
/// # Example
///
/// ```rust,no_run
/// use rust_mc_status::{McClient, StatusExt};
///
/// # #[tokio::main] async fn main() -> Result<(), rust_mc_status::McError> {
/// let client = McClient::builder().build();
/// let s = client.java("mc.hypixel.net").await?;
///
/// println!("Version  : {}", s.version());
/// println!("Players  : {}", s.display_players());
/// println!("Latency  : {:.0} ms", s.latency_ms());
/// println!("MOTD     : {}", s.motd_clean());
/// println!("Cached   : {}", s.is_cached());
/// # Ok(()) }
/// ```
#[derive(Serialize)]
#[serde(transparent)]
pub struct JavaServerStatus(pub(crate) ServerStatus);

impl JavaServerStatus {
    /// Primary MOTD / description as returned by the server.
    ///
    /// May contain `§`-color codes. Use [`motd_clean`](Self::motd_clean) to
    /// strip them.  Returns `""` when the inner data is not Java (shouldn't
    /// happen in normal usage).
    pub fn motd(&self) -> &str {
        match &self.0.data {
            ServerData::Java(j) => &j.description,
            _ => "",
        }
    }

    /// Primary MOTD with all Minecraft `§`-formatting codes removed and
    /// whitespace normalised.
    ///
    /// Equivalent to [`strip_formatting`](crate::strip_formatting) applied to
    /// [`motd`](Self::motd).
    pub fn motd_clean(&self) -> String {
        strip_formatting(self.motd())
    }

    /// Server version name, e.g. `"1.21.4"` or `"Requires MC 1.8 / 1.21"`.
    pub fn version(&self) -> &str {
        match &self.0.data {
            ServerData::Java(j) => &j.version.name,
            _ => "",
        }
    }

    /// Number of players currently online as reported by the server.
    ///
    /// Large networks often report custom inflated numbers here.
    pub fn players_online(&self) -> i64 {
        match &self.0.data {
            ServerData::Java(j) => j.players.online,
            _ => 0,
        }
    }

    /// Maximum player capacity as reported by the server.
    pub fn players_max(&self) -> i64 {
        match &self.0.data {
            ServerData::Java(j) => j.players.max,
            _ => 0,
        }
    }

    /// Base64-encoded 64×64 PNG favicon, if the server provides one.
    ///
    /// The string may include a `data:image/png;base64,` prefix.
    /// Use [`save_favicon`](Self::save_favicon) to decode and write to disk.
    pub fn favicon(&self) -> Option<&str> {
        match &self.0.data {
            ServerData::Java(j) => j.favicon.as_deref(),
            _ => None,
        }
    }

    /// Decode the favicon and write it to `filename` as a PNG file.
    ///
    /// # Errors
    ///
    /// - [`McError::Protocol`] — no favicon available.
    /// - [`McError::Protocol`] — base64 decoding failed.
    /// - [`McError::Io`] — file could not be created or written.
    pub fn save_favicon(&self, filename: &str) -> Result<(), crate::McError> {
        match &self.0.data {
            ServerData::Java(j) => j.save_favicon(filename),
            _ => Err(crate::McError::invalid_response("not a java server")),
        }
    }

    /// `true` if this result was served from the response cache.
    ///
    /// Cached responses have `latency_ms() == 0.0` and reflect the server
    /// state at the time of the original ping, not the current moment.
    /// The maximum age is determined by the TTL passed to
    /// [`McClientBuilder::response_cache`](crate::McClientBuilder::response_cache).
    #[inline]
    pub fn is_cached(&self) -> bool {
        self.0.cached
    }

    /// Access the underlying [`ServerStatus`] directly.
    ///
    /// Use this to access fields not surfaced by the typed accessors, such as
    /// `dns`, `meta`, or to match on [`ServerData`](crate::ServerData) for
    /// the full [`JavaStatus`](crate::JavaStatus).
    pub fn raw(&self) -> &ServerStatus {
        &self.0
    }
}

impl StatusExt for JavaServerStatus {
    fn latency_ms(&self) -> f64  { self.0.latency }
    fn ip(&self) -> &str         { &self.0.ip }
    fn hostname(&self) -> &str   { &self.0.hostname }
    fn port(&self) -> u16        { self.0.port }
    fn is_online(&self) -> bool  { self.0.online }
    fn display_players(&self) -> String {
        format!("{}/{}", self.players_online(), self.players_max())
    }
}

impl fmt::Display for JavaServerStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f, "{} [{} ms] {}/{}{}",
            self.hostname(),
            self.latency_ms() as u32,
            self.players_online(),
            self.players_max(),
            self.motd_clean(),
        )
    }
}

impl fmt::Debug for JavaServerStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("JavaServerStatus")
            .field("hostname",   &self.hostname())
            .field("ip",         &self.ip())
            .field("port",       &self.port())
            .field("latency_ms", &self.latency_ms())
            .field("cached",     &self.is_cached())
            .field("version",    &self.version())
            .field("players",    &self.display_players())
            .field("motd",       &self.motd_clean())
            .finish()
    }
}