//! Request extractors shared across routes. use axum::{ async_trait, extract::FromRequestParts, http::{request::Parts, HeaderName}, }; use std::convert::Infallible; /// The game a request belongs to, read from the `X-OpenFUT-Game` header. /// /// Multi-game support: each game bridge tags its requests with its own id /// (e.g. `fifa17`, `fifa23`) so core can scope the active profile - and thus all /// downstream club/card/squad/market state - to that game. Defaults to `fifa23` /// when the header is absent, so the existing FIFA 23 bridge and the integration /// tests (which send no header) keep operating on their game unchanged. /// /// Extraction never fails: a missing or malformed header falls back to the default. #[derive(Debug, Clone)] pub struct GameId(pub String); /// The game assumed when no `X-OpenFUT-Game` header is present. pub const DEFAULT_GAME: &str = "fifa23"; static HEADER: HeaderName = HeaderName::from_static("x-openfut-game"); impl GameId { pub fn as_str(&self) -> &str { &self.0 } } #[async_trait] impl FromRequestParts for GameId where S: Send + Sync, { type Rejection = Infallible; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { let game = parts .headers .get(&HEADER) .and_then(|v| v.to_str().ok()) .map(|s| s.trim()) .filter(|s| !s.is_empty()) .unwrap_or(DEFAULT_GAME) .to_string(); Ok(GameId(game)) } }