phantom_frame/
lib.rs

1pub mod cache;
2pub mod config;
3pub mod control;
4pub mod path_matcher;
5pub mod proxy;
6
7use axum::Router;
8use cache::{CacheStore, RefreshTrigger};
9use proxy::ProxyState;
10use std::sync::Arc;
11
12/// Information about an incoming request for cache key generation
13#[derive(Clone, Debug)]
14pub struct RequestInfo<'a> {
15    /// HTTP method (e.g., "GET", "POST", "PUT", "DELETE")
16    pub method: &'a str,
17    /// Request path (e.g., "/api/users")
18    pub path: &'a str,
19    /// Query string (e.g., "id=123&sort=asc")
20    pub query: &'a str,
21    /// Request headers (for custom cache key logic based on headers)
22    pub headers: &'a axum::http::HeaderMap,
23}
24
25/// Configuration for creating a proxy
26#[derive(Clone)]
27pub struct CreateProxyConfig {
28    /// The backend URL to proxy requests to
29    pub proxy_url: String,
30    
31    /// Paths to include in caching (empty means include all)
32    /// Supports wildcards and method prefixes: "/api/*", "POST /api/*", "GET /*/users", etc.
33    pub include_paths: Vec<String>,
34    
35    /// Paths to exclude from caching (empty means exclude none)
36    /// Supports wildcards and method prefixes: "/admin/*", "POST *", "PUT /api/*", etc.
37    /// Exclude overrides include
38    pub exclude_paths: Vec<String>,
39    
40    /// Custom cache key generator
41    /// Takes request info and returns a cache key
42    /// Default: method + path + query string
43    pub cache_key_fn: Arc<dyn Fn(&RequestInfo) -> String + Send + Sync>,
44}
45
46impl CreateProxyConfig {
47    /// Create a new config with default settings
48    pub fn new(proxy_url: String) -> Self {
49        Self {
50            proxy_url,
51            include_paths: vec![],
52            exclude_paths: vec![],
53            cache_key_fn: Arc::new(|req_info| {
54                if req_info.query.is_empty() {
55                    format!("{}:{}", req_info.method, req_info.path)
56                } else {
57                    format!("{}:{}?{}", req_info.method, req_info.path, req_info.query)
58                }
59            }),
60        }
61    }
62    
63    /// Set include paths
64    pub fn with_include_paths(mut self, paths: Vec<String>) -> Self {
65        self.include_paths = paths;
66        self
67    }
68    
69    /// Set exclude paths
70    pub fn with_exclude_paths(mut self, paths: Vec<String>) -> Self {
71        self.exclude_paths = paths;
72        self
73    }
74    
75    /// Set custom cache key function
76    pub fn with_cache_key_fn<F>(mut self, f: F) -> Self
77    where
78        F: Fn(&RequestInfo) -> String + Send + Sync + 'static,
79    {
80        self.cache_key_fn = Arc::new(f);
81        self
82    }
83}
84
85/// The main library interface for using phantom-frame as a library
86/// Returns a proxy handler function and a refresh trigger
87pub fn create_proxy(config: CreateProxyConfig) -> (Router, RefreshTrigger) {
88    let refresh_trigger = RefreshTrigger::new();
89    let cache = CacheStore::new(refresh_trigger.clone());
90
91    let proxy_state = Arc::new(ProxyState::new(cache, config));
92
93    let app = Router::new()
94        .fallback(proxy::proxy_handler)
95        .with_state(proxy_state);
96
97    (app, refresh_trigger)
98}
99
100/// Create a proxy handler with an existing refresh trigger
101pub fn create_proxy_with_trigger(config: CreateProxyConfig, refresh_trigger: RefreshTrigger) -> Router {
102    let cache = CacheStore::new(refresh_trigger);
103    let proxy_state = Arc::new(ProxyState::new(cache, config));
104
105    Router::new()
106        .fallback(proxy::proxy_handler)
107        .with_state(proxy_state)
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn test_create_proxy() {
116        let config = CreateProxyConfig::new("http://localhost:8080".to_string());
117        let (_app, trigger) = create_proxy(config);
118        trigger.trigger();
119        // Just ensure it compiles and runs without panic
120    }
121}