simulator-api 0.23.0

Wire-protocol types for the Solana simulator backtest WebSocket API
Documentation
//! How a gRPC caller names the session it wants.
//!
//! gRPC method paths are fixed by the proto, so a session cannot be named by the URL the way it
//! is for HTTP and WebSocket. A client that can rewrite paths prefixes `/backtest/{id}`; one that
//! cannot uses [`SESSION_ID_HEADER`]. Both halves of the format live here so the side that writes
//! it and the side that parses it cannot drift.

use crate::SessionId;

/// Request metadata naming the session, for a client that cannot rewrite the path.
pub const SESSION_ID_HEADER: &str = "x-backtest-session-id";

/// Prefix carrying the session id, mirroring the HTTP and WebSocket routes.
const SESSION_PATH_PREFIX: &str = "/backtest/";

/// The path a client sends to reach `method_path` on `session_id`.
pub fn prefixed_path(session_id: &SessionId, method_path: &str) -> String {
    format!("{SESSION_PATH_PREFIX}{session_id}{method_path}")
}

/// Split a prefixed path back into the session id and the method path the session itself expects.
///
/// `None` when the path carries no session prefix, or names a session with no method after it.
pub fn split_prefixed_path(path: &str) -> Option<(&str, String)> {
    let (session_id, method) = path.strip_prefix(SESSION_PATH_PREFIX)?.split_once('/')?;
    Some((session_id, format!("/{method}")))
}

#[cfg(test)]
mod tests {
    use super::*;

    /// The drift this module exists to prevent: what the client writes is what the manager reads.
    #[test]
    fn a_prefixed_path_splits_back_into_its_parts() {
        for method in [
            "/geyser.Geyser/Subscribe",
            "/geyser.Geyser/GetVersion",
            "/geyser.Geyser/SubscribeReplayInfo",
        ] {
            let session_id = SessionId::new();
            let path = prefixed_path(&session_id, method);
            let (parsed, rest) =
                split_prefixed_path(&path).unwrap_or_else(|| panic!("{method} did not split"));
            assert_eq!(parsed, session_id.to_string(), "{method}");
            assert_eq!(rest, method, "{method}");
        }
    }

    #[test]
    fn a_path_without_a_session_does_not_split() {
        for (name, path) in [
            ("no prefix", "/geyser.Geyser/Subscribe"),
            ("prefix only", "/backtest/"),
            ("session with no method", "/backtest/abc"),
            ("a different route", "/available-ranges"),
        ] {
            assert!(split_prefixed_path(path).is_none(), "{name}");
        }
    }
}