Skip to main content

systemprompt_api/services/static_content/
fallback.rs

1use axum::extract::State;
2use axum::http::{HeaderMap, Method, StatusCode, Uri};
3use axum::response::{IntoResponse, Json};
4use serde_json::json;
5use systemprompt_models::modules::ApiPaths;
6
7use super::static_files::StaticContentState;
8
9pub async fn smart_fallback_handler(
10    State(state): State<StaticContentState>,
11    uri: Uri,
12    method: Method,
13    headers: HeaderMap,
14    req_ctx: Option<axum::Extension<systemprompt_models::RequestContext>>,
15) -> impl IntoResponse {
16    let path = uri.path();
17
18    if is_api_path(path) {
19        return (
20            StatusCode::NOT_FOUND,
21            Json(json!({
22                "error": "Not Found",
23                "message": format!("No route matches {method} {path}"),
24                "path": path,
25                "suggestions": get_api_suggestions(path)
26            })),
27        )
28            .into_response();
29    }
30
31    super::serve_static_content(State(state), uri, headers, req_ctx)
32        .await
33        .into_response()
34}
35
36#[cfg(feature = "test-api")]
37pub mod test_api {
38    #[must_use]
39    pub fn is_api_path(path: &str) -> bool {
40        super::is_api_path(path)
41    }
42
43    #[must_use]
44    pub fn get_api_suggestions(path: &str) -> Vec<String> {
45        super::get_api_suggestions(path)
46    }
47}
48
49fn is_api_path(path: &str) -> bool {
50    path.starts_with(ApiPaths::API_BASE)
51        || path.starts_with(ApiPaths::WELLKNOWN_BASE)
52        || path.starts_with("/server/")
53        || path.starts_with("/mcp/")
54        || path.starts_with("/health")
55        || path.starts_with(ApiPaths::OPENAPI_BASE)
56        || path.starts_with(ApiPaths::DOCS_BASE)
57        || path.starts_with(ApiPaths::SWAGGER_BASE)
58        || path.starts_with("/v1/")
59        || path.starts_with("/auth/")
60        || path.starts_with("/oauth/")
61}
62
63fn get_api_suggestions(path: &str) -> Vec<String> {
64    if path.starts_with(ApiPaths::API_BASE) {
65        vec![
66            format!("{} - API discovery endpoint", ApiPaths::DISCOVERY),
67            format!("{} - Health check", ApiPaths::HEALTH),
68            format!(
69                "{} - Core services (contexts, tasks, artifacts)",
70                ApiPaths::CORE_BASE
71            ),
72            format!("{} - Agent registry", ApiPaths::AGENTS_REGISTRY),
73            format!("{} - MCP server registry", ApiPaths::MCP_REGISTRY),
74        ]
75    } else if path.starts_with(ApiPaths::WELLKNOWN_BASE) {
76        vec![
77            format!("{} - OAuth metadata", ApiPaths::WELLKNOWN_OAUTH_SERVER),
78            format!("{} - Agent card", ApiPaths::WELLKNOWN_AGENT_CARD),
79        ]
80    } else if path.contains("health") {
81        vec![format!("{} - Health check endpoint", ApiPaths::HEALTH)]
82    } else if path.contains("openapi") || path.contains("swagger") {
83        vec![format!(
84            "{} - API discovery (OpenAPI not yet available)",
85            ApiPaths::DISCOVERY
86        )]
87    } else {
88        vec![
89            format!("{} - Start here for API discovery", ApiPaths::DISCOVERY),
90            "/ - Frontend application".to_owned(),
91        ]
92    }
93}