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