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