Skip to main content

a3s_boot/routing/route/
definition.rs

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::{ProviderRouteHandler, 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/// A framework-neutral route definition.
27#[derive(Clone)]
28pub struct RouteDefinition {
29    pub(super) method: HttpMethod,
30    pub(super) path: String,
31    pub(super) host: Option<String>,
32    pub(crate) 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    /// Build a route whose handler resolves a provider from the current request
91    /// scope before constructing the concrete route handler.
92    pub fn new_provider<T, F>(
93        method: HttpMethod,
94        path: impl Into<String>,
95        factory: F,
96    ) -> Result<Self>
97    where
98        T: Send + Sync + 'static,
99        F: Fn(Arc<T>) -> Result<RouteDefinition> + Send + Sync + 'static,
100    {
101        Self::new(method, path, ProviderRouteHandler::<T, F>::new(factory))
102    }
103
104    pub fn method(&self) -> HttpMethod {
105        self.method
106    }
107
108    pub fn path(&self) -> &str {
109        &self.path
110    }
111
112    pub fn host(&self) -> Option<&str> {
113        self.host.as_deref()
114    }
115
116    pub fn path_shape(&self) -> String {
117        route_shape_key(&self.path)
118    }
119
120    pub fn host_shape(&self) -> Option<String> {
121        self.host.as_deref().map(host_shape_key)
122    }
123
124    pub fn path_param_names(&self) -> Vec<&str> {
125        route_param_names(&self.path)
126    }
127
128    pub fn host_param_names(&self) -> Vec<&str> {
129        self.host
130            .as_deref()
131            .map(host_param_names)
132            .unwrap_or_default()
133    }
134
135    pub fn matches_path(&self, path: &str) -> bool {
136        match_path_shape(&self.path, path)
137    }
138
139    #[cfg(feature = "axum")]
140    pub(crate) fn has_catch_all_path(&self) -> bool {
141        has_catch_all(&self.path)
142    }
143
144    pub fn matches_host(&self, host: Option<&str>) -> bool {
145        match &self.host {
146            Some(pattern) => host.is_some_and(|host| match_host_shape(pattern, host)),
147            None => true,
148        }
149    }
150
151    pub fn path_params(&self, path: &str) -> Result<Option<BTreeMap<String, String>>> {
152        match_path_params(&self.path, path)
153    }
154
155    pub fn host_params(&self, host: Option<&str>) -> Result<Option<BTreeMap<String, String>>> {
156        match (&self.host, host) {
157            (Some(pattern), Some(host)) => match_host_params(pattern, host),
158            (Some(_), None) => Ok(None),
159            (None, _) => Ok(Some(BTreeMap::new())),
160        }
161    }
162
163    pub fn module_name(&self) -> Option<&str> {
164        self.module_name.as_deref()
165    }
166
167    pub fn controller_prefix(&self) -> Option<&str> {
168        self.controller_prefix.as_deref()
169    }
170
171    pub fn handler(&self) -> Arc<dyn RouteHandler> {
172        Arc::clone(&self.handler)
173    }
174
175    pub fn openapi(&self) -> &OpenApiRouteMetadata {
176        &self.openapi
177    }
178
179    pub fn versioning(&self) -> &RouteVersioning {
180        &self.versioning
181    }
182
183    pub fn serialization(&self) -> &SerializationOptions {
184        &self.serialization
185    }
186
187    pub fn metadata(&self) -> &BTreeMap<String, Value> {
188        &self.metadata
189    }
190
191    pub fn metadata_value(&self, key: &str) -> Option<&Value> {
192        self.metadata.get(key)
193    }
194
195    pub fn metadata_as<T>(&self, key: &str) -> Result<Option<T>>
196    where
197        T: DeserializeOwned,
198    {
199        let Some(value) = self.metadata.get(key) else {
200            return Ok(None);
201        };
202
203        serde_json::from_value(value.clone())
204            .map(Some)
205            .map_err(|error| {
206                BootError::Internal(format!(
207                    "failed to deserialize route metadata `{key}`: {error}"
208                ))
209            })
210    }
211
212    pub fn validation_enabled(&self) -> bool {
213        self.validation_enabled
214    }
215
216    pub fn with_host(mut self, pattern: impl Into<String>) -> Result<Self> {
217        let pattern = pattern.into();
218        validate_host_pattern(&pattern)?;
219        self.host = Some(pattern);
220        Ok(self)
221    }
222
223    pub fn without_host(mut self) -> Self {
224        self.host = None;
225        self
226    }
227
228    pub fn with_version(mut self, version: impl Into<String>) -> Self {
229        self.versioning = RouteVersioning::version(version);
230        self
231    }
232
233    pub fn with_versions<I, V>(mut self, versions: I) -> Self
234    where
235        I: IntoIterator<Item = V>,
236        V: Into<String>,
237    {
238        self.versioning = RouteVersioning::versions(versions);
239        self
240    }
241
242    pub fn version_neutral(mut self) -> Self {
243        self.versioning = RouteVersioning::neutral();
244        self
245    }
246
247    pub fn with_serialization(mut self, options: SerializationOptions) -> Self {
248        self.serialization = options;
249        self
250    }
251
252    pub fn with_metadata<V>(self, key: impl Into<String>, value: V) -> Result<Self>
253    where
254        V: Serialize,
255    {
256        let key = key.into();
257        let value = serde_json::to_value(value).map_err(|error| {
258            BootError::Internal(format!(
259                "failed to serialize route metadata `{key}`: {error}"
260            ))
261        })?;
262        Ok(self.with_metadata_value(key, value))
263    }
264
265    pub fn with_metadata_value(mut self, key: impl Into<String>, value: Value) -> Self {
266        self.metadata.insert(key.into(), value);
267        self
268    }
269
270    #[cfg(feature = "cache")]
271    pub fn with_cache_key(self, key: impl Into<String>) -> Self {
272        self.with_metadata_value(crate::CACHE_KEY_METADATA, Value::String(key.into()))
273    }
274
275    #[cfg(feature = "cache")]
276    pub fn with_cache_ttl(self, ttl: Duration) -> Self {
277        self.with_metadata_value(
278            crate::CACHE_TTL_METADATA,
279            Value::Number(serde_json::Number::from(ttl.as_millis() as u64)),
280        )
281    }
282
283    #[cfg(feature = "cache")]
284    pub fn without_cache(self) -> Self {
285        self.with_metadata_value(crate::CACHE_DISABLED_METADATA, Value::Bool(true))
286    }
287
288    pub(crate) fn with_metadata_defaults(mut self, metadata: &BTreeMap<String, Value>) -> Self {
289        for (key, value) in metadata {
290            self.metadata
291                .entry(key.clone())
292                .or_insert_with(|| value.clone());
293        }
294        self
295    }
296
297    pub(crate) fn with_metadata_default_value(
298        mut self,
299        key: impl Into<String>,
300        value: Value,
301    ) -> Self {
302        self.metadata.entry(key.into()).or_insert(value);
303        self
304    }
305
306    pub(crate) fn with_host_default(mut self, host: Option<&str>) -> Result<Self> {
307        if self.host.is_none() {
308            if let Some(host) = host {
309                self = self.with_host(host.to_string())?;
310            }
311        }
312        Ok(self)
313    }
314
315    pub(crate) fn host_shape_key(&self) -> Option<String> {
316        self.host.as_deref().map(host_shape_key)
317    }
318
319    pub(crate) fn host_specificity(&self) -> Vec<u8> {
320        host_specificity(self.host.as_deref())
321    }
322}