Skip to main content

rust_mc_status/status/
java.rs

1//! [`JavaServerStatus`] — typed wrapper around a Java Edition ping result.
2//!
3//! Returned by [`McClient::java`](crate::McClient::java) and
4//! [`ping_java`](crate::ping_java). Wraps a [`ServerStatus`] and exposes
5//! Java-specific accessors so callers don't need to match on [`ServerData`].
6
7use std::fmt;
8
9use serde::Serialize;
10
11use crate::models::{ServerStatus, ServerData};
12use crate::core::fmt::strip_formatting;
13use super::ext::StatusExt;
14
15/// Typed wrapper around a Java Edition [`ServerStatus`].
16///
17/// Implements [`StatusExt`] for common fields and [`serde::Serialize`] via
18/// `#[serde(transparent)]` — serialises identically to the inner
19/// [`ServerStatus`].
20///
21/// # Accessing raw fields
22///
23/// Use `.raw()` to get the underlying [`ServerStatus`] directly, or match on
24/// [`ServerData::Java`](crate::ServerData::Java) for the raw [`JavaStatus`](crate::JavaStatus).
25///
26/// # Example
27///
28/// ```rust,no_run
29/// use rust_mc_status::{McClient, StatusExt};
30///
31/// # #[tokio::main] async fn main() -> Result<(), rust_mc_status::McError> {
32/// let client = McClient::builder().build();
33/// let s = client.java("mc.hypixel.net").await?;
34///
35/// println!("Version  : {}", s.version());
36/// println!("Players  : {}", s.display_players());
37/// println!("Latency  : {:.0} ms", s.latency_ms());
38/// println!("MOTD     : {}", s.motd_clean());
39/// println!("Cached   : {}", s.is_cached());
40/// # Ok(()) }
41/// ```
42#[derive(Serialize)]
43#[serde(transparent)]
44pub struct JavaServerStatus(pub(crate) ServerStatus);
45
46impl JavaServerStatus {
47    /// Primary MOTD / description as returned by the server.
48    ///
49    /// May contain `§`-color codes. Use [`motd_clean`](Self::motd_clean) to
50    /// strip them.  Returns `""` when the inner data is not Java (shouldn't
51    /// happen in normal usage).
52    pub fn motd(&self) -> &str {
53        match &self.0.data {
54            ServerData::Java(j) => &j.description,
55            _ => "",
56        }
57    }
58
59    /// Primary MOTD with all Minecraft `§`-formatting codes removed and
60    /// whitespace normalised.
61    ///
62    /// Equivalent to [`strip_formatting`](crate::strip_formatting) applied to
63    /// [`motd`](Self::motd).
64    pub fn motd_clean(&self) -> String {
65        strip_formatting(self.motd())
66    }
67
68    /// Server version name, e.g. `"1.21.4"` or `"Requires MC 1.8 / 1.21"`.
69    pub fn version(&self) -> &str {
70        match &self.0.data {
71            ServerData::Java(j) => &j.version.name,
72            _ => "",
73        }
74    }
75
76    /// Number of players currently online as reported by the server.
77    ///
78    /// Large networks often report custom inflated numbers here.
79    pub fn players_online(&self) -> i64 {
80        match &self.0.data {
81            ServerData::Java(j) => j.players.online,
82            _ => 0,
83        }
84    }
85
86    /// Maximum player capacity as reported by the server.
87    pub fn players_max(&self) -> i64 {
88        match &self.0.data {
89            ServerData::Java(j) => j.players.max,
90            _ => 0,
91        }
92    }
93
94    /// Base64-encoded 64×64 PNG favicon, if the server provides one.
95    ///
96    /// The string may include a `data:image/png;base64,` prefix.
97    /// Use [`save_favicon`](Self::save_favicon) to decode and write to disk.
98    pub fn favicon(&self) -> Option<&str> {
99        match &self.0.data {
100            ServerData::Java(j) => j.favicon.as_deref(),
101            _ => None,
102        }
103    }
104
105    /// Decode the favicon and write it to `filename` as a PNG file.
106    ///
107    /// # Errors
108    ///
109    /// - [`McError::Protocol`] — no favicon available.
110    /// - [`McError::Protocol`] — base64 decoding failed.
111    /// - [`McError::Io`] — file could not be created or written.
112    pub fn save_favicon(&self, filename: &str) -> Result<(), crate::McError> {
113        match &self.0.data {
114            ServerData::Java(j) => j.save_favicon(filename),
115            _ => Err(crate::McError::invalid_response("not a java server")),
116        }
117    }
118
119    /// `true` if this result was served from the response cache.
120    ///
121    /// Cached responses have `latency_ms() == 0.0` and reflect the server
122    /// state at the time of the original ping, not the current moment.
123    /// The maximum age is determined by the TTL passed to
124    /// [`McClientBuilder::response_cache`](crate::McClientBuilder::response_cache).
125    #[inline]
126    pub fn is_cached(&self) -> bool {
127        self.0.cached
128    }
129
130    /// Access the underlying [`ServerStatus`] directly.
131    ///
132    /// Use this to access fields not surfaced by the typed accessors, such as
133    /// `dns`, `meta`, or to match on [`ServerData`](crate::ServerData) for
134    /// the full [`JavaStatus`](crate::JavaStatus).
135    pub fn raw(&self) -> &ServerStatus {
136        &self.0
137    }
138}
139
140impl StatusExt for JavaServerStatus {
141    fn latency_ms(&self) -> f64  { self.0.latency }
142    fn ip(&self) -> &str         { &self.0.ip }
143    fn hostname(&self) -> &str   { &self.0.hostname }
144    fn port(&self) -> u16        { self.0.port }
145    fn is_online(&self) -> bool  { self.0.online }
146    fn display_players(&self) -> String {
147        format!("{}/{}", self.players_online(), self.players_max())
148    }
149}
150
151impl fmt::Display for JavaServerStatus {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        write!(
154            f, "{} [{} ms] {}/{} — {}",
155            self.hostname(),
156            self.latency_ms() as u32,
157            self.players_online(),
158            self.players_max(),
159            self.motd_clean(),
160        )
161    }
162}
163
164impl fmt::Debug for JavaServerStatus {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        f.debug_struct("JavaServerStatus")
167            .field("hostname",   &self.hostname())
168            .field("ip",         &self.ip())
169            .field("port",       &self.port())
170            .field("latency_ms", &self.latency_ms())
171            .field("cached",     &self.is_cached())
172            .field("version",    &self.version())
173            .field("players",    &self.display_players())
174            .field("motd",       &self.motd_clean())
175            .finish()
176    }
177}