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 path.starts_with(ApiPaths::TRACK_BASE) {
97 return RouteType::ApiEndpoint {
98 category: ApiCategory::Other,
99 };
100 }
101
102 if let Some(routing) = &self.content_routing {
103 if routing.is_html_page(path) {
104 return RouteType::HtmlContent {
105 source: routing.determine_source(path),
106 };
107 }
108 } else if !Self::is_static_asset_path(path) && !path.starts_with(ApiPaths::API_BASE) {
109 return RouteType::HtmlContent {
110 source: "unknown".to_string(),
111 };
112 }
113
114 RouteType::NotFound
115 }
116
117 pub fn should_track_analytics(&self, path: &str, method: &str) -> bool {
118 if method == "OPTIONS" {
119 return false;
120 }
121
122 match self.classify(path, method) {
123 RouteType::HtmlContent { .. } => true,
124 RouteType::ApiEndpoint { category } => {
125 matches!(
126 category,
127 ApiCategory::Core | ApiCategory::Content | ApiCategory::Other
128 )
129 },
130 RouteType::StaticAsset { .. } | RouteType::NotFound => false,
131 }
132 }
133
134 pub fn is_html(&self, path: &str) -> bool {
135 matches!(self.classify(path, "GET"), RouteType::HtmlContent { .. })
136 }
137
138 pub fn get_event_metadata(&self, path: &str, method: &str) -> EventMetadata {
139 match self.classify(path, method) {
140 RouteType::HtmlContent { .. } => EventMetadata::HTML_CONTENT,
141 RouteType::ApiEndpoint { .. } => EventMetadata::API_REQUEST,
142 RouteType::StaticAsset { .. } => EventMetadata::STATIC_ASSET,
143 RouteType::NotFound => EventMetadata::NOT_FOUND,
144 }
145 }
146
147 fn is_static_asset_path(path: &str) -> bool {
148 if path.starts_with(ApiPaths::ASSETS_BASE)
149 || path.starts_with(ApiPaths::WELLKNOWN_BASE)
150 || path.starts_with(ApiPaths::GENERATED_BASE)
151 || path.starts_with(ApiPaths::FILES_BASE)
152 {
153 return true;
154 }
155
156 matches!(
157 Path::new(path).extension().and_then(|e| e.to_str()),
158 Some(
159 "js" | "css"
160 | "map"
161 | "ttf"
162 | "woff"
163 | "woff2"
164 | "otf"
165 | "png"
166 | "jpg"
167 | "jpeg"
168 | "svg"
169 | "ico"
170 | "webp"
171 )
172 ) || path == "/vite.svg"
173 || path == "/favicon.ico"
174 }
175
176 fn determine_asset_type(path: &str) -> AssetType {
177 match Path::new(path).extension().and_then(|e| e.to_str()) {
178 Some("js") => AssetType::JavaScript,
179 Some("css") => AssetType::Stylesheet,
180 Some("png" | "jpg" | "jpeg" | "svg" | "ico" | "webp") => AssetType::Image,
181 Some("ttf" | "woff" | "woff2" | "otf") => AssetType::Font,
182 Some("map") => AssetType::SourceMap,
183 _ => AssetType::Other,
184 }
185 }
186
187 fn determine_api_category(path: &str) -> ApiCategory {
188 if path.starts_with(ApiPaths::CONTENT_BASE) {
189 ApiCategory::Content
190 } else if path.starts_with(ApiPaths::CORE_BASE) {
191 ApiCategory::Core
192 } else if path.starts_with(ApiPaths::AGENTS_BASE) {
193 ApiCategory::Agents
194 } else if path.starts_with(ApiPaths::OAUTH_BASE) {
195 ApiCategory::OAuth
196 } else {
197 ApiCategory::Other
198 }
199 }
200}