Skip to content

Commit 7ee6720

Browse files
committed
chore(guard): simplify gateway routing
1 parent d9f9f0c commit 7ee6720

File tree

7 files changed

+155
-224
lines changed

7 files changed

+155
-224
lines changed

engine/packages/guard/src/routing/mod.rs

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ pub fn create_routing_function(ctx: StandaloneCtx, shared_state: SharedState) ->
142142

143143
/// Parse actor routing information from path
144144
/// Matches patterns:
145-
/// - /gateway/actors/{actor_id}/tokens/{token}/route/{...path}
146-
/// - /gateway/actors/{actor_id}/route/{...path}
145+
/// - /gateway/{actor_id}/{...path}
146+
/// - /gateway/{actor_id}@{token}/{...path}
147147
pub fn parse_actor_path(path: &str) -> Option<ActorPathInfo> {
148148
// Find query string position (everything from ? onwards, but before fragment)
149149
let query_pos = path.find('?');
@@ -173,47 +173,41 @@ pub fn parse_actor_path(path: &str) -> Option<ActorPathInfo> {
173173
// Split the path into segments
174174
let segments: Vec<&str> = base_path.split('/').filter(|s| !s.is_empty()).collect();
175175

176-
// Check minimum required segments: gateway, actors, {actor_id}, route
177-
if segments.len() < 4 {
176+
// Check minimum required segments: gateway, {actor_id}
177+
if segments.len() < 2 {
178178
return None;
179179
}
180180

181-
// Verify the fixed segments
182-
if segments[0] != "gateway" || segments[1] != "actors" {
181+
// Verify the fixed segment
182+
if segments[0] != "gateway" {
183183
return None;
184184
}
185185

186-
// Check for empty actor_id
187-
if segments[2].is_empty() {
186+
// Check for empty actor_id segment
187+
if segments[1].is_empty() {
188188
return None;
189189
}
190190

191-
let actor_id = segments[2].to_string();
192-
193-
// Check for token or direct route
194-
let (token, remaining_path_start_idx) =
195-
if segments.len() >= 6 && segments[3] == "tokens" && segments[5] == "route" {
196-
// Pattern with token: /gateway/actors/{actor_id}/tokens/{token}/route/{...path}
197-
// Check for empty token
198-
if segments[4].is_empty() {
199-
return None;
200-
}
201-
(Some(segments[4].to_string()), 6)
202-
} else if segments.len() >= 4 && segments[3] == "route" {
203-
// Pattern without token: /gateway/actors/{actor_id}/route/{...path}
204-
(None, 4)
205-
} else {
191+
// Parse actor_id and optional token from second segment
192+
// Pattern: {actor_id}@{token} or just {actor_id}
193+
let actor_id_segment = segments[1];
194+
let (actor_id, token) = if let Some(at_pos) = actor_id_segment.find('@') {
195+
let aid = &actor_id_segment[..at_pos];
196+
let tok = &actor_id_segment[at_pos + 1..];
197+
198+
// Check for empty actor_id or token
199+
if aid.is_empty() || tok.is_empty() {
206200
return None;
207-
};
201+
}
202+
203+
(aid.to_string(), Some(tok.to_string()))
204+
} else {
205+
(actor_id_segment.to_string(), None)
206+
};
208207

209208
// Calculate the position in the original path where remaining path starts
210-
let mut prefix_len = 0;
211-
for (i, segment) in segments.iter().enumerate() {
212-
if i >= remaining_path_start_idx {
213-
break;
214-
}
215-
prefix_len += 1 + segment.len(); // +1 for the slash
216-
}
209+
// We need to skip "/gateway/{actor_id_segment}"
210+
let prefix_len = 1 + segments[0].len() + 1 + segments[1].len(); // "/gateway/{actor_id_segment}"
217211

218212
// Extract the remaining path preserving trailing slashes
219213
let remaining_base = if prefix_len < base_path.len() {

engine/packages/guard/tests/parse_actor_path.rs

Lines changed: 47 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use rivet_guard::routing::parse_actor_path;
22

33
#[test]
44
fn test_parse_actor_path_with_token() {
5-
// Basic path with token and route
6-
let path = "/gateway/actors/actor-123/tokens/my-token/route/api/v1/endpoint";
5+
// Basic path with token using @ syntax
6+
let path = "/gateway/actor-123@my-token/api/v1/endpoint";
77
let result = parse_actor_path(path).unwrap();
88
assert_eq!(result.actor_id, "actor-123");
99
assert_eq!(result.token, Some("my-token".to_string()));
@@ -13,7 +13,7 @@ fn test_parse_actor_path_with_token() {
1313
#[test]
1414
fn test_parse_actor_path_without_token() {
1515
// Path without token
16-
let path = "/gateway/actors/actor-123/route/api/v1/endpoint";
16+
let path = "/gateway/actor-123/api/v1/endpoint";
1717
let result = parse_actor_path(path).unwrap();
1818
assert_eq!(result.actor_id, "actor-123");
1919
assert_eq!(result.token, None);
@@ -23,7 +23,7 @@ fn test_parse_actor_path_without_token() {
2323
#[test]
2424
fn test_parse_actor_path_with_uuid() {
2525
// Path with UUID as actor ID
26-
let path = "/gateway/actors/12345678-1234-1234-1234-123456789abc/route/status";
26+
let path = "/gateway/12345678-1234-1234-1234-123456789abc/status";
2727
let result = parse_actor_path(path).unwrap();
2828
assert_eq!(result.actor_id, "12345678-1234-1234-1234-123456789abc");
2929
assert_eq!(result.token, None);
@@ -33,14 +33,14 @@ fn test_parse_actor_path_with_uuid() {
3333
#[test]
3434
fn test_parse_actor_path_with_query_params() {
3535
// Path with query parameters
36-
let path = "/gateway/actors/actor-456/route/api/endpoint?foo=bar&baz=qux";
36+
let path = "/gateway/actor-456/api/endpoint?foo=bar&baz=qux";
3737
let result = parse_actor_path(path).unwrap();
3838
assert_eq!(result.actor_id, "actor-456");
3939
assert_eq!(result.token, None);
4040
assert_eq!(result.remaining_path, "/api/endpoint?foo=bar&baz=qux");
4141

4242
// Path with token and query parameters
43-
let path = "/gateway/actors/actor-456/tokens/token123/route/api?key=value";
43+
let path = "/gateway/actor-456@token123/api?key=value";
4444
let result = parse_actor_path(path).unwrap();
4545
assert_eq!(result.actor_id, "actor-456");
4646
assert_eq!(result.token, Some("token123".to_string()));
@@ -50,7 +50,7 @@ fn test_parse_actor_path_with_query_params() {
5050
#[test]
5151
fn test_parse_actor_path_with_fragment() {
5252
// Path with fragment
53-
let path = "/gateway/actors/actor-789/route/page#section";
53+
let path = "/gateway/actor-789/page#section";
5454
let result = parse_actor_path(path).unwrap();
5555
assert_eq!(result.actor_id, "actor-789");
5656
assert_eq!(result.token, None);
@@ -60,15 +60,15 @@ fn test_parse_actor_path_with_fragment() {
6060

6161
#[test]
6262
fn test_parse_actor_path_empty_remaining() {
63-
// Path with no remaining path after route
64-
let path = "/gateway/actors/actor-000/route";
63+
// Path with no remaining path
64+
let path = "/gateway/actor-000";
6565
let result = parse_actor_path(path).unwrap();
6666
assert_eq!(result.actor_id, "actor-000");
6767
assert_eq!(result.token, None);
6868
assert_eq!(result.remaining_path, "/");
6969

7070
// With token and no remaining path
71-
let path = "/gateway/actors/actor-000/tokens/tok/route";
71+
let path = "/gateway/actor-000@tok";
7272
let result = parse_actor_path(path).unwrap();
7373
assert_eq!(result.actor_id, "actor-000");
7474
assert_eq!(result.token, Some("tok".to_string()));
@@ -78,7 +78,7 @@ fn test_parse_actor_path_empty_remaining() {
7878
#[test]
7979
fn test_parse_actor_path_with_trailing_slash() {
8080
// Path with trailing slash
81-
let path = "/gateway/actors/actor-111/route/api/";
81+
let path = "/gateway/actor-111/api/";
8282
let result = parse_actor_path(path).unwrap();
8383
assert_eq!(result.actor_id, "actor-111");
8484
assert_eq!(result.token, None);
@@ -88,8 +88,7 @@ fn test_parse_actor_path_with_trailing_slash() {
8888
#[test]
8989
fn test_parse_actor_path_complex_remaining() {
9090
// Complex remaining path with multiple segments
91-
let path =
92-
"/gateway/actors/actor-complex/tokens/secure-token/route/api/v2/users/123/profile/settings";
91+
let path = "/gateway/actor-complex@secure-token/api/v2/users/123/profile/settings";
9392
let result = parse_actor_path(path).unwrap();
9493
assert_eq!(result.actor_id, "actor-complex");
9594
assert_eq!(result.token, Some("secure-token".to_string()));
@@ -99,7 +98,7 @@ fn test_parse_actor_path_complex_remaining() {
9998
#[test]
10099
fn test_parse_actor_path_special_characters() {
101100
// Actor ID with allowed special characters
102-
let path = "/gateway/actors/actor_id-123.test/route/endpoint";
101+
let path = "/gateway/actor_id-123.test/endpoint";
103102
let result = parse_actor_path(path).unwrap();
104103
assert_eq!(result.actor_id, "actor_id-123.test");
105104
assert_eq!(result.token, None);
@@ -109,7 +108,7 @@ fn test_parse_actor_path_special_characters() {
109108
#[test]
110109
fn test_parse_actor_path_encoded_characters() {
111110
// URL encoded characters in path
112-
let path = "/gateway/actors/actor-123/route/api%20endpoint/test%2Fpath";
111+
let path = "/gateway/actor-123/api%20endpoint/test%2Fpath";
113112
let result = parse_actor_path(path).unwrap();
114113
assert_eq!(result.actor_id, "actor-123");
115114
assert_eq!(result.token, None);
@@ -121,70 +120,48 @@ fn test_parse_actor_path_encoded_characters() {
121120
#[test]
122121
fn test_parse_actor_path_invalid_prefix() {
123122
// Wrong prefix
124-
assert!(parse_actor_path("/api/actors/123/route/endpoint").is_none());
125-
assert!(parse_actor_path("/gateway/actor/123/route/endpoint").is_none());
126-
assert!(parse_actor_path("/actors/123/route/endpoint").is_none());
127-
}
128-
129-
#[test]
130-
fn test_parse_actor_path_missing_route() {
131-
// Missing route keyword
132-
assert!(parse_actor_path("/gateway/actors/123").is_none());
133-
assert!(parse_actor_path("/gateway/actors/123/endpoint").is_none());
134-
assert!(parse_actor_path("/gateway/actors/123/tokens/tok").is_none());
123+
assert!(parse_actor_path("/api/123/endpoint").is_none());
124+
assert!(parse_actor_path("/actor/123/endpoint").is_none());
135125
}
136126

137127
#[test]
138128
fn test_parse_actor_path_too_short() {
139129
// Too few segments
140130
assert!(parse_actor_path("/gateway").is_none());
141-
assert!(parse_actor_path("/gateway/actors").is_none());
142-
assert!(parse_actor_path("/gateway/actors/123").is_none());
143131
}
144132

145133
#[test]
146-
fn test_parse_actor_path_malformed_token_path() {
147-
// Token path but missing route
148-
assert!(parse_actor_path("/gateway/actors/123/tokens/tok/api").is_none());
149-
// Token without value
150-
assert!(parse_actor_path("/gateway/actors/123/tokens//route/api").is_none());
151-
}
152-
153-
#[test]
154-
fn test_parse_actor_path_wrong_segment_positions() {
155-
// Segments in wrong positions
156-
assert!(parse_actor_path("/actors/gateway/123/route/endpoint").is_none());
157-
assert!(parse_actor_path("/gateway/route/actors/123/endpoint").is_none());
134+
fn test_parse_actor_path_malformed_token() {
135+
// Token without actor_id (empty before @)
136+
assert!(parse_actor_path("/gateway/@tok/api").is_none());
137+
// Empty token (nothing after @)
138+
assert!(parse_actor_path("/gateway/actor-123@/api").is_none());
158139
}
159140

160141
#[test]
161142
fn test_parse_actor_path_empty_values() {
162143
// Empty actor_id
163-
assert!(parse_actor_path("/gateway/actors//route/endpoint").is_none());
164-
assert!(parse_actor_path("/gateway/actors//tokens/tok/route/endpoint").is_none());
144+
assert!(parse_actor_path("/gateway//endpoint").is_none());
165145
}
166146

167147
#[test]
168148
fn test_parse_actor_path_double_slash() {
169149
// Double slashes in path
170-
let path = "/gateway/actors//actor-123/route/endpoint";
150+
let path = "/gateway//actor-123/endpoint";
171151
// This will fail because the double slash creates an empty segment
172152
assert!(parse_actor_path(path).is_none());
173153
}
174154

175155
#[test]
176156
fn test_parse_actor_path_case_sensitive() {
177157
// Keywords are case sensitive
178-
assert!(parse_actor_path("/Gateway/actors/123/route/endpoint").is_none());
179-
assert!(parse_actor_path("/gateway/Actors/123/route/endpoint").is_none());
180-
assert!(parse_actor_path("/gateway/actors/123/Route/endpoint").is_none());
181-
assert!(parse_actor_path("/gateway/actors/123/tokens/tok/Route/endpoint").is_none());
158+
assert!(parse_actor_path("/Gateway/123/endpoint").is_none());
182159
}
183160

184161
#[test]
185162
fn test_parse_actor_path_query_and_fragment() {
186163
// Path with both query and fragment
187-
let path = "/gateway/actors/actor-123/route/api?query=1#section";
164+
let path = "/gateway/actor-123/api?query=1#section";
188165
let result = parse_actor_path(path).unwrap();
189166
assert_eq!(result.actor_id, "actor-123");
190167
assert_eq!(result.token, None);
@@ -194,10 +171,30 @@ fn test_parse_actor_path_query_and_fragment() {
194171

195172
#[test]
196173
fn test_parse_actor_path_only_query_string() {
197-
// Path ending with route but having query string
198-
let path = "/gateway/actors/actor-123/route?direct=true";
174+
// Path ending after actor_id but having query string
175+
let path = "/gateway/actor-123?direct=true";
199176
let result = parse_actor_path(path).unwrap();
200177
assert_eq!(result.actor_id, "actor-123");
201178
assert_eq!(result.token, None);
202179
assert_eq!(result.remaining_path, "/?direct=true");
203180
}
181+
182+
#[test]
183+
fn test_parse_actor_path_token_with_special_chars() {
184+
// Token containing special characters
185+
let path = "/gateway/actor-123@token_with-chars.123/endpoint";
186+
let result = parse_actor_path(path).unwrap();
187+
assert_eq!(result.actor_id, "actor-123");
188+
assert_eq!(result.token, Some("token_with-chars.123".to_string()));
189+
assert_eq!(result.remaining_path, "/endpoint");
190+
}
191+
192+
#[test]
193+
fn test_parse_actor_path_multiple_at_signs() {
194+
// Multiple @ signs - only first one is used for token splitting
195+
let path = "/gateway/actor-123@token@with@ats/endpoint";
196+
let result = parse_actor_path(path).unwrap();
197+
assert_eq!(result.actor_id, "actor-123");
198+
assert_eq!(result.token, Some("token@with@ats".to_string()));
199+
assert_eq!(result.remaining_path, "/endpoint");
200+
}

examples/cursors-raw-websocket/src/frontend/App.tsx

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

0 commit comments

Comments
 (0)