Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 25 additions & 31 deletions engine/packages/guard/src/routing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ pub fn create_routing_function(ctx: StandaloneCtx, shared_state: SharedState) ->

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

// Check minimum required segments: gateway, actors, {actor_id}, route
if segments.len() < 4 {
// Check minimum required segments: gateway, {actor_id}
if segments.len() < 2 {
return None;
}

// Verify the fixed segments
if segments[0] != "gateway" || segments[1] != "actors" {
// Verify the fixed segment
if segments[0] != "gateway" {
return None;
}

// Check for empty actor_id
if segments[2].is_empty() {
// Check for empty actor_id segment
if segments[1].is_empty() {
return None;
}

let actor_id = segments[2].to_string();

// Check for token or direct route
let (token, remaining_path_start_idx) =
if segments.len() >= 6 && segments[3] == "tokens" && segments[5] == "route" {
// Pattern with token: /gateway/actors/{actor_id}/tokens/{token}/route/{...path}
// Check for empty token
if segments[4].is_empty() {
return None;
}
(Some(segments[4].to_string()), 6)
} else if segments.len() >= 4 && segments[3] == "route" {
// Pattern without token: /gateway/actors/{actor_id}/route/{...path}
(None, 4)
} else {
// Parse actor_id and optional token from second segment
// Pattern: {actor_id}@{token} or just {actor_id}
let actor_id_segment = segments[1];
let (actor_id, token) = if let Some(at_pos) = actor_id_segment.find('@') {
let aid = &actor_id_segment[..at_pos];
let tok = &actor_id_segment[at_pos + 1..];

// Check for empty actor_id or token
if aid.is_empty() || tok.is_empty() {
return None;
};
}

(aid.to_string(), Some(tok.to_string()))
} else {
(actor_id_segment.to_string(), None)
};

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

// Extract the remaining path preserving trailing slashes
let remaining_base = if prefix_len < base_path.len() {
Expand Down
97 changes: 47 additions & 50 deletions engine/packages/guard/tests/parse_actor_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use rivet_guard::routing::parse_actor_path;

#[test]
fn test_parse_actor_path_with_token() {
// Basic path with token and route
let path = "/gateway/actors/actor-123/tokens/my-token/route/api/v1/endpoint";
// Basic path with token using @ syntax
let path = "/gateway/actor-123@my-token/api/v1/endpoint";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-123");
assert_eq!(result.token, Some("my-token".to_string()));
Expand All @@ -13,7 +13,7 @@ fn test_parse_actor_path_with_token() {
#[test]
fn test_parse_actor_path_without_token() {
// Path without token
let path = "/gateway/actors/actor-123/route/api/v1/endpoint";
let path = "/gateway/actor-123/api/v1/endpoint";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-123");
assert_eq!(result.token, None);
Expand All @@ -23,7 +23,7 @@ fn test_parse_actor_path_without_token() {
#[test]
fn test_parse_actor_path_with_uuid() {
// Path with UUID as actor ID
let path = "/gateway/actors/12345678-1234-1234-1234-123456789abc/route/status";
let path = "/gateway/12345678-1234-1234-1234-123456789abc/status";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "12345678-1234-1234-1234-123456789abc");
assert_eq!(result.token, None);
Expand All @@ -33,14 +33,14 @@ fn test_parse_actor_path_with_uuid() {
#[test]
fn test_parse_actor_path_with_query_params() {
// Path with query parameters
let path = "/gateway/actors/actor-456/route/api/endpoint?foo=bar&baz=qux";
let path = "/gateway/actor-456/api/endpoint?foo=bar&baz=qux";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-456");
assert_eq!(result.token, None);
assert_eq!(result.remaining_path, "/api/endpoint?foo=bar&baz=qux");

// Path with token and query parameters
let path = "/gateway/actors/actor-456/tokens/token123/route/api?key=value";
let path = "/gateway/actor-456@token123/api?key=value";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-456");
assert_eq!(result.token, Some("token123".to_string()));
Expand All @@ -50,7 +50,7 @@ fn test_parse_actor_path_with_query_params() {
#[test]
fn test_parse_actor_path_with_fragment() {
// Path with fragment
let path = "/gateway/actors/actor-789/route/page#section";
let path = "/gateway/actor-789/page#section";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-789");
assert_eq!(result.token, None);
Expand All @@ -60,15 +60,15 @@ fn test_parse_actor_path_with_fragment() {

#[test]
fn test_parse_actor_path_empty_remaining() {
// Path with no remaining path after route
let path = "/gateway/actors/actor-000/route";
// Path with no remaining path
let path = "/gateway/actor-000";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-000");
assert_eq!(result.token, None);
assert_eq!(result.remaining_path, "/");

// With token and no remaining path
let path = "/gateway/actors/actor-000/tokens/tok/route";
let path = "/gateway/actor-000@tok";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-000");
assert_eq!(result.token, Some("tok".to_string()));
Expand All @@ -78,7 +78,7 @@ fn test_parse_actor_path_empty_remaining() {
#[test]
fn test_parse_actor_path_with_trailing_slash() {
// Path with trailing slash
let path = "/gateway/actors/actor-111/route/api/";
let path = "/gateway/actor-111/api/";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-111");
assert_eq!(result.token, None);
Expand All @@ -88,8 +88,7 @@ fn test_parse_actor_path_with_trailing_slash() {
#[test]
fn test_parse_actor_path_complex_remaining() {
// Complex remaining path with multiple segments
let path =
"/gateway/actors/actor-complex/tokens/secure-token/route/api/v2/users/123/profile/settings";
let path = "/gateway/actor-complex@secure-token/api/v2/users/123/profile/settings";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-complex");
assert_eq!(result.token, Some("secure-token".to_string()));
Expand All @@ -99,7 +98,7 @@ fn test_parse_actor_path_complex_remaining() {
#[test]
fn test_parse_actor_path_special_characters() {
// Actor ID with allowed special characters
let path = "/gateway/actors/actor_id-123.test/route/endpoint";
let path = "/gateway/actor_id-123.test/endpoint";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor_id-123.test");
assert_eq!(result.token, None);
Expand All @@ -109,7 +108,7 @@ fn test_parse_actor_path_special_characters() {
#[test]
fn test_parse_actor_path_encoded_characters() {
// URL encoded characters in path
let path = "/gateway/actors/actor-123/route/api%20endpoint/test%2Fpath";
let path = "/gateway/actor-123/api%20endpoint/test%2Fpath";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-123");
assert_eq!(result.token, None);
Expand All @@ -121,70 +120,48 @@ fn test_parse_actor_path_encoded_characters() {
#[test]
fn test_parse_actor_path_invalid_prefix() {
// Wrong prefix
assert!(parse_actor_path("/api/actors/123/route/endpoint").is_none());
assert!(parse_actor_path("/gateway/actor/123/route/endpoint").is_none());
assert!(parse_actor_path("/actors/123/route/endpoint").is_none());
}

#[test]
fn test_parse_actor_path_missing_route() {
// Missing route keyword
assert!(parse_actor_path("/gateway/actors/123").is_none());
assert!(parse_actor_path("/gateway/actors/123/endpoint").is_none());
assert!(parse_actor_path("/gateway/actors/123/tokens/tok").is_none());
assert!(parse_actor_path("/api/123/endpoint").is_none());
assert!(parse_actor_path("/actor/123/endpoint").is_none());
}

#[test]
fn test_parse_actor_path_too_short() {
// Too few segments
assert!(parse_actor_path("/gateway").is_none());
assert!(parse_actor_path("/gateway/actors").is_none());
assert!(parse_actor_path("/gateway/actors/123").is_none());
}

#[test]
fn test_parse_actor_path_malformed_token_path() {
// Token path but missing route
assert!(parse_actor_path("/gateway/actors/123/tokens/tok/api").is_none());
// Token without value
assert!(parse_actor_path("/gateway/actors/123/tokens//route/api").is_none());
}

#[test]
fn test_parse_actor_path_wrong_segment_positions() {
// Segments in wrong positions
assert!(parse_actor_path("/actors/gateway/123/route/endpoint").is_none());
assert!(parse_actor_path("/gateway/route/actors/123/endpoint").is_none());
fn test_parse_actor_path_malformed_token() {
// Token without actor_id (empty before @)
assert!(parse_actor_path("/gateway/@tok/api").is_none());
// Empty token (nothing after @)
assert!(parse_actor_path("/gateway/actor-123@/api").is_none());
}

#[test]
fn test_parse_actor_path_empty_values() {
// Empty actor_id
assert!(parse_actor_path("/gateway/actors//route/endpoint").is_none());
assert!(parse_actor_path("/gateway/actors//tokens/tok/route/endpoint").is_none());
assert!(parse_actor_path("/gateway//endpoint").is_none());
}

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

#[test]
fn test_parse_actor_path_case_sensitive() {
// Keywords are case sensitive
assert!(parse_actor_path("/Gateway/actors/123/route/endpoint").is_none());
assert!(parse_actor_path("/gateway/Actors/123/route/endpoint").is_none());
assert!(parse_actor_path("/gateway/actors/123/Route/endpoint").is_none());
assert!(parse_actor_path("/gateway/actors/123/tokens/tok/Route/endpoint").is_none());
assert!(parse_actor_path("/Gateway/123/endpoint").is_none());
}

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

#[test]
fn test_parse_actor_path_only_query_string() {
// Path ending with route but having query string
let path = "/gateway/actors/actor-123/route?direct=true";
// Path ending after actor_id but having query string
let path = "/gateway/actor-123?direct=true";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-123");
assert_eq!(result.token, None);
assert_eq!(result.remaining_path, "/?direct=true");
}

#[test]
fn test_parse_actor_path_token_with_special_chars() {
// Token containing special characters
let path = "/gateway/actor-123@token_with-chars.123/endpoint";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-123");
assert_eq!(result.token, Some("token_with-chars.123".to_string()));
assert_eq!(result.remaining_path, "/endpoint");
}

#[test]
fn test_parse_actor_path_multiple_at_signs() {
// Multiple @ signs - only first one is used for token splitting
let path = "/gateway/actor-123@token@with@ats/endpoint";
let result = parse_actor_path(path).unwrap();
assert_eq!(result.actor_id, "actor-123");
assert_eq!(result.token, Some("token@with@ats".to_string()));
assert_eq!(result.remaining_path, "/endpoint");
}
2 changes: 1 addition & 1 deletion examples/cursors-raw-websocket/src/frontend/App.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading