summaryrefslogtreecommitdiff
path: root/src/cli/status.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-21 20:19:54 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-21 20:22:57 +0300
commit866aaf740e10fcfce169aaf81380ea0257fbe272 (patch)
treef815392b7c6b24a7c0de14e655bd635656383813 /src/cli/status.rs
parentca579c402d5668560ca13059f82c03178be18b2a (diff)
refactor: separate server client boundaries
Diffstat (limited to 'src/cli/status.rs')
-rw-r--r--src/cli/status.rs299
1 files changed, 299 insertions, 0 deletions
diff --git a/src/cli/status.rs b/src/cli/status.rs
new file mode 100644
index 00000000..9b8a1648
--- /dev/null
+++ b/src/cli/status.rs
@@ -0,0 +1,299 @@
+use serde::Serialize;
+
+use crate::api;
+use crate::api::client::{ApiClient, ApiClientError};
+
+pub(super) fn run_status_command(args: &[String]) -> std::io::Result<i32> {
+ let Some((scope, json)) = parse_status_args(args) else {
+ return Ok(2);
+ };
+
+ match scope {
+ StatusScope::Full => print_full_status(json),
+ StatusScope::Server => print_server_status(json),
+ StatusScope::Client => {
+ print_client_status(json)?;
+ Ok(0)
+ }
+ StatusScope::Help => {
+ print_status_help();
+ Ok(0)
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum StatusScope {
+ Full,
+ Server,
+ Client,
+ Help,
+}
+
+fn parse_status_args(args: &[String]) -> Option<(StatusScope, bool)> {
+ match args.first().map(|arg| arg.as_str()) {
+ None => Some((StatusScope::Full, false)),
+ Some("--json") if args.len() == 1 => Some((StatusScope::Full, true)),
+ Some("server") => {
+ parse_status_scope_args(args, StatusScope::Server, "herdr status server [--json]")
+ }
+ Some("client") => {
+ parse_status_scope_args(args, StatusScope::Client, "herdr status client [--json]")
+ }
+ Some("help" | "--help" | "-h") => {
+ if args.len() > 1 {
+ print_status_help();
+ return None;
+ }
+ Some((StatusScope::Help, false))
+ }
+ Some(_) => {
+ print_status_help();
+ None
+ }
+ }
+}
+
+fn parse_status_scope_args(
+ args: &[String],
+ scope: StatusScope,
+ usage: &str,
+) -> Option<(StatusScope, bool)> {
+ match args.get(1).map(|arg| arg.as_str()) {
+ None => Some((scope, false)),
+ Some("--json") if args.len() == 2 => Some((scope, true)),
+ _ => {
+ eprintln!("usage: {usage}");
+ None
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+enum ServerRuntimeStatus {
+ Running {
+ version: Option<String>,
+ protocol: Option<u32>,
+ },
+ NotRunning,
+}
+
+fn print_full_status(json: bool) -> std::io::Result<i32> {
+ let server = read_server_runtime_status()?;
+
+ if json {
+ print_json(&FullStatusJson {
+ client: client_status_json(),
+ server: server_status_json(&server),
+ update: update_status_json(&server),
+ })?;
+ return Ok(0);
+ }
+
+ println!("client:");
+ println!(" version: {}", env!("CARGO_PKG_VERSION"));
+ println!(" protocol: {}", crate::protocol::PROTOCOL_VERSION);
+ println!();
+ println!("server:");
+ print_server_status_body(&server, " ");
+ println!();
+ println!("update:");
+ println!(" restart_needed: {}", restart_needed_label(&server));
+
+ Ok(0)
+}
+
+fn print_server_status(json: bool) -> std::io::Result<i32> {
+ let server = read_server_runtime_status()?;
+ if json {
+ print_json(&server_status_json(&server))?;
+ return Ok(0);
+ }
+ print_server_status_body(&server, "");
+ Ok(0)
+}
+
+fn print_client_status(json: bool) -> std::io::Result<()> {
+ if json {
+ print_json(&client_status_json())?;
+ return Ok(());
+ }
+
+ println!("version: {}", env!("CARGO_PKG_VERSION"));
+ println!("protocol: {}", crate::protocol::PROTOCOL_VERSION);
+ println!("binary: {}", current_exe_label());
+ Ok(())
+}
+
+fn print_server_status_body(server: &ServerRuntimeStatus, indent: &str) {
+ match server {
+ ServerRuntimeStatus::Running { version, protocol } => {
+ println!("{indent}status: running");
+ println!("{indent}version: {}", option_label(version.as_deref()));
+ println!("{indent}protocol: {}", protocol_label(*protocol));
+ println!("{indent}compatible: {}", compatibility_label(*protocol));
+ println!("{indent}socket: {}", api::socket_path().display());
+ }
+ ServerRuntimeStatus::NotRunning => {
+ println!("{indent}status: not running");
+ println!("{indent}socket: {}", api::socket_path().display());
+ }
+ }
+}
+
+fn read_server_runtime_status() -> std::io::Result<ServerRuntimeStatus> {
+ match ApiClient::local().status() {
+ Ok(status) => Ok(ServerRuntimeStatus::Running {
+ version: status.version,
+ protocol: status.protocol,
+ }),
+ Err(ApiClientError::Io(err)) if server_not_running_error(&err) => {
+ Ok(ServerRuntimeStatus::NotRunning)
+ }
+ Err(err) => Err(api_client_error_to_io(err)),
+ }
+}
+
+fn server_not_running_error(err: &std::io::Error) -> bool {
+ matches!(
+ err.kind(),
+ std::io::ErrorKind::NotFound | std::io::ErrorKind::ConnectionRefused
+ )
+}
+
+fn api_client_error_to_io(err: ApiClientError) -> std::io::Error {
+ match err {
+ ApiClientError::Io(err) => err,
+ err => std::io::Error::other(err),
+ }
+}
+
+fn option_label(value: Option<&str>) -> &str {
+ value.unwrap_or("unknown")
+}
+
+fn protocol_label(protocol: Option<u32>) -> String {
+ protocol
+ .map(|value| value.to_string())
+ .unwrap_or_else(|| "unknown".to_string())
+}
+
+fn compatibility_label(protocol: Option<u32>) -> &'static str {
+ match protocol {
+ Some(protocol) if protocol == crate::protocol::PROTOCOL_VERSION => "yes",
+ Some(_) => "no",
+ None => "unknown",
+ }
+}
+
+fn restart_needed_label(server: &ServerRuntimeStatus) -> &'static str {
+ match server {
+ ServerRuntimeStatus::Running { version, .. } => match version.as_deref() {
+ Some(env!("CARGO_PKG_VERSION")) => "no",
+ Some(_) => "yes",
+ None => "unknown",
+ },
+ ServerRuntimeStatus::NotRunning => "no",
+ }
+}
+
+#[derive(Serialize)]
+struct FullStatusJson {
+ client: ClientStatusJson,
+ server: ServerStatusJson,
+ update: UpdateStatusJson,
+}
+
+#[derive(Serialize)]
+struct ClientStatusJson {
+ version: &'static str,
+ protocol: u32,
+ binary: String,
+ session: Option<String>,
+}
+
+#[derive(Serialize)]
+struct ServerStatusJson {
+ status: &'static str,
+ running: bool,
+ version: Option<String>,
+ protocol: Option<u32>,
+ compatible: Option<bool>,
+ socket: String,
+ session: Option<String>,
+ restart_needed: Option<bool>,
+}
+
+#[derive(Serialize)]
+struct UpdateStatusJson {
+ restart_needed: Option<bool>,
+}
+
+fn client_status_json() -> ClientStatusJson {
+ ClientStatusJson {
+ version: env!("CARGO_PKG_VERSION"),
+ protocol: crate::protocol::PROTOCOL_VERSION,
+ binary: current_exe_label(),
+ session: crate::session::active_name(),
+ }
+}
+
+fn server_status_json(server: &ServerRuntimeStatus) -> ServerStatusJson {
+ match server {
+ ServerRuntimeStatus::Running { version, protocol } => ServerStatusJson {
+ status: "running",
+ running: true,
+ version: version.clone(),
+ protocol: *protocol,
+ compatible: protocol.map(|value| value == crate::protocol::PROTOCOL_VERSION),
+ socket: api::socket_path().display().to_string(),
+ session: crate::session::active_name(),
+ restart_needed: restart_needed_bool(server),
+ },
+ ServerRuntimeStatus::NotRunning => ServerStatusJson {
+ status: "not_running",
+ running: false,
+ version: None,
+ protocol: None,
+ compatible: None,
+ socket: api::socket_path().display().to_string(),
+ session: crate::session::active_name(),
+ restart_needed: Some(false),
+ },
+ }
+}
+
+fn update_status_json(server: &ServerRuntimeStatus) -> UpdateStatusJson {
+ UpdateStatusJson {
+ restart_needed: restart_needed_bool(server),
+ }
+}
+
+fn restart_needed_bool(server: &ServerRuntimeStatus) -> Option<bool> {
+ match server {
+ ServerRuntimeStatus::Running { version, .. } => match version.as_deref() {
+ Some(env!("CARGO_PKG_VERSION")) => Some(false),
+ Some(_) => Some(true),
+ None => None,
+ },
+ ServerRuntimeStatus::NotRunning => Some(false),
+ }
+}
+
+fn print_json(value: &impl Serialize) -> std::io::Result<()> {
+ println!("{}", serde_json::to_string(value)?);
+ Ok(())
+}
+
+fn current_exe_label() -> String {
+ std::env::current_exe()
+ .map(|path| path.display().to_string())
+ .unwrap_or_else(|err| format!("unknown ({err})"))
+}
+
+fn print_status_help() {
+ eprintln!("herdr status commands:");
+ eprintln!(" herdr status [--json] show local client and running server status");
+ eprintln!(" herdr status server [--json] show running server status");
+ eprintln!(" herdr status client [--json] show local client binary status");
+}