systemprompt_models/routing/
mod.rs1use crate::modules::ApiPaths;
2use crate::ContentRouting;
3use std::path::Path;
4use std::sync::Arc;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct EventMetadata {
8 pub event_type: &'static str,
9 pub event_category: &'static str,
10 pub log_module: &'static str,
11}
12
13impl EventMetadata {
14 pub const HTML_CONTENT: Self = Self {
15 event_type: "page_view",
16 event_category: "content",
17 log_module: "page_view",
18 };
19
20 pub const API_REQUEST: Self = Self {
21 event_type: "http_request",
22 event_category: "api",
23 log_module: "http_request",
24 };
25
26 pub const STATIC_ASSET: Self = Self {
27 event_type: "asset_request",
28 event_category: "static",
29 log_module: "asset_request",
30 };
31
32 pub const NOT_FOUND: Self = Self {
33 event_type: "not_found",
34 event_category: "error",
35 log_module: "not_found",
36 };
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum RouteType {
41 HtmlContent { source: String },
42 ApiEndpoint { category: ApiCategory },
43 StaticAsset { asset_type: AssetType },
44 NotFound,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum ApiCategory {
49 Content,
50 Core,
51 Agents,
52 OAuth,
53 Other,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum AssetType {
58 JavaScript,
59 Stylesheet,
60 Image,
61 Font,
62 SourceMap,
63 Other,
64}
65
66pub struct RouteClassifier {
67 content_routing: Option<Arc<dyn ContentRouting>>,
68}
69
70impl std::fmt::Debug for RouteClassifier {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 f.debug_struct("RouteClassifier")
73 .field("content_routing", &self.content_routing.is_some())
74 .finish()
75 }
76}
77
78impl RouteClassifier {
79 pub fn new(content_routing: Option<Arc<dyn ContentRouting>>) -> Self {
80 Self { content_routing }
81 }
82
83 pub fn classify(&self, path: &str, _method: &str) -> RouteType {
84 if Self::is_static_asset_path(path) {
85 return RouteType::StaticAsset {
86 asset_type: Self::determine_asset_type(path),
87 };
88 }
89
90 if path.starts_with(ApiPaths::API_BASE) {
91 return RouteType::ApiEndpoint {
92 category: Self::determine_api_category(path),
93 };
94 }
95
96 if let Some(routing) = &self.content_routing {
97 if routing.is_html_page(path) {
98 return RouteType::HtmlContent {
99 source: routing.determine_source(path),
100 };
101 }
102 } else if !Self::is_static_asset_path(path) && !path.starts_with(ApiPaths::API_BASE) {
103 return RouteType::HtmlContent {
104 source: "unknown".to_string(),
105 };
106 }
107
108 RouteType::NotFound
109 }
110
111 pub fn should_track_analytics(&self, path: &str, method: &str) -> bool {
112 if method == "OPTIONS" {
113 return false;
114 }
115
116 match self.classify(path, method) {
117 RouteType::HtmlContent { .. } => true,
118 RouteType::ApiEndpoint { category } => {
119 matches!(category, ApiCategory::Core | ApiCategory::Content)
120 },
121 RouteType::StaticAsset { .. } | RouteType::NotFound => false,
122 }
123 }
124
125 pub fn is_html(&self, path: &str) -> bool {
126 matches!(self.classify(path, "GET"), RouteType::HtmlContent { .. })
127 }
128
129 pub fn get_event_metadata(&self, path: &str, method: &str) -> EventMetadata {
130 match self.classify(path, method) {
131 RouteType::HtmlContent { .. } => EventMetadata::HTML_CONTENT,
132 RouteType::ApiEndpoint { .. } => EventMetadata::API_REQUEST,
133 RouteType::StaticAsset { .. } => EventMetadata::STATIC_ASSET,
134 RouteType::NotFound => EventMetadata::NOT_FOUND,
135 }
136 }
137
138 fn is_static_asset_path(path: &str) -> bool {
139 if path.starts_with(ApiPaths::ASSETS_BASE)
140 || path.starts_with(ApiPaths::WELLKNOWN_BASE)
141 || path.starts_with(ApiPaths::GENERATED_BASE)
142 || path.starts_with(ApiPaths::FILES_BASE)
143 {
144 return true;
145 }
146
147 matches!(
148 Path::new(path).extension().and_then(|e| e.to_str()),
149 Some(
150 "js" | "css"
151 | "map"
152 | "ttf"
153 | "woff"
154 | "woff2"
155 | "otf"
156 | "png"
157 | "jpg"
158 | "jpeg"
159 | "svg"
160 | "ico"
161 | "webp"
162 )
163 ) || path == "/vite.svg"
164 || path == "/favicon.ico"
165 }
166
167 fn determine_asset_type(path: &str) -> AssetType {
168 match Path::new(path).extension().and_then(|e| e.to_str()) {
169 Some("js") => AssetType::JavaScript,
170 Some("css") => AssetType::Stylesheet,
171 Some("png" | "jpg" | "jpeg" | "svg" | "ico" | "webp") => AssetType::Image,
172 Some("ttf" | "woff" | "woff2" | "otf") => AssetType::Font,
173 Some("map") => AssetType::SourceMap,
174 _ => AssetType::Other,
175 }
176 }
177
178 fn determine_api_category(path: &str) -> ApiCategory {
179 if path.starts_with(ApiPaths::CONTENT_BASE) {
180 ApiCategory::Content
181 } else if path.starts_with(ApiPaths::CORE_BASE) {
182 ApiCategory::Core
183 } else if path.starts_with(ApiPaths::AGENTS_BASE) {
184 ApiCategory::Agents
185 } else if path.starts_with(ApiPaths::OAUTH_BASE) {
186 ApiCategory::OAuth
187 } else {
188 ApiCategory::Other
189 }
190 }
191}