1use crate::pipeline::PipelineComponent;
2use crate::{
3 BootError, ExceptionFilter, Guard, HttpMethod, Interceptor, Middleware, OpenApiRouteMetadata,
4 Pipe, RequestValidator, Result, RouteVersioning, SerializationOptions, ValidationOptions,
5};
6use serde::de::DeserializeOwned;
7use serde::Serialize;
8use serde_json::Value;
9use std::collections::BTreeMap;
10use std::sync::Arc;
11#[cfg(feature = "cache")]
12use std::time::Duration;
13
14use crate::routing::handler::{RequestScopedRouteHandler, RouteHandler};
15use crate::routing::host::{
16 host_param_names, host_shape_key, host_specificity, match_host_params, match_host_shape,
17 validate_host_pattern,
18};
19#[cfg(feature = "axum")]
20use crate::routing::path::has_catch_all;
21use crate::routing::path::{
22 match_path_params, match_path_shape, route_param_names, route_shape_key, validate_route_path,
23};
24use crate::ModuleRef;
25
26#[derive(Clone)]
28pub struct RouteDefinition {
29 pub(super) method: HttpMethod,
30 pub(super) path: String,
31 pub(super) host: Option<String>,
32 pub(super) handler: Arc<dyn RouteHandler>,
33 pub(super) middleware: Vec<Arc<dyn Middleware>>,
34 pub(super) pipes: Vec<PipelineComponent<dyn Pipe>>,
35 pub(super) guards: Vec<PipelineComponent<dyn Guard>>,
36 pub(super) interceptors: Vec<PipelineComponent<dyn Interceptor>>,
37 pub(super) filters: Vec<PipelineComponent<dyn ExceptionFilter>>,
38 pub(super) validators: Vec<RequestValidator>,
39 pub(super) validation_enabled: bool,
40 pub(super) validation_disabled: bool,
41 pub(super) validation_options: ValidationOptions,
42 pub(super) module_name: Option<String>,
43 pub(super) controller_prefix: Option<String>,
44 pub(super) module_ref: Option<ModuleRef>,
45 pub(super) openapi: OpenApiRouteMetadata,
46 pub(super) versioning: RouteVersioning,
47 pub(super) serialization: SerializationOptions,
48 pub(super) metadata: BTreeMap<String, Value>,
49}
50
51impl RouteDefinition {
52 pub fn new<H>(method: HttpMethod, path: impl Into<String>, handler: H) -> Result<Self>
53 where
54 H: RouteHandler,
55 {
56 let path = path.into();
57 validate_route_path(&path)?;
58 Ok(Self {
59 method,
60 path,
61 host: None,
62 handler: Arc::new(handler),
63 middleware: Vec::new(),
64 pipes: Vec::new(),
65 guards: Vec::new(),
66 interceptors: Vec::new(),
67 filters: Vec::new(),
68 validators: Vec::new(),
69 validation_enabled: false,
70 validation_disabled: false,
71 validation_options: ValidationOptions::default(),
72 module_name: None,
73 controller_prefix: None,
74 module_ref: None,
75 openapi: OpenApiRouteMetadata::default(),
76 versioning: RouteVersioning::default(),
77 serialization: SerializationOptions::default(),
78 metadata: BTreeMap::new(),
79 })
80 }
81
82 pub fn new_scoped<F, H>(method: HttpMethod, path: impl Into<String>, factory: F) -> Result<Self>
83 where
84 F: Fn(&ModuleRef) -> Result<H> + Send + Sync + 'static,
85 H: RouteHandler,
86 {
87 Self::new(method, path, RequestScopedRouteHandler::new(factory))
88 }
89
90 pub fn method(&self) -> HttpMethod {
91 self.method
92 }
93
94 pub fn path(&self) -> &str {
95 &self.path
96 }
97
98 pub fn host(&self) -> Option<&str> {
99 self.host.as_deref()
100 }
101
102 pub fn path_shape(&self) -> String {
103 route_shape_key(&self.path)
104 }
105
106 pub fn host_shape(&self) -> Option<String> {
107 self.host.as_deref().map(host_shape_key)
108 }
109
110 pub fn path_param_names(&self) -> Vec<&str> {
111 route_param_names(&self.path)
112 }
113
114 pub fn host_param_names(&self) -> Vec<&str> {
115 self.host
116 .as_deref()
117 .map(host_param_names)
118 .unwrap_or_default()
119 }
120
121 pub fn matches_path(&self, path: &str) -> bool {
122 match_path_shape(&self.path, path)
123 }
124
125 #[cfg(feature = "axum")]
126 pub(crate) fn has_catch_all_path(&self) -> bool {
127 has_catch_all(&self.path)
128 }
129
130 pub fn matches_host(&self, host: Option<&str>) -> bool {
131 match &self.host {
132 Some(pattern) => host.is_some_and(|host| match_host_shape(pattern, host)),
133 None => true,
134 }
135 }
136
137 pub fn path_params(&self, path: &str) -> Result<Option<BTreeMap<String, String>>> {
138 match_path_params(&self.path, path)
139 }
140
141 pub fn host_params(&self, host: Option<&str>) -> Result<Option<BTreeMap<String, String>>> {
142 match (&self.host, host) {
143 (Some(pattern), Some(host)) => match_host_params(pattern, host),
144 (Some(_), None) => Ok(None),
145 (None, _) => Ok(Some(BTreeMap::new())),
146 }
147 }
148
149 pub fn module_name(&self) -> Option<&str> {
150 self.module_name.as_deref()
151 }
152
153 pub fn controller_prefix(&self) -> Option<&str> {
154 self.controller_prefix.as_deref()
155 }
156
157 pub fn handler(&self) -> Arc<dyn RouteHandler> {
158 Arc::clone(&self.handler)
159 }
160
161 pub fn openapi(&self) -> &OpenApiRouteMetadata {
162 &self.openapi
163 }
164
165 pub fn versioning(&self) -> &RouteVersioning {
166 &self.versioning
167 }
168
169 pub fn serialization(&self) -> &SerializationOptions {
170 &self.serialization
171 }
172
173 pub fn metadata(&self) -> &BTreeMap<String, Value> {
174 &self.metadata
175 }
176
177 pub fn metadata_value(&self, key: &str) -> Option<&Value> {
178 self.metadata.get(key)
179 }
180
181 pub fn metadata_as<T>(&self, key: &str) -> Result<Option<T>>
182 where
183 T: DeserializeOwned,
184 {
185 let Some(value) = self.metadata.get(key) else {
186 return Ok(None);
187 };
188
189 serde_json::from_value(value.clone())
190 .map(Some)
191 .map_err(|error| {
192 BootError::Internal(format!(
193 "failed to deserialize route metadata `{key}`: {error}"
194 ))
195 })
196 }
197
198 pub fn validation_enabled(&self) -> bool {
199 self.validation_enabled
200 }
201
202 pub fn with_host(mut self, pattern: impl Into<String>) -> Result<Self> {
203 let pattern = pattern.into();
204 validate_host_pattern(&pattern)?;
205 self.host = Some(pattern);
206 Ok(self)
207 }
208
209 pub fn without_host(mut self) -> Self {
210 self.host = None;
211 self
212 }
213
214 pub fn with_version(mut self, version: impl Into<String>) -> Self {
215 self.versioning = RouteVersioning::version(version);
216 self
217 }
218
219 pub fn with_versions<I, V>(mut self, versions: I) -> Self
220 where
221 I: IntoIterator<Item = V>,
222 V: Into<String>,
223 {
224 self.versioning = RouteVersioning::versions(versions);
225 self
226 }
227
228 pub fn version_neutral(mut self) -> Self {
229 self.versioning = RouteVersioning::neutral();
230 self
231 }
232
233 pub fn with_serialization(mut self, options: SerializationOptions) -> Self {
234 self.serialization = options;
235 self
236 }
237
238 pub fn with_metadata<V>(self, key: impl Into<String>, value: V) -> Result<Self>
239 where
240 V: Serialize,
241 {
242 let key = key.into();
243 let value = serde_json::to_value(value).map_err(|error| {
244 BootError::Internal(format!(
245 "failed to serialize route metadata `{key}`: {error}"
246 ))
247 })?;
248 Ok(self.with_metadata_value(key, value))
249 }
250
251 pub fn with_metadata_value(mut self, key: impl Into<String>, value: Value) -> Self {
252 self.metadata.insert(key.into(), value);
253 self
254 }
255
256 #[cfg(feature = "cache")]
257 pub fn with_cache_key(self, key: impl Into<String>) -> Self {
258 self.with_metadata_value(crate::CACHE_KEY_METADATA, Value::String(key.into()))
259 }
260
261 #[cfg(feature = "cache")]
262 pub fn with_cache_ttl(self, ttl: Duration) -> Self {
263 self.with_metadata_value(
264 crate::CACHE_TTL_METADATA,
265 Value::Number(serde_json::Number::from(ttl.as_millis() as u64)),
266 )
267 }
268
269 #[cfg(feature = "cache")]
270 pub fn without_cache(self) -> Self {
271 self.with_metadata_value(crate::CACHE_DISABLED_METADATA, Value::Bool(true))
272 }
273
274 pub(crate) fn with_metadata_defaults(mut self, metadata: &BTreeMap<String, Value>) -> Self {
275 for (key, value) in metadata {
276 self.metadata
277 .entry(key.clone())
278 .or_insert_with(|| value.clone());
279 }
280 self
281 }
282
283 pub(crate) fn with_metadata_default_value(
284 mut self,
285 key: impl Into<String>,
286 value: Value,
287 ) -> Self {
288 self.metadata.entry(key.into()).or_insert(value);
289 self
290 }
291
292 pub(crate) fn with_host_default(mut self, host: Option<&str>) -> Result<Self> {
293 if self.host.is_none() {
294 if let Some(host) = host {
295 self = self.with_host(host.to_string())?;
296 }
297 }
298 Ok(self)
299 }
300
301 pub(crate) fn host_shape_key(&self) -> Option<String> {
302 self.host.as_deref().map(host_shape_key)
303 }
304
305 pub(crate) fn host_specificity(&self) -> Vec<u8> {
306 host_specificity(self.host.as_deref())
307 }
308}