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