aboutsummaryrefslogtreecommitdiff
path: root/src/cli
diff options
context:
space:
mode:
Diffstat (limited to 'src/cli')
-rw-r--r--src/cli/plugin.rs3
-rw-r--r--src/cli/protocol_guard.rs110
-rw-r--r--src/cli/server.rs4
3 files changed, 116 insertions, 1 deletions
diff --git a/src/cli/plugin.rs b/src/cli/plugin.rs
index 5cc528f4..68cf0282 100644
--- a/src/cli/plugin.rs
+++ b/src/cli/plugin.rs
@@ -932,6 +932,9 @@ fn register_installed_plugin(
),
)));
}
+ Err(unlink_err) if super::protocol_mismatch_was_reported(&unlink_err) => {
+ return Err(InstallFailure::KeepCheckout(unlink_err));
+ }
Err(unlink_err) => {
return Err(InstallFailure::KeepCheckout(std::io::Error::other(
format!(
diff --git a/src/cli/protocol_guard.rs b/src/cli/protocol_guard.rs
new file mode 100644
index 00000000..21302bbf
--- /dev/null
+++ b/src/cli/protocol_guard.rs
@@ -0,0 +1,110 @@
+use std::fmt;
+
+use crate::api::schema::{ErrorBody, ErrorResponse};
+
+#[derive(Debug)]
+pub(super) struct ProtocolMismatchReported;
+
+impl fmt::Display for ProtocolMismatchReported {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("protocol mismatch was already reported")
+ }
+}
+
+impl std::error::Error for ProtocolMismatchReported {}
+
+pub(super) fn mismatch_response(
+ request_id: &str,
+ server_protocol: u32,
+ restart_guidance: &str,
+) -> Option<ErrorResponse> {
+ let client_protocol = crate::protocol::PROTOCOL_VERSION;
+ if client_protocol == server_protocol {
+ return None;
+ }
+
+ let message = if client_protocol > server_protocol {
+ format!(
+ "client protocol {client_protocol} is newer than server protocol {server_protocol}; restart the Herdr server before using this command. {restart_guidance}"
+ )
+ } else {
+ format!(
+ "client protocol {client_protocol} is older than server protocol {server_protocol}; upgrade the Herdr client before using this command"
+ )
+ };
+
+ Some(ErrorResponse {
+ id: request_id.to_string(),
+ error: ErrorBody {
+ code: "protocol_mismatch".into(),
+ message,
+ },
+ })
+}
+
+pub(super) fn reported_error() -> std::io::Error {
+ std::io::Error::other(ProtocolMismatchReported)
+}
+
+pub(super) fn was_reported(err: &std::io::Error) -> bool {
+ err.get_ref()
+ .and_then(|source| source.downcast_ref::<ProtocolMismatchReported>())
+ .is_some()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn matching_protocol_has_no_error() {
+ assert!(mismatch_response("req", crate::protocol::PROTOCOL_VERSION, "restart").is_none());
+ }
+
+ #[test]
+ fn older_server_error_preserves_request_id_and_guidance() {
+ let response = mismatch_response(
+ "cli:wait:agent-status",
+ crate::protocol::PROTOCOL_VERSION - 1,
+ "Run the session stop command, then restart.",
+ )
+ .unwrap();
+
+ assert_eq!(response.id, "cli:wait:agent-status");
+ assert_eq!(response.error.code, "protocol_mismatch");
+ assert!(response.error.message.contains(&format!(
+ "client protocol {}",
+ crate::protocol::PROTOCOL_VERSION
+ )));
+ assert!(response.error.message.contains(&format!(
+ "server protocol {}",
+ crate::protocol::PROTOCOL_VERSION - 1
+ )));
+ assert!(response.error.message.contains("restart"));
+ }
+
+ #[test]
+ fn newer_server_error_tells_user_to_upgrade_client() {
+ let response = mismatch_response(
+ "cli:pane:list",
+ crate::protocol::PROTOCOL_VERSION + 1,
+ "unused restart guidance",
+ )
+ .unwrap();
+
+ assert!(response
+ .error
+ .message
+ .contains("older than server protocol"));
+ assert!(response.error.message.contains("upgrade the Herdr client"));
+ assert!(!response.error.message.contains("unused restart guidance"));
+ }
+
+ #[test]
+ fn reported_error_is_recognizable_without_string_matching() {
+ assert!(was_reported(&reported_error()));
+ assert!(!was_reported(&std::io::Error::other(
+ "protocol mismatch was already reported"
+ )));
+ }
+}
diff --git a/src/cli/server.rs b/src/cli/server.rs
index 29775999..ff87cab1 100644
--- a/src/cli/server.rs
+++ b/src/cli/server.rs
@@ -201,7 +201,9 @@ fn server_live_handoff(args: &[String]) -> std::io::Result<i32> {
return Ok(2);
};
- let response = super::send_request(&Request {
+ // Live handoff is itself a protocol-mismatch recovery path, so it must
+ // reach the running server without the normal CLI compatibility guard.
+ let response = super::send_request_unchecked(&Request {
id: "cli:server:live-handoff".into(),
method: Method::ServerLiveHandoff(params),
})?;