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, Eq)]
34pub struct DiscoveredGateway {
35 pub path: String,
36 pub path_shape: String,
37 pub module_name: Option<String>,
38 pub events: Vec<String>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct DiscoveredMessagePattern {
44 pub pattern: String,
45 pub kind: MessagePatternKind,
46 pub module_name: Option<String>,
47}
48
49#[derive(Debug, Clone, PartialEq)]
51pub struct DiscoveryService {
52 modules: Vec<DiscoveredModule>,
53 routes: Vec<DiscoveredRoute>,
54 gateways: Vec<DiscoveredGateway>,
55 message_patterns: Vec<DiscoveredMessagePattern>,
56}
57
58impl DiscoveryService {
59 pub fn from_app(app: &BootApplication) -> Result<Self> {
60 Ok(Self {
61 modules: discover_modules(app)?,
62 routes: discover_routes(app),
63 gateways: discover_gateways(app),
64 message_patterns: discover_message_patterns(app),
65 })
66 }
67
68 pub fn modules(&self) -> &[DiscoveredModule] {
69 &self.modules
70 }
71
72 pub fn routes(&self) -> &[DiscoveredRoute] {
73 &self.routes
74 }
75
76 pub fn gateways(&self) -> &[DiscoveredGateway] {
77 &self.gateways
78 }
79
80 pub fn message_patterns(&self) -> &[DiscoveredMessagePattern] {
81 &self.message_patterns
82 }
83
84 pub fn module(&self, name: &str) -> Option<&DiscoveredModule> {
85 self.modules.iter().find(|module| module.name == name)
86 }
87
88 pub fn modules_with_provider(&self, token: &ProviderToken) -> Vec<&DiscoveredModule> {
89 self.modules
90 .iter()
91 .filter(|module| module.provider_tokens.contains(token))
92 .collect()
93 }
94
95 pub fn routes_for_module(&self, module_name: &str) -> Vec<&DiscoveredRoute> {
96 self.routes
97 .iter()
98 .filter(|route| route.module_name.as_deref() == Some(module_name))
99 .collect()
100 }
101
102 pub fn routes_for_controller(&self, controller_prefix: &str) -> Vec<&DiscoveredRoute> {
103 self.routes
104 .iter()
105 .filter(|route| route.controller_prefix.as_deref() == Some(controller_prefix))
106 .collect()
107 }
108
109 pub fn gateways_for_module(&self, module_name: &str) -> Vec<&DiscoveredGateway> {
110 self.gateways
111 .iter()
112 .filter(|gateway| gateway.module_name.as_deref() == Some(module_name))
113 .collect()
114 }
115
116 pub fn message_patterns_for_module(&self, module_name: &str) -> Vec<&DiscoveredMessagePattern> {
117 self.message_patterns
118 .iter()
119 .filter(|pattern| pattern.module_name.as_deref() == Some(module_name))
120 .collect()
121 }
122
123 pub fn reflector(&self) -> Reflector {
124 Reflector::new(self.clone())
125 }
126}
127
128#[derive(Debug, Clone, PartialEq)]
130pub struct Reflector {
131 discovery: DiscoveryService,
132}
133
134impl Reflector {
135 pub fn new(discovery: DiscoveryService) -> Self {
136 Self { discovery }
137 }
138
139 pub fn from_app(app: &BootApplication) -> Result<Self> {
140 Ok(Self::new(DiscoveryService::from_app(app)?))
141 }
142
143 pub fn discovery(&self) -> &DiscoveryService {
144 &self.discovery
145 }
146
147 pub fn route(&self, method: HttpMethod, path: &str) -> Option<&DiscoveredRoute> {
148 self.discovery
149 .routes
150 .iter()
151 .find(|route| route.method == method && route.path == path)
152 }
153
154 pub fn openapi(&self, method: HttpMethod, path: &str) -> Option<&OpenApiRouteMetadata> {
155 self.route(method, path).map(|route| &route.openapi)
156 }
157
158 pub fn metadata(&self, method: HttpMethod, path: &str) -> Option<&BTreeMap<String, Value>> {
159 self.route(method, path).map(|route| &route.metadata)
160 }
161
162 pub fn metadata_value(&self, method: HttpMethod, path: &str, key: &str) -> Option<&Value> {
163 self.metadata(method, path)
164 .and_then(|metadata| metadata.get(key))
165 }
166
167 pub fn metadata_as<T>(&self, method: HttpMethod, path: &str, key: &str) -> Result<Option<T>>
168 where
169 T: DeserializeOwned,
170 {
171 let Some(value) = self.metadata_value(method, path, key) else {
172 return Ok(None);
173 };
174
175 serde_json::from_value(value.clone())
176 .map(Some)
177 .map_err(|error| {
178 BootError::Internal(format!(
179 "failed to deserialize route metadata `{key}`: {error}"
180 ))
181 })
182 }
183
184 pub fn operation_id(&self, method: HttpMethod, path: &str) -> Option<&str> {
185 self.openapi(method, path)
186 .and_then(|metadata| metadata.operation_id.as_deref())
187 }
188
189 pub fn routes_with_tag(&self, tag: &str) -> Vec<&DiscoveredRoute> {
190 self.discovery
191 .routes
192 .iter()
193 .filter(|route| route.openapi.tags.iter().any(|value| value == tag))
194 .collect()
195 }
196
197 pub fn routes_with_metadata(&self, key: &str) -> Vec<&DiscoveredRoute> {
198 self.discovery
199 .routes
200 .iter()
201 .filter(|route| route.metadata.contains_key(key))
202 .collect()
203 }
204
205 pub fn routes_with_metadata_value(&self, key: &str, value: &Value) -> Vec<&DiscoveredRoute> {
206 self.discovery
207 .routes
208 .iter()
209 .filter(|route| route.metadata.get(key) == Some(value))
210 .collect()
211 }
212
213 pub fn routes_for_module(&self, module_name: &str) -> Vec<&DiscoveredRoute> {
214 self.discovery.routes_for_module(module_name)
215 }
216
217 pub fn routes_for_controller(&self, controller_prefix: &str) -> Vec<&DiscoveredRoute> {
218 self.discovery.routes_for_controller(controller_prefix)
219 }
220}
221
222fn discover_modules(app: &BootApplication) -> Result<Vec<DiscoveredModule>> {
223 app.module_instances
224 .iter()
225 .map(|instance| {
226 Ok(DiscoveredModule {
227 name: instance.module.name().to_string(),
228 provider_tokens: instance.module_ref.local_tokens()?,
229 })
230 })
231 .collect()
232}
233
234fn discover_routes(app: &BootApplication) -> Vec<DiscoveredRoute> {
235 app.routes()
236 .iter()
237 .map(|route| DiscoveredRoute {
238 method: route.method(),
239 path: route.path().to_string(),
240 path_shape: route.path_shape(),
241 path_params: route
242 .path_param_names()
243 .into_iter()
244 .map(str::to_string)
245 .collect(),
246 module_name: route.module_name().map(str::to_string),
247 controller_prefix: route.controller_prefix().map(str::to_string),
248 openapi: route.openapi().clone(),
249 versioning: route.versioning().clone(),
250 serialization: route.serialization().clone(),
251 metadata: route.metadata().clone(),
252 validation_enabled: route.validation_enabled(),
253 })
254 .collect()
255}
256
257fn discover_gateways(app: &BootApplication) -> Vec<DiscoveredGateway> {
258 app.gateways()
259 .iter()
260 .map(|gateway| DiscoveredGateway {
261 path: gateway.path().to_string(),
262 path_shape: gateway.path_shape(),
263 module_name: gateway.module_name().map(str::to_string),
264 events: gateway.events().into_iter().map(str::to_string).collect(),
265 })
266 .collect()
267}
268
269fn discover_message_patterns(app: &BootApplication) -> Vec<DiscoveredMessagePattern> {
270 app.message_patterns()
271 .iter()
272 .map(|pattern| DiscoveredMessagePattern {
273 pattern: pattern.pattern().to_string(),
274 kind: pattern.kind(),
275 module_name: pattern.module_name().map(str::to_string),
276 })
277 .collect()
278}