1use quic_reverse_control::RejectCode;
18use thiserror::Error;
19
20#[derive(Debug, Error)]
22pub enum Error {
23 #[error("negotiation failed: {0}")]
25 NegotiationFailed(#[from] NegotiationError),
26
27 #[error("protocol violation: {0}")]
29 ProtocolViolation(String),
30
31 #[error("timeout: {0}")]
33 Timeout(TimeoutKind),
34
35 #[error("transport error: {0}")]
37 Transport(#[source] Box<dyn std::error::Error + Send + Sync>),
38
39 #[error("stream rejected: {code}{}", reason.as_ref().map(|r| format!(": {r}")).unwrap_or_default())]
41 StreamRejected {
42 code: RejectCode,
44 reason: Option<String>,
46 },
47
48 #[error("session closed")]
50 SessionClosed,
51
52 #[error("disconnected")]
54 Disconnected,
55
56 #[error("capacity limit reached: {0}")]
58 CapacityExceeded(&'static str),
59
60 #[error("configuration error: {0}")]
62 Config(#[from] crate::config::ConfigError),
63
64 #[error("control protocol error: {0}")]
66 Control(#[from] quic_reverse_control::ControlError),
67
68 #[error("codec error: {0}")]
70 Codec(#[from] quic_reverse_control::CodecError),
71}
72
73impl Error {
74 pub fn transport<E>(error: E) -> Self
76 where
77 E: std::error::Error + Send + Sync + 'static,
78 {
79 Self::Transport(Box::new(error))
80 }
81
82 pub fn protocol_violation(message: impl Into<String>) -> Self {
84 Self::ProtocolViolation(message.into())
85 }
86
87 #[must_use]
89 pub const fn stream_rejected(code: RejectCode, reason: Option<String>) -> Self {
90 Self::StreamRejected { code, reason }
91 }
92}
93
94#[derive(Debug, Error)]
96pub enum NegotiationError {
97 #[error("no compatible protocol version: local supports {local:?}, remote supports {remote}")]
99 VersionMismatch {
100 local: Vec<u16>,
102 remote: u16,
104 },
105
106 #[error("required feature not supported: {0}")]
108 MissingFeature(String),
109
110 #[error("unexpected message during negotiation")]
112 UnexpectedMessage,
113
114 #[error("negotiation timed out")]
116 Timeout,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum TimeoutKind {
122 Negotiation,
124 OpenRequest,
126 StreamBind,
128 Ping,
130}
131
132impl std::fmt::Display for TimeoutKind {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 match self {
135 Self::Negotiation => write!(f, "negotiation"),
136 Self::OpenRequest => write!(f, "open request"),
137 Self::StreamBind => write!(f, "stream bind"),
138 Self::Ping => write!(f, "ping"),
139 }
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn error_display() {
149 let err = Error::stream_rejected(RejectCode::Unauthorized, Some("denied".into()));
150 assert!(err.to_string().contains("unauthorized"));
151 assert!(err.to_string().contains("denied"));
152 }
153
154 #[test]
155 fn timeout_kind_display() {
156 assert_eq!(TimeoutKind::Negotiation.to_string(), "negotiation");
157 assert_eq!(TimeoutKind::OpenRequest.to_string(), "open request");
158 }
159
160 #[test]
161 fn negotiation_error_display() {
162 let err = NegotiationError::VersionMismatch {
163 local: vec![1, 2],
164 remote: 3,
165 };
166 let msg = err.to_string();
167 assert!(msg.contains("[1, 2]"));
168 assert!(msg.contains("3"));
169 }
170}