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::{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(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}