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