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