Skip to content

Commit 502bc58

Browse files
authored
Add typed WebSocket handler errors and public error metadata (#33)
1 parent cf04fd1 commit 502bc58

12 files changed

Lines changed: 514 additions & 35 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ ws-wtx-http2 = ["ws-wtx", "wtx/http2"]
114114
serde = { version = "1.0", features = ["derive"] }
115115
serde_json = { version = "1.0", features = ["raw_value"] }
116116
eyre = "0.6"
117+
derive_more = { version = "2.1.1", features = ["display"] }
117118
convert_case = "0.6"
118119
itertools = "0.12"
119120
clap = { version = "4.5", features = ["derive", "env"] }

examples/test_ws_echo.sh

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ declare -a FULL_DESC=(
3131
"ReceiveUserInfo: with appPubId"
3232
"ReceiveUserInfo: with token"
3333
"ReceiveUserInfo: with appPubId and token"
34-
"ReceiveUserInfo: error code is 100400 (BAD_REQUEST)"
35-
"ReceiveUserInfo: username appears in error message"
34+
"ReceiveUserInfo: code and kind match"
35+
"ReceiveUserInfo: fixed message"
3636
"Error: unknown method returns error"
3737
"Error: missing params field (no response expected)"
3838
"Error: wrong params type"
@@ -63,12 +63,12 @@ declare -a FULL_ASSERT=(
6363
'.type == "Immediate"'
6464
'.params.message == "echo: "'
6565
'.params.message == "echo: hello world! @#$%"'
66-
'.type == "Error" and (.params | test("Test passed"))'
67-
'.type == "Error" and (.params | test("Test passed"))'
68-
'.type == "Error" and (.params | test("Test passed"))'
69-
'.type == "Error" and (.params | test("Test passed"))'
70-
'.code == 100400'
71-
'.params | test("bob")'
66+
'.type == "Error" and .kind == "BadRequest"'
67+
'.type == "Error" and .kind == "BadRequest"'
68+
'.type == "Error" and .kind == "BadRequest"'
69+
'.type == "Error" and .kind == "BadRequest"'
70+
'.code == 100400 and .kind == "BadRequest"'
71+
'.params == "ReceiveUserInfo is not processed by this test server"'
7272
'.type == "Error"'
7373
'.type == "Error"'
7474
'.type == "Error"'

examples/ws-echo/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Messages follow the library's JSON envelope format:
2828
{"method": 211, "seq": 2, "params": {"userPubId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "username": "alice"}}
2929

3030
// server → client (always an error — test server)
31-
{"type":"Error","method":211,"seq":2,"params":"Test passed: received ReceiveUserInfo for user 'alice' (id: a1b2c3d4-e5f6-7890-abcd-ef1234567890) — this is a test server and will not process the request","error_code":{"code":100400},"log_id":"..."}
31+
{"type":"Error","method":211,"code":100400,"kind":"BadRequest","seq":2,"log_id":"...","params":"ReceiveUserInfo is not processed by this test server"}
3232
```
3333

3434
The optional fields `appPubId` and `token` can also be included:
@@ -76,7 +76,7 @@ With optional fields:
7676
Expected response (error confirming the test passed):
7777

7878
```
79-
{"type":"Error","method":211,"seq":2,"params":"Test passed: received ReceiveUserInfo for user 'alice' (id: a1b2c3d4-e5f6-7890-abcd-ef1234567890) — this is a test server and will not process the request","error_code":{"code":100400},"log_id":"..."}
79+
{"type":"Error","method":211,"code":100400,"kind":"BadRequest","seq":2,"log_id":"...","params":"ReceiveUserInfo is not processed by this test server"}
8080
```
8181

8282
## Run with Cloudflare Containers (wrangler dev)
@@ -115,5 +115,5 @@ Or call `ReceiveUserInfo`:
115115
Expected response:
116116

117117
```
118-
{"type":"Error","method":211,"seq":2,"params":"Test passed: received ReceiveUserInfo for user 'alice' (id: a1b2c3d4-e5f6-7890-abcd-ef1234567890) — this is a test server and will not process the request","error_code":{"code":100400},"log_id":"..."}
118+
{"type":"Error","method":211,"code":100400,"kind":"BadRequest","seq":2,"log_id":"...","params":"ReceiveUserInfo is not processed by this test server"}
119119
```

examples/ws-echo/main.rs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::sync::Arc;
22

33
use async_trait::async_trait;
4+
use derive_more::Display;
45
use endpoint_libs::libs::error_code::ErrorCode;
5-
use endpoint_libs::libs::handler::RequestHandler;
6+
use endpoint_libs::libs::handler::{RequestHandler, Response};
67
#[cfg(feature = "error_aggregation")]
78
use endpoint_libs::libs::log::error_aggregation::ErrorAggregationConfig;
89
use endpoint_libs::libs::log::{LogLevel, LoggingConfig, OtelConfig, setup_logging};
@@ -95,8 +96,9 @@ pub struct MethodEcho;
9596
#[async_trait(?Send)]
9697
impl RequestHandler for MethodEcho {
9798
type Request = EchoRequest;
99+
type Error = CustomError;
98100

99-
async fn handle(&self, ctx: RequestContext, req: EchoRequest) -> Result<EchoResponse> {
101+
async fn handle(&self, ctx: RequestContext, req: EchoRequest) -> Response<EchoRequest> {
100102
tracing::info!(
101103
conn_id = %ctx.connection_id,
102104
message = %req.message,
@@ -116,15 +118,32 @@ impl RequestHandler for MethodEcho {
116118

117119
pub struct MethodReceiveUserInfo;
118120

121+
#[derive(Debug, Display)]
122+
pub enum ReceiveUserInfoError {
123+
#[display("ReceiveUserInfo is not processed by this test server")]
124+
Rejected,
125+
}
126+
127+
impl From<ReceiveUserInfoError> for CustomError {
128+
fn from(err: ReceiveUserInfoError) -> Self {
129+
match err {
130+
ReceiveUserInfoError::Rejected => {
131+
CustomError::new(ErrorCode::BAD_REQUEST, err.to_string())
132+
}
133+
}
134+
}
135+
}
136+
119137
#[async_trait(?Send)]
120138
impl RequestHandler for MethodReceiveUserInfo {
121139
type Request = HoneyReceiveUserInfoRequest;
140+
type Error = ReceiveUserInfoError;
122141

123142
async fn handle(
124143
&self,
125144
ctx: RequestContext,
126145
req: HoneyReceiveUserInfoRequest,
127-
) -> Result<HoneyReceiveUserInfoResponse> {
146+
) -> Response<HoneyReceiveUserInfoRequest, ReceiveUserInfoError> {
128147
tracing::info!(
129148
conn_id = %ctx.connection_id,
130149
user_pub_id = %req.user_pub_id,
@@ -133,21 +152,12 @@ impl RequestHandler for MethodReceiveUserInfo {
133152
has_token = req.token.is_some(),
134153
"ReceiveUserInfo request received (test server — will reject)"
135154
);
136-
let msg = format!(
137-
"Test passed: received ReceiveUserInfo for user '{}' (id: {}){} — \
138-
this is a test server and will not process the request",
139-
req.username,
140-
req.user_pub_id,
141-
req.app_pub_id
142-
.map(|id| format!(", app: {id}"))
143-
.unwrap_or_default(),
144-
);
145155
tracing::info!(
146156
conn_id = %ctx.connection_id,
147157
user_pub_id = %req.user_pub_id,
148158
"Rejecting ReceiveUserInfo with BAD_REQUEST"
149159
);
150-
Err(CustomError::new(ErrorCode::BAD_REQUEST, msg).into())
160+
Err(ReceiveUserInfoError::Rejected.into())
151161
}
152162
}
153163

0 commit comments

Comments
 (0)