aboutsummaryrefslogtreecommitdiff
path: root/src/cli/server_not_running.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cli/server_not_running.rs')
-rw-r--r--src/cli/server_not_running.rs74
1 files changed, 74 insertions, 0 deletions
diff --git a/src/cli/server_not_running.rs b/src/cli/server_not_running.rs
new file mode 100644
index 00000000..9e974f3c
--- /dev/null
+++ b/src/cli/server_not_running.rs
@@ -0,0 +1,74 @@
+use std::fmt;
+use std::path::Path;
+
+use crate::api::schema::{ErrorBody, ErrorResponse};
+
+/// Marker error signalling a dead API socket. Carries the `ErrorResponse` that
+/// should be printed at the edge that finally surfaces the error, so callers
+/// that recover (e.g. plugin offline fallback) print nothing. Mirrors
+/// `ProtocolMismatchReported`, except printing is deferred because several CLI
+/// commands recover from a dead server instead of reporting it.
+#[derive(Debug)]
+pub(super) struct ServerNotRunningReported {
+ pub(super) response: ErrorResponse,
+}
+
+impl fmt::Display for ServerNotRunningReported {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ // Delegate to the carried message so pre-existing paths that
+ // stringify transport errors still show the actionable text.
+ f.write_str(&self.response.error.message)
+ }
+}
+
+impl std::error::Error for ServerNotRunningReported {}
+
+/// Builds the friendly `server_not_running` ErrorResponse shown when no
+/// server is listening on the resolved API socket.
+pub(super) fn response(request_id: &str, socket_path: &Path) -> ErrorResponse {
+ let attach_command = startup_command(socket_path);
+ ErrorResponse {
+ id: request_id.to_string(),
+ error: ErrorBody {
+ code: "server_not_running".into(),
+ message: format!(
+ "no herdr server is running at {}; run `{attach_command}` to start or attach it",
+ socket_path.display()
+ ),
+ },
+ }
+}
+
+fn startup_command(socket_path: &Path) -> String {
+ let session_socket =
+ crate::session::api_socket_path_for(crate::session::active_name().as_deref());
+ if socket_path == session_socket {
+ crate::session::local_attach_command()
+ } else {
+ // A socket override wins over an inherited HERDR_SESSION. Keep the
+ // command in the current environment so it starts the overridden
+ // target instead of directing the user to an unrelated session.
+ "herdr".to_string()
+ }
+}
+
+/// Wraps the response in the recognizable marker WITHOUT printing. The caller
+/// that ultimately surfaces the error prints the carried response (see
+/// `reported_response`); recovering callers simply drop it.
+pub(super) fn reported_error(response: ErrorResponse) -> std::io::Error {
+ std::io::Error::other(ServerNotRunningReported { response })
+}
+
+pub(super) fn was_reported(err: &std::io::Error) -> bool {
+ err.get_ref()
+ .and_then(|source| source.downcast_ref::<ServerNotRunningReported>())
+ .is_some()
+}
+
+/// Returns the `ErrorResponse` carried by a `server_not_running` marker, if any,
+/// so the surfacing edge can print it exactly once.
+pub(super) fn reported_response(err: &std::io::Error) -> Option<&ErrorResponse> {
+ err.get_ref()
+ .and_then(|source| source.downcast_ref::<ServerNotRunningReported>())
+ .map(|reported| &reported.response)
+}