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