Skip to main content

simulator_api/
geyser_route.rs

1//! How a gRPC caller names the session it wants.
2//!
3//! gRPC method paths are fixed by the proto, so a session cannot be named by the URL the way it
4//! is for HTTP and WebSocket. A client that can rewrite paths prefixes `/backtest/{id}`; one that
5//! cannot uses [`SESSION_ID_HEADER`]. Both halves of the format live here so the side that writes
6//! it and the side that parses it cannot drift.
7
8use crate::SessionId;
9
10/// Request metadata naming the session, for a client that cannot rewrite the path.
11pub const SESSION_ID_HEADER: &str = "x-backtest-session-id";
12
13/// Prefix carrying the session id, mirroring the HTTP and WebSocket routes.
14const SESSION_PATH_PREFIX: &str = "/backtest/";
15
16/// The path a client sends to reach `method_path` on `session_id`.
17pub fn prefixed_path(session_id: &SessionId, method_path: &str) -> String {
18    format!("{SESSION_PATH_PREFIX}{session_id}{method_path}")
19}
20
21/// Split a prefixed path back into the session id and the method path the session itself expects.
22///
23/// `None` when the path carries no session prefix, or names a session with no method after it.
24pub fn split_prefixed_path(path: &str) -> Option<(&str, String)> {
25    let (session_id, method) = path.strip_prefix(SESSION_PATH_PREFIX)?.split_once('/')?;
26    Some((session_id, format!("/{method}")))
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32
33    /// The drift this module exists to prevent: what the client writes is what the manager reads.
34    #[test]
35    fn a_prefixed_path_splits_back_into_its_parts() {
36        for method in [
37            "/geyser.Geyser/Subscribe",
38            "/geyser.Geyser/GetVersion",
39            "/geyser.Geyser/SubscribeReplayInfo",
40        ] {
41            let session_id = SessionId::new();
42            let path = prefixed_path(&session_id, method);
43            let (parsed, rest) =
44                split_prefixed_path(&path).unwrap_or_else(|| panic!("{method} did not split"));
45            assert_eq!(parsed, session_id.to_string(), "{method}");
46            assert_eq!(rest, method, "{method}");
47        }
48    }
49
50    #[test]
51    fn a_path_without_a_session_does_not_split() {
52        for (name, path) in [
53            ("no prefix", "/geyser.Geyser/Subscribe"),
54            ("prefix only", "/backtest/"),
55            ("session with no method", "/backtest/abc"),
56            ("a different route", "/available-ranges"),
57        ] {
58            assert!(split_prefixed_path(path).is_none(), "{name}");
59        }
60    }
61}