1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#[derive(thiserror::Error, Debug, Clone, PartialEq)]
pub enum ServeError {
// TODO stop using?
#[error("done")]
Done,
#[error("cancelled")]
Cancel,
#[error("closed, code={0}")]
Closed(u64),
#[error("not found")]
NotFound,
#[error("not found: {0} [error:{1}]")]
NotFoundWithId(String, uuid::Uuid),
#[error("duplicate")]
Duplicate,
#[error("multiple stream modes")]
Mode,
#[error("wrong size")]
Size,
#[error("internal error: {0}")]
Internal(String),
#[error("internal error: {0} [error:{1}]")]
InternalWithId(String, uuid::Uuid),
#[error("not implemented: {0}")]
NotImplemented(String),
#[error("not implemented: {0} [error:{1}]")]
NotImplementedWithId(String, uuid::Uuid),
}
impl ServeError {
/// Returns error codes for per-request/per-track errors.
/// These codes are used in SUBSCRIBE_ERROR, PUBLISH_DONE, FETCH_ERROR, etc.
/// Error codes are based on draft-ietf-moq-transport-14 Section 13.1.x
pub fn code(&self) -> u64 {
match self {
// Special case: 0 typically means successful completion or internal error depending on context
Self::Done => 0,
// Cancel/Going away - maps to various contexts
Self::Cancel => 1,
// Pass through application-specific error codes
Self::Closed(code) => *code,
// TRACK_DOES_NOT_EXIST (0x4) from SUBSCRIBE_ERROR codes
Self::NotFound | Self::NotFoundWithId(_, _) => 0x4,
// This is more of a session-level error, but keeping a reasonable code
Self::Duplicate => 0x5,
// NOT_SUPPORTED (0x3) - appears in multiple error code registries
Self::Mode => 0x3,
Self::Size => 0x3,
Self::NotImplemented(_) | Self::NotImplementedWithId(_, _) => 0x3,
// INTERNAL_ERROR (0x0) - per-request error registries use 0x0
Self::Internal(_) | Self::InternalWithId(_, _) => 0x0,
}
}
/// Create NotFound error with correlation ID but no additional context.
/// Uses generic messages for both logging and wire protocol.
///
/// Example: `ServeError::not_found_id()`
#[track_caller]
pub fn not_found_id() -> Self {
let id = uuid::Uuid::new_v4();
let loc = std::panic::Location::caller();
log::warn!("[{}] Not found at {}:{}", id, loc.file(), loc.line());
Self::NotFoundWithId("Track not found".to_string(), id)
}
/// Create NotFound error with correlation ID and internal context.
/// The internal context is logged but a generic message is sent on the wire.
///
/// Example: `ServeError::not_found_ctx("subscribe_id=123 not in map")`
#[track_caller]
pub fn not_found_ctx(internal_context: impl Into<String>) -> Self {
let context = internal_context.into();
let id = uuid::Uuid::new_v4();
let loc = std::panic::Location::caller();
log::warn!(
"[{}] Not found: {} at {}:{}",
id,
context,
loc.file(),
loc.line()
);
Self::NotFoundWithId("Track not found".to_string(), id)
}
/// Create NotFound error with full control over internal and external messages.
/// The internal context is logged, and the external message is sent on the wire.
///
/// Example: `ServeError::not_found_full("subscribe_id=123 not in map", "Subscription expired")`
#[track_caller]
pub fn not_found_full(
internal_context: impl Into<String>,
external_message: impl Into<String>,
) -> Self {
let context = internal_context.into();
let message = external_message.into();
let id = uuid::Uuid::new_v4();
let loc = std::panic::Location::caller();
log::warn!(
"[{}] Not found: {} at {}:{}",
id,
context,
loc.file(),
loc.line()
);
Self::NotFoundWithId(message, id)
}
/// Create Internal error with correlation ID and internal context.
/// The internal context is logged but a generic message is sent on the wire.
///
/// Example: `ServeError::internal_ctx("subscriber map in bad state")`
#[track_caller]
pub fn internal_ctx(internal_context: impl Into<String>) -> Self {
let context = internal_context.into();
let id = uuid::Uuid::new_v4();
let loc = std::panic::Location::caller();
log::error!(
"[{}] Internal error: {} at {}:{}",
id,
context,
loc.file(),
loc.line()
);
Self::InternalWithId("Internal error".to_string(), id)
}
/// Create NotImplemented error with correlation ID and feature context.
/// The feature name is logged but a generic message is sent on the wire.
///
/// Example: `ServeError::not_implemented_ctx("datagrams")`
#[track_caller]
pub fn not_implemented_ctx(feature: impl Into<String>) -> Self {
let feature = feature.into();
let id = uuid::Uuid::new_v4();
let loc = std::panic::Location::caller();
log::warn!(
"[{}] Not implemented: {} at {}:{}",
id,
feature,
loc.file(),
loc.line()
);
Self::NotImplementedWithId("Feature not implemented".to_string(), id)
}
}