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}