1#![deny(missing_docs)]
2
3#[cfg(unix)]
12pub mod attach;
13#[cfg(windows)]
14#[path = "attach_windows.rs"]
15pub mod attach;
16pub mod auto_start;
17pub(crate) mod commands;
18pub mod connection;
19pub mod control;
20pub mod nested;
21
22pub use attach::{
23 attach_terminal, attach_terminal_with_initial_bytes, attach_with_terminal, drive_attach_stream,
24 AttachError, RawTerminal,
25};
26pub use auto_start::{
27 ensure_server_running, ensure_server_running_with_config, AutoStartConfig,
28 AutoStartConfigSelection, AutoStartError, INTERNAL_DAEMON_FLAG,
29};
30pub use commands::server::StartServerError;
31pub use commands::window::SplitWindowOptions;
32pub use connection::{
33 connect, connect_or_absent, default_socket_path, resolve_socket_path, socket_path_for_label,
34 AttachSessionUpgrade, AttachTransition, ConnectResult, Connection, ControlModeUpgrade,
35 ControlTransition,
36};
37pub use control::{drive_control_mode, drive_control_mode_with_stdio};
38pub use nested::{
39 detect_context, ensure_nested_context, require_nested_context, ClientContext,
40 NestedContextError,
41};
42
43use rmux_proto::RmuxError;
44use std::fmt;
45
46#[derive(Debug)]
48pub enum ClientError {
49 Io(std::io::Error),
51 Protocol(RmuxError),
53 Attach(AttachError),
55 UnexpectedEof,
57}
58
59impl fmt::Display for ClientError {
60 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 Self::Io(error) => write!(formatter, "i/o error: {error}"),
63 Self::Protocol(error) => write!(formatter, "protocol error: {error}"),
64 Self::Attach(error) => write!(formatter, "attach error: {error}"),
65 Self::UnexpectedEof => formatter
66 .write_str("server closed connection before a complete response frame arrived"),
67 }
68 }
69}
70
71impl std::error::Error for ClientError {
72 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
73 match self {
74 Self::Io(error) => Some(error),
75 Self::Protocol(error) => Some(error),
76 Self::Attach(error) => Some(error),
77 Self::UnexpectedEof => None,
78 }
79 }
80}
81
82impl From<std::io::Error> for ClientError {
83 fn from(error: std::io::Error) -> Self {
84 Self::Io(error)
85 }
86}
87
88impl From<RmuxError> for ClientError {
89 fn from(error: RmuxError) -> Self {
90 Self::Protocol(error)
91 }
92}
93
94impl From<AttachError> for ClientError {
95 fn from(error: AttachError) -> Self {
96 Self::Attach(error)
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use std::error::Error as _;
103 use std::io;
104
105 use super::{AttachError, ClientError};
106
107 #[test]
108 fn client_error_wraps_attach_errors() {
109 let error = ClientError::from(AttachError::Io(io::Error::other("dup failed")));
110
111 assert!(
112 matches!(error, ClientError::Attach(AttachError::Io(_))),
113 "attach errors should preserve their variant information"
114 );
115 assert_eq!(
116 error.to_string(),
117 expected_attach_error_display("dup failed")
118 );
119 assert!(
120 error.source().is_some(),
121 "wrapped attach error should chain"
122 );
123 }
124
125 #[cfg(unix)]
126 fn expected_attach_error_display(message: &str) -> String {
127 format!("attach error: terminal descriptor operation failed: {message}")
128 }
129
130 #[cfg(windows)]
131 fn expected_attach_error_display(message: &str) -> String {
132 format!("attach error: terminal console operation failed: {message}")
133 }
134
135 #[cfg(not(any(unix, windows)))]
136 fn expected_attach_error_display(message: &str) -> String {
137 format!("attach error: terminal descriptor operation failed: {message}")
138 }
139}