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