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