1use crate::{
2 BootApplication, BootError, HttpMethod, MessagePatternKind, OpenApiRouteMetadata,
3 ProviderToken, Result, RouteVersioning, SerializationOptions,
4};
5use serde::de::DeserializeOwned;
6use serde_json::Value;
7use std::collections::BTreeMap;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct DiscoveredModule {
12 pub name: String,
13 pub provider_tokens: Vec<ProviderToken>,
14}
15
16#[derive(Debug, Clone, PartialEq)]
18pub struct DiscoveredRoute {
19 pub method: HttpMethod,
20 pub path: String,
21 pub path_shape: String,
22 pub path_params: Vec<String>,
23 pub module_name: Option<String>,
24 pub controller_prefix: Option<String>,
25 pub openapi: OpenApiRouteMetadata,
26 pub versioning: RouteVersioning,
27 pub serialization: SerializationOptions,
28 pub metadata: BTreeMap<String, Value>,
29 pub validation_enabled: bool,
30}
31
32#[derive(Debug, Clone, PartialEq)]
34pub struct DiscoveredGateway {
35 pub path: String,
36 pub path_shape: String,
37 pub namespace: Option<String>,
38 pub module_name: Option<String>,
39 pub events: Vec<String>,
40 pub metadata: BTreeMap<String, Value>,
41 pub event_metadata: BTreeMap<String, BTreeMap<String, Value>>,
42}
43
44#[derive(Debug, Clone, PartialEq)]
46pub struct DiscoveredMessagePattern {
47 pub pattern: String,
48 pub kind: MessagePatternKind,
49 pub module_name: Option<String>,
50 pub metadata: BTreeMap<String, Value>,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct ApplicationGraph {
56 pub modules: Vec<ApplicationGraphModule>,
57}
58
59impl ApplicationGraph {
60 pub fn module(&self, name: &str) -> Option<&ApplicationGraphModule> {
61 self.modules.iter().find(|module| module.name == name)
62 }
63
64 pub fn imports_of(&self, name: &str) -> Vec<&ApplicationGraphModule> {
65 let Some(module) = self.module(name) else {
66 return Vec::new();
67 };
68
69 module
70 .imports
71 .iter()
72 .filter_map(|import| self.module(import))
73 .collect()
74 }
75
76 pub fn dependents_of(&self, name: &str) -> Vec<&ApplicationGraphModule> {
77 self.modules
78 .iter()
79 .filter(|module| module.imports.iter().any(|import| import == name))
80 .collect()
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct ApplicationGraphModule {
87 pub name: String,
88 pub imports: Vec<String>,
89 pub provider_tokens: Vec<ProviderToken>,
90 pub export_tokens: Vec<ProviderToken>,
91 pub is_global: bool,
92 pub route_prefix: Option<String>,
93 pub route_count: usize,
94 pub gateway_count: usize,
95 pub message_pattern_count: usize,
96}
97
98#[derive(Debug, Clone, PartialEq)]
100pub struct DiscoveryService {
101 modules: Vec<DiscoveredModule>,
102 routes: Vec<DiscoveredRoute>,
103 gateways: Vec<DiscoveredGateway>,
104 message_patterns: Vec<DiscoveredMessagePattern>,
105 graph: ApplicationGraph,
106}
107
108impl DiscoveryService {
109 pub fn from_app(app: &BootApplication) -> Result<Self> {
110 let modules = discover_modules(app)?;
111 let routes = discover_routes(app);
112 let gateways = discover_gateways(app);
113 let message_patterns = discover_message_patterns(app);
114 let graph = discover_application_graph(app, &routes, &gateways, &message_patterns)?;
115
116 Ok(Self {
117 modules,
118 routes,
119 gateways,
120 message_patterns,
121 graph,
122 })
123 }
124
125 pub fn modules(&self) -> &[DiscoveredModule] {
126 &self.modules
127 }
128
129 pub fn routes(&self) -> &[DiscoveredRoute] {
130 &self.routes
131 }
132
133 pub fn gateways(&self) -> &[DiscoveredGateway] {
134 &self.gateways
135 }
136
137 pub fn message_patterns(&self) -> &[DiscoveredMessagePattern] {
138 &self.message_patterns
139 }
140
141 pub fn graph(&self) -> &ApplicationGraph {
142 &self.graph
143 }
144
145 pub fn module(&self, name: &str) -> Option<&DiscoveredModule> {
146 self.modules.iter().find(|module| module.name == name)
147 }
148
149 pub fn modules_with_provider(&self, token: &ProviderToken) -> Vec<&DiscoveredModule> {
150 self.modules
151 .iter()
152 .filter(|module| module.provider_tokens.contains(token))
153 .collect()
154 }
155
156 pub fn routes_for_module(&self, module_name: &str) -> Vec<&DiscoveredRoute> {
157 self.routes
158 .iter()
159 .filter(|route| route.module_name.as_deref() == Some(module_name))
160 .collect()
161 }
162
163 pub fn routes_for_controller(&self, controller_prefix: &str) -> Vec<&DiscoveredRoute> {
164 self.routes
165 .iter()
166 .filter(|route| route.controller_prefix.as_deref() == Some(controller_prefix))
167 .collect()
168 }
169
170 pub fn gateways_for_module(&self, module_name: &str) -> Vec<&DiscoveredGateway> {
171 self.gateways
172 .iter()
173 .filter(|gateway| gateway.module_name.as_deref() == Some(module_name))
174 .collect()
175 }
176
177 pub fn message_patterns_for_module(&self, module_name: &str) -> Vec<&DiscoveredMessagePattern> {
178 self.message_patterns
179 .iter()
180 .filter(|pattern| pattern.module_name.as_deref() == Some(module_name))
181 .collect()
182 }
183
184 pub fn gateway(&self, path: &str) -> Option<&DiscoveredGateway> {
185 self.gateways.iter().find(|gateway| gateway.path == path)
186 }
187
188 pub fn message_pattern(&self, pattern: &str) -> Option<&DiscoveredMessagePattern> {
189 self.message_patterns
190 .iter()
191 .find(|message_pattern| message_pattern.pattern == pattern)
192 }
193
194 pub fn reflector(&self) -> Reflector {
195 Reflector::new(self.clone())
196 }
197}
198
199#[derive(Debug, Clone, PartialEq)]
201pub struct Reflector {
202 discovery: DiscoveryService,
203}
204
205impl Reflector {
206 pub fn new(discovery: DiscoveryService) -> Self {
207 Self { discovery }
208 }
209
210 pub fn from_app(app: &BootApplication) -> Result<Self> {
211 Ok(Self::new(DiscoveryService::from_app(app)?))
212 }
213
214 pub fn discovery(&self) -> &DiscoveryService {
215 &self.discovery
216 }
217
218 pub fn route(&self, method: HttpMethod, path: &str) -> Option<&DiscoveredRoute> {
219 self.discovery
220 .routes
221 .iter()
222 .find(|route| route.method == method && route.path == path)
223 }
224
225 pub fn openapi(&self, method: HttpMethod, path: &str) -> Option<&OpenApiRouteMetadata> {
226 self.route(method, path).map(|route| &route.openapi)
227 }
228
229 pub fn metadata(&self, method: HttpMethod, path: &str) -> Option<&BTreeMap<String, Value>> {
230 self.route(method, path).map(|route| &route.metadata)
231 }
232
233 pub fn metadata_value(&self, method: HttpMethod, path: &str, key: &str) -> Option<&Value> {
234 self.metadata(method, path)
235 .and_then(|metadata| metadata.get(key))
236 }
237
238 pub fn metadata_as<T>(&self, method: HttpMethod, path: &str, key: &str) -> Result<Option<T>>
239 where
240 T: DeserializeOwned,
241 {
242 let Some(value) = self.metadata_value(method, path, key) else {
243 return Ok(None);
244 };
245
246 serde_json::from_value(value.clone())
247 .map(Some)
248 .map_err(|error| {
249 BootError::Internal(format!(
250 "failed to deserialize route metadata `{key}`: {error}"
251 ))
252 })
253 }
254
255 pub fn operation_id(&self, method: HttpMethod, path: &str) -> Option<&str> {
256 self.openapi(method, path)
257 .and_then(|metadata| metadata.operation_id.as_deref())
258 }
259
260 pub fn routes_with_tag(&self, tag: &str) -> Vec<&DiscoveredRoute> {
261 self.discovery
262 .routes
263 .iter()
264 .filter(|route| route.openapi.tags.iter().any(|value| value == tag))
265 .collect()
266 }
267
268 pub fn routes_with_metadata(&self, key: &str) -> Vec<&DiscoveredRoute> {
269 self.discovery
270 .routes
271 .iter()
272 .filter(|route| route.metadata.contains_key(key))
273 .collect()
274 }
275
276 pub fn routes_with_metadata_value(&self, key: &str, value: &Value) -> Vec<&DiscoveredRoute> {
277 self.discovery
278 .routes
279 .iter()
280 .filter(|route| route.metadata.get(key) == Some(value))
281 .collect()
282 }
283
284 pub fn gateway(&self, path: &str) -> Option<&DiscoveredGateway> {
285 self.discovery.gateway(path)
286 }
287
288 pub fn gateway_metadata(&self, path: &str) -> Option<&BTreeMap<String, Value>> {
289 self.gateway(path).map(|gateway| &gateway.metadata)
290 }
291
292 pub fn gateway_metadata_value(&self, path: &str, key: &str) -> Option<&Value> {
293 self.gateway_metadata(path)
294 .and_then(|metadata| metadata.get(key))
295 }
296
297 pub fn gateway_metadata_as<T>(&self, path: &str, key: &str) -> Result<Option<T>>
298 where
299 T: DeserializeOwned,
300 {
301 let Some(value) = self.gateway_metadata_value(path, key) else {
302 return Ok(None);
303 };
304
305 serde_json::from_value(value.clone())
306 .map(Some)
307 .map_err(|error| {
308 BootError::Internal(format!(
309 "failed to deserialize websocket gateway metadata `{key}`: {error}"
310 ))
311 })
312 }
313
314 pub fn gateway_event_metadata(
315 &self,
316 path: &str,
317 event: &str,
318 ) -> Option<&BTreeMap<String, Value>> {
319 self.gateway(path)
320 .and_then(|gateway| gateway.event_metadata.get(event))
321 }
322
323 pub fn gateway_event_metadata_value(
324 &self,
325 path: &str,
326 event: &str,
327 key: &str,
328 ) -> Option<&Value> {
329 self.gateway_event_metadata(path, event)
330 .and_then(|metadata| metadata.get(key))
331 }
332
333 pub fn gateway_event_metadata_as<T>(
334 &self,
335 path: &str,
336 event: &str,
337 key: &str,
338 ) -> Result<Option<T>>
339 where
340 T: DeserializeOwned,
341 {
342 let Some(value) = self.gateway_event_metadata_value(path, event, key) else {
343 return Ok(None);
344 };
345
346 serde_json::from_value(value.clone())
347 .map(Some)
348 .map_err(|error| {
349 BootError::Internal(format!(
350 "failed to deserialize websocket event metadata `{key}`: {error}"
351 ))
352 })
353 }
354
355 pub fn message_pattern(&self, pattern: &str) -> Option<&DiscoveredMessagePattern> {
356 self.discovery.message_pattern(pattern)
357 }
358
359 pub fn message_pattern_metadata(&self, pattern: &str) -> Option<&BTreeMap<String, Value>> {
360 self.message_pattern(pattern)
361 .map(|message_pattern| &message_pattern.metadata)
362 }
363
364 pub fn message_pattern_metadata_value(&self, pattern: &str, key: &str) -> Option<&Value> {
365 self.message_pattern_metadata(pattern)
366 .and_then(|metadata| metadata.get(key))
367 }
368
369 pub fn message_pattern_metadata_as<T>(&self, pattern: &str, key: &str) -> Result<Option<T>>
370 where
371 T: DeserializeOwned,
372 {
373 let Some(value) = self.message_pattern_metadata_value(pattern, key) else {
374 return Ok(None);
375 };
376
377 serde_json::from_value(value.clone())
378 .map(Some)
379 .map_err(|error| {
380 BootError::Internal(format!(
381 "failed to deserialize message pattern metadata `{key}`: {error}"
382 ))
383 })
384 }
385
386 pub fn routes_for_module(&self, module_name: &str) -> Vec<&DiscoveredRoute> {
387 self.discovery.routes_for_module(module_name)
388 }
389
390 pub fn routes_for_controller(&self, controller_prefix: &str) -> Vec<&DiscoveredRoute> {
391 self.discovery.routes_for_controller(controller_prefix)
392 }
393}
394
395fn discover_modules(app: &BootApplication) -> Result<Vec<DiscoveredModule>> {
396 app.module_instances
397 .iter()
398 .map(|instance| {
399 Ok(DiscoveredModule {
400 name: instance.module.name().to_string(),
401 provider_tokens: instance.module_ref.local_tokens()?,
402 })
403 })
404 .collect()
405}
406
407fn discover_application_graph(
408 app: &BootApplication,
409 routes: &[DiscoveredRoute],
410 gateways: &[DiscoveredGateway],
411 message_patterns: &[DiscoveredMessagePattern],
412) -> Result<ApplicationGraph> {
413 let mut modules = Vec::new();
414
415 for instance in &app.module_instances {
416 let name = instance.module.name().to_string();
417 let route_count = routes
418 .iter()
419 .filter(|route| route.module_name.as_deref() == Some(name.as_str()))
420 .count();
421 let gateway_count = gateways
422 .iter()
423 .filter(|gateway| gateway.module_name.as_deref() == Some(name.as_str()))
424 .count();
425 let message_pattern_count = message_patterns
426 .iter()
427 .filter(|pattern| pattern.module_name.as_deref() == Some(name.as_str()))
428 .count();
429
430 modules.push(ApplicationGraphModule {
431 name,
432 imports: instance.imports.clone(),
433 provider_tokens: instance.module_ref.local_tokens()?,
434 export_tokens: instance.exports.clone(),
435 is_global: instance.is_global,
436 route_prefix: instance.route_prefix.clone(),
437 route_count,
438 gateway_count,
439 message_pattern_count,
440 });
441 }
442
443 Ok(ApplicationGraph { modules })
444}
445
446fn discover_routes(app: &BootApplication) -> Vec<DiscoveredRoute> {
447 app.routes()
448 .iter()
449 .map(|route| DiscoveredRoute {
450 method: route.method(),
451 path: route.path().to_string(),
452 path_shape: route.path_shape(),
453 path_params: route
454 .path_param_names()
455 .into_iter()
456 .map(str::to_string)
457 .collect(),
458 module_name: route.module_name().map(str::to_string),
459 controller_prefix: route.controller_prefix().map(str::to_string),
460 openapi: route.openapi().clone(),
461 versioning: route.versioning().clone(),
462 serialization: route.serialization().clone(),
463 metadata: route.metadata().clone(),
464 validation_enabled: route.validation_enabled(),
465 })
466 .collect()
467}
468
469fn discover_gateways(app: &BootApplication) -> Vec<DiscoveredGateway> {
470 app.gateways()
471 .iter()
472 .map(|gateway| DiscoveredGateway {
473 path: gateway.path().to_string(),
474 path_shape: gateway.path_shape(),
475 namespace: gateway.namespace().map(str::to_string),
476 module_name: gateway.module_name().map(str::to_string),
477 events: gateway.events().into_iter().map(str::to_string).collect(),
478 metadata: gateway.metadata().clone(),
479 event_metadata: gateway
480 .events()
481 .into_iter()
482 .filter_map(|event| {
483 gateway
484 .event_metadata(event)
485 .map(|metadata| (event.to_string(), metadata.clone()))
486 })
487 .collect(),
488 })
489 .collect()
490}
491
492fn discover_message_patterns(app: &BootApplication) -> Vec<DiscoveredMessagePattern> {
493 app.message_patterns()
494 .iter()
495 .map(|pattern| DiscoveredMessagePattern {
496 pattern: pattern.pattern().to_string(),
497 kind: pattern.kind(),
498 module_name: pattern.module_name().map(str::to_string),
499 metadata: pattern.metadata().clone(),
500 })
501 .collect()
502}