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