@lithia-js/native 1.0.0-canary.2 → 1.0.0-canary.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,78 +0,0 @@
1
- use regex::Regex;
2
-
3
- pub trait PathTransformer {
4
- /// Normalize a file path by removing extensions, groups, and normalizing
5
- /// separators.
6
- fn normalize(&self, path: &str) -> String;
7
-
8
- /// Normalize a path and apply an optional global prefix, returning a
9
- /// value that always starts with a leading slash.
10
- fn normalize_path(&self, path: &str, global_prefix: &str) -> String;
11
-
12
- /// Clone the transformer as a boxed trait object.
13
- fn clone_box(&self) -> Box<dyn PathTransformer>;
14
- }
15
-
16
- #[derive(Clone)]
17
- pub struct NativeEventTransformer {
18
- remove_ext: Regex,
19
- remove_groups: Regex,
20
- }
21
-
22
- impl NativeEventTransformer {
23
- pub fn new() -> Self {
24
- Self {
25
- remove_ext: Regex::new(r"\.[A-Za-z0-9]+$").unwrap(),
26
- remove_groups: Regex::new(r"\(([^(/\\]+)\)[/\\]").unwrap(),
27
- }
28
- }
29
- }
30
-
31
- impl PathTransformer for NativeEventTransformer {
32
- /// Normalize an event file path: remove extension, remove groups and
33
- /// normalize separators to `/`.
34
- fn normalize(&self, path: &str) -> String {
35
- let mut s = path.to_string();
36
- s = self.remove_ext.replace(&s, "").to_string();
37
- s = self.remove_groups.replace_all(&s, "").to_string();
38
- s.replace('\\', "/")
39
- }
40
-
41
- /// Normalize a path and apply an optional global prefix, returning a
42
- /// value that always starts with a leading slash.
43
- fn normalize_path(&self, path: &str, global_prefix: &str) -> String {
44
- let combined = if global_prefix.is_empty() {
45
- path.to_string()
46
- } else {
47
- format!("{}/{}", global_prefix.trim_end_matches('/'), path.trim_start_matches('/'))
48
- };
49
-
50
- let no_trailing = if combined.ends_with('/') && combined.len() > 1 {
51
- combined.trim_end_matches('/').to_string()
52
- } else {
53
- combined
54
- };
55
-
56
- if no_trailing.starts_with('/') {
57
- no_trailing
58
- } else {
59
- format!("/{}", no_trailing)
60
- }
61
- }
62
-
63
- fn clone_box(&self) -> Box<dyn PathTransformer> {
64
- Box::new(self.clone())
65
- }
66
- }
67
-
68
- #[cfg(test)]
69
- mod tests {
70
- use super::*;
71
-
72
- #[test]
73
- fn normalize_basic() {
74
- let t = NativeEventTransformer::new();
75
- assert_eq!(t.normalize("app/events/chat/message.ts"), "app/events/chat/message");
76
- assert_eq!(t.normalize("(v1)/events/connection.ts"), "events/connection");
77
- }
78
- }
package/src/lib.rs DELETED
@@ -1,54 +0,0 @@
1
- //! N-API bindings exported by the native crate.
2
- //!
3
- //! This module exposes a small surface area to the host (Node) so JavaScript
4
- //! code can invoke the native builder and scanner implemented in Rust.
5
- //! Prefer using the higher-level functions exported here rather than calling
6
- //! into the modules directly from the host.
7
-
8
- use napi_derive::napi;
9
-
10
- mod builder;
11
- mod router;
12
- mod scanner;
13
- mod events;
14
-
15
- /// Compile the project and emit artifacts.
16
- ///
17
- /// This re-export exposes the native `build_project` entrypoint implemented
18
- /// in the `builder` module. The function is intended to be invoked from the
19
- /// host via N-API and performs scanning, compilation and manifest emission.
20
- pub use builder::build_project;
21
-
22
- /// Route and manifest types produced by the native router processor.
23
- pub use router::{Route, RoutesManifest};
24
-
25
- /// Scanner types returned by `scan_dir`.
26
- pub use scanner::{FileInfo, ScanOptions};
27
-
28
- use crate::scanner::FileScanner;
29
-
30
- /// Scan a directory tree for files matching the provided `path_components`.
31
- ///
32
- /// `path_components` is a vector of path segments used as roots for the
33
- /// scanner (for example `["examples/1-basic-api"]`). `options` may include
34
- /// include/ignore globs. Returns a list of `FileInfo` describing discovered
35
- /// files or an error which is converted into a `napi::Error` for the host.
36
- #[napi]
37
- pub fn scan_dir(
38
- path_components: Vec<String>,
39
- options: Option<ScanOptions>,
40
- ) -> napi::Result<Vec<FileInfo>> {
41
- let scanner = scanner::NativeFileScanner::new();
42
- scanner
43
- .scan_dir(&path_components, options)
44
- .map_err(|e| napi::Error::from_reason(format!("scan failed: {}", e)))
45
- }
46
-
47
- /// Return the native crate version embedded at compile time.
48
- ///
49
- /// This returns the value of `CARGO_PKG_VERSION` so the host can verify the
50
- /// native binary version matches expectations.
51
- #[napi]
52
- pub fn schema_version() -> &'static str {
53
- env!("CARGO_PKG_VERSION")
54
- }
@@ -1,239 +0,0 @@
1
- //! Route filename conventions and helpers.
2
- //!
3
- //! This module defines how route filenames are interpreted by the framework.
4
- //! It provides:
5
- //! - `RouteConvention` trait used to normalize filesystem paths and extract
6
- //! optional HTTP method suffixes.
7
- //! - `NativeRouteConvention` default implementation that follows the project's
8
- //! filename conventions (e.g. `route.get.ts`, route groups, dynamic
9
- //! segments).
10
- //!
11
- //! The convention is intentionally small and pluggable so the routing
12
- //! behaviour can be adapted for different project layouts.
13
-
14
- use regex::Regex;
15
-
16
- use crate::router::transformer::{NativePathTransformer, PathTransformer};
17
-
18
- /// Trait that defines how filenames are converted into runtime route paths.
19
- /// Implementors should remove route-specific filename suffixes and perform
20
- /// any normalization required before the `PathTransformer` is applied.
21
- pub trait RouteConvention {
22
- /// Transform a filesystem location into a normalized route path.
23
- ///
24
- /// The returned path should include a leading `/`.
25
- fn transform_path(&self, path: &str) -> String;
26
-
27
- /// Extract an optional HTTP method suffix from a filename and return the
28
- /// sanitized path alongside the detected method.
29
- fn extract_method(&self, path: &str) -> ExtractedMethod;
30
- }
31
-
32
- /// Supported HTTP method suffixes that can be encoded in filenames.
33
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
34
- pub enum MatchedMethodSuffix {
35
- Delete,
36
- Get,
37
- Head,
38
- Options,
39
- Patch,
40
- Post,
41
- Put,
42
- }
43
-
44
- impl MatchedMethodSuffix {
45
- /// Parse a method name (case-insensitive) into a `MatchedMethodSuffix`.
46
- pub fn from_str(method: &str) -> Option<Self> {
47
- match method.to_lowercase().as_str() {
48
- "delete" => Some(MatchedMethodSuffix::Delete),
49
- "get" => Some(MatchedMethodSuffix::Get),
50
- "head" => Some(MatchedMethodSuffix::Head),
51
- "options" => Some(MatchedMethodSuffix::Options),
52
- "patch" => Some(MatchedMethodSuffix::Patch),
53
- "post" => Some(MatchedMethodSuffix::Post),
54
- "put" => Some(MatchedMethodSuffix::Put),
55
- _ => None,
56
- }
57
- }
58
-
59
- /// Return the uppercase HTTP method string (e.g. `GET`).
60
- pub fn as_str(&self) -> &'static str {
61
- match self {
62
- MatchedMethodSuffix::Delete => "DELETE",
63
- MatchedMethodSuffix::Get => "GET",
64
- MatchedMethodSuffix::Head => "HEAD",
65
- MatchedMethodSuffix::Options => "OPTIONS",
66
- MatchedMethodSuffix::Patch => "PATCH",
67
- MatchedMethodSuffix::Post => "POST",
68
- MatchedMethodSuffix::Put => "PUT",
69
- }
70
- }
71
- }
72
-
73
- /// Result of extracting an optional method suffix from a filename.
74
- #[derive(Debug)]
75
- pub struct ExtractedMethod {
76
- /// Detected method if the filename contained a method suffix.
77
- pub method: Option<MatchedMethodSuffix>,
78
-
79
- /// The sanitized path with the method suffix removed. Always starts with
80
- /// a leading `/`.
81
- pub updated_path: String,
82
- }
83
-
84
- /// Default route filename convention implementation.
85
- /// Recognizes filenames like `/users/route.ts` and `/users/route.post.ts` and
86
- /// removes the convention-specific suffixes. It delegates general path
87
- /// transformations (groups, dynamic segments, extension removal) to a
88
- /// `PathTransformer`.
89
- pub struct NativeRouteConvention {
90
- route_regex: Regex,
91
- transformer: Box<dyn PathTransformer>,
92
- }
93
-
94
- impl NativeRouteConvention {
95
- /// Create a new `NativeRouteConvention`.
96
- /// `transformer` can be used to inject a custom `PathTransformer`; if
97
- /// omitted the default `NativePathTransformer` is used.
98
- pub fn new(transformer: Option<Box<dyn PathTransformer>>) -> Self {
99
- Self {
100
- // Allow matching "route.get.ts" at the start of string OR after a slash
101
- route_regex: Regex::new(
102
- r"(^|/)route(\.(delete|get|head|options|patch|post|put))?\.(ts|js)$",
103
- )
104
- .unwrap(),
105
- transformer: transformer.unwrap_or_else(|| Box::new(NativePathTransformer::new())),
106
- }
107
- }
108
- }
109
-
110
- impl RouteConvention for NativeRouteConvention {
111
- fn transform_path(&self, path: &str) -> String {
112
- let mut result = path.to_string();
113
-
114
- // Normalize to use forward slashes first
115
- result = result.replace('\\', "/");
116
-
117
- // Remove /route.ts and /route.{method}.ts (convention-specific)
118
- result = self.route_regex.replace(&result, "").to_string();
119
-
120
- // Delegate generic transformations to transformer
121
- result = self.transformer.transform_file_path(&result);
122
-
123
- // Ensure leading slash
124
- if !result.starts_with('/') {
125
- result = format!("/{}", result);
126
- }
127
-
128
- result
129
- }
130
-
131
- fn extract_method(&self, path: &str) -> ExtractedMethod {
132
- if let Some(caps) = self.route_regex.captures(path) {
133
- let method = caps
134
- .get(3) // Capture group 3 is the method name (without dot)
135
- .and_then(|m| MatchedMethodSuffix::from_str(m.as_str()));
136
-
137
- let mut updated_path = self.route_regex.replace(path, "").to_string();
138
-
139
- // Ensure leading slash
140
- if !updated_path.starts_with('/') {
141
- updated_path = format!("/{}", updated_path);
142
- }
143
-
144
- return ExtractedMethod { method, updated_path };
145
- }
146
-
147
- let mut updated_path = path.to_string();
148
-
149
- // Ensure leading slash
150
- if !updated_path.starts_with('/') {
151
- updated_path = format!("/{}", updated_path);
152
- }
153
-
154
- ExtractedMethod { method: None, updated_path }
155
- }
156
- }
157
-
158
- #[cfg(test)]
159
- mod tests {
160
- use super::*;
161
-
162
- fn convention() -> NativeRouteConvention {
163
- NativeRouteConvention::new(None)
164
- }
165
-
166
- #[test]
167
- fn transform_removes_route_ts() {
168
- let c = convention();
169
- assert_eq!(c.transform_path("users/route.ts"), "/users");
170
- }
171
-
172
- #[test]
173
- fn transform_removes_method_suffix() {
174
- let c = convention();
175
- assert_eq!(c.transform_path("users/route.get.ts"), "/users");
176
- }
177
-
178
- #[test]
179
- fn transform_removes_route_groups() {
180
- let c = convention();
181
- assert_eq!(c.transform_path("(v1)/users/route.ts"), "/users");
182
- }
183
-
184
- #[test]
185
- fn transform_dynamic_segments() {
186
- let c = convention();
187
- assert_eq!(c.transform_path("users/[id]/route.ts"), "/users/:id");
188
- }
189
-
190
- #[test]
191
- fn transform_normalizes_windows_separators() {
192
- let c = convention();
193
- assert_eq!(c.transform_path(r"users\[id]\route.ts"), "/users/:id");
194
- }
195
-
196
- #[test]
197
- fn transform_ensures_leading_slash() {
198
- let c = convention();
199
- assert_eq!(c.transform_path("users/route.ts"), "/users");
200
- }
201
-
202
- #[test]
203
- fn extract_method_from_route_filename() {
204
- let c = convention();
205
- let extracted = c.extract_method("users/route.post.ts");
206
- assert_eq!(extracted.method, Some(MatchedMethodSuffix::Post));
207
- assert_eq!(extracted.updated_path, "/users");
208
- }
209
-
210
- #[test]
211
- fn extract_method_without_method_suffix() {
212
- let c = convention();
213
- let extracted = c.extract_method("users/route.ts");
214
- assert_eq!(extracted.method, None);
215
- assert_eq!(extracted.updated_path, "/users");
216
- }
217
-
218
- #[test]
219
- fn extract_then_transform_produces_clean_route() {
220
- let c = convention();
221
-
222
- let extracted = c.extract_method("/(auth)/users/[id]/route.put.ts");
223
- let path = c.transform_path(&extracted.updated_path);
224
-
225
- assert_eq!(extracted.method, Some(MatchedMethodSuffix::Put));
226
- assert_eq!(path, "/users/:id");
227
- }
228
-
229
- #[test]
230
- fn full_static_route_pipeline() {
231
- let c = convention();
232
-
233
- let extracted = c.extract_method("/health/route.ts");
234
- let path = c.transform_path(&extracted.updated_path);
235
-
236
- assert_eq!(extracted.method, None);
237
- assert_eq!(path, "/health");
238
- }
239
- }
package/src/router/mod.rs DELETED
@@ -1,118 +0,0 @@
1
- //! Router core types and interop structures.
2
- //!
3
- //! This module defines the small set of types used by the router and the
4
- //! native build pipeline to represent discovered routes and the manifest
5
- //! exported to the host environment (Node). It exposes:
6
- //! - `Route`: serializable representation exposed to JavaScript via N-API.
7
- //! - `RoutesManifest`: top-level manifest object sent to the host.
8
- //! - `RouteCore`: internal representation used inside the Rust codebase.
9
-
10
- use std::fs;
11
-
12
- use napi_derive::napi;
13
- use serde::Serialize;
14
-
15
- use crate::{builder::config::BuildConfig, router::{convention::MatchedMethodSuffix, processor::{NativeRouteProcessor, RouteProcessor}}, scanner::FileScanner, schema_version};
16
-
17
- pub mod convention;
18
- pub mod processor;
19
- pub mod transformer;
20
-
21
- /// Serializable route representation sent to the host (N-API).
22
- /// Fields are camel-cased to be idiomatic on the JavaScript side.
23
- #[napi(object)]
24
- #[derive(Serialize)]
25
- #[serde(rename_all = "camelCase")]
26
- pub struct Route {
27
- /// Uppercase HTTP method name, e.g. `GET` or `POST`, when present.
28
- pub method: Option<String>,
29
-
30
- /// Normalized route path (always starting with `/`).
31
- pub path: String,
32
-
33
- /// True when the route contains dynamic segments.
34
- pub dynamic: bool,
35
-
36
- /// Absolute filesystem path to the source file backing the route.
37
- pub file_path: String,
38
-
39
- /// Generated route matching regex as a string.
40
- pub regex: String,
41
- }
42
-
43
- /// Manifest containing all discovered routes, serializable to the host.
44
- #[napi(object)]
45
- #[derive(Serialize)]
46
- pub struct RoutesManifest {
47
- /// Manifest version string.
48
- pub version: String,
49
-
50
- /// List of routes.
51
- pub routes: Vec<Route>,
52
- }
53
-
54
- /// Internal representation of a route used within Rust code.
55
- /// `RouteCore` contains a typed `MatchedMethodSuffix` for internal routing
56
- /// logic; it is converted to the exported `Route` when communicating with the
57
- /// host.
58
- pub struct RouteCore {
59
- pub method: Option<MatchedMethodSuffix>,
60
- pub path: String,
61
- pub dynamic: bool,
62
- pub file_path: String,
63
- pub regex: String,
64
- }
65
-
66
- impl From<RouteCore> for Route {
67
- fn from(core: RouteCore) -> Self {
68
- Self {
69
- method: core.method.map(|m| m.as_str().to_string()),
70
- path: core.path,
71
- dynamic: core.dynamic,
72
- file_path: core.file_path,
73
- regex: core.regex,
74
- }
75
- }
76
- }
77
-
78
-
79
- pub fn write_routes_manifest(config: &BuildConfig) -> Result<(), String> {
80
- let routes_path = config.out_root.join("app").join("routes");
81
- if !routes_path.exists() {
82
- return Ok(());
83
- }
84
-
85
- let route_files = crate::scanner::NativeFileScanner::new()
86
- .scan_dir(
87
- &[
88
- config.output_path_str(),
89
- "app".to_string(),
90
- "routes".to_string(),
91
- ],
92
- Some(crate::scanner::ScanOptions {
93
- include: Some(vec!["**/*.js".to_string()]),
94
- ignore: None,
95
- }),
96
- )
97
- .map_err(|e| format!("scan failed: {}", e))?;
98
-
99
- let processor = NativeRouteProcessor::new(None, None);
100
-
101
- let version = schema_version().to_string();
102
- let routes: Vec<Route> = route_files
103
- .iter()
104
- .map(|file| processor.process_route_file(file))
105
- .filter(|route| route.method.is_some())
106
- .map(Route::from)
107
- .collect();
108
-
109
- let manifest = RoutesManifest { version, routes };
110
-
111
- let json = serde_json::to_string(&manifest)
112
- .map_err(|e| format!("Failed to serialize routes: {}", e))?;
113
-
114
- fs::write(config.out_root.join("routes.json"), json)
115
- .map_err(|e| format!("Failed to write file: {}", e))?;
116
-
117
- Ok(())
118
- }
@@ -1,201 +0,0 @@
1
- //! Route file processor utilities.
2
- //!
3
- //! This module exposes the `RouteProcessor` trait and a concrete
4
- //! `NativeRouteProcessor` implementation. The processor is responsible for
5
- //! converting a scanned filesystem `FileInfo` (a file that follows the route
6
- //! file conventions) into a `RouteCore` structure used by the router.
7
- //!
8
- //! Behaviour highlights:
9
- //! - Extracts optional HTTP method suffix (e.g. `route.post.ts`).
10
- //! - Transforms filesystem path patterns into normalized runtime paths
11
- //! (e.g. `users/[id]/route.ts` -> `/users/:id`).
12
- //! - Removes trailing `/index` segments and ensures leading slash.
13
- //! - Produces a regex string for route matching and indicates whether a
14
- //! route is dynamic.
15
-
16
- use crate::scanner::FileInfo;
17
- use crate::router::{
18
- convention::{NativeRouteConvention, RouteConvention},
19
- transformer::{NativePathTransformer, PathTransformer},
20
- RouteCore,
21
- };
22
-
23
- /// Trait that converts a discovered `FileInfo` into framework `RouteCore`.
24
- /// Implementations encapsulate how filenames and paths are interpreted as
25
- /// runtime routes.
26
- pub trait RouteProcessor {
27
- /// Process a scanned `file` and return a `RouteCore` describing the
28
- /// route's HTTP method (if any), normalized path, whether it's dynamic,
29
- /// the original file path and the generated regex.
30
- fn process_route_file(&self, file: &FileInfo) -> RouteCore;
31
- }
32
-
33
- /// Native implementation of `RouteProcessor` used by the default router.
34
- /// `NativeRouteProcessor` composes a `PathTransformer` and a
35
- /// `RouteConvention` to implement the file -> route conversion logic. Both
36
- /// components are replaceable for testing or customization.
37
- pub struct NativeRouteProcessor {
38
- transformer: Box<dyn PathTransformer>,
39
- convention: Box<dyn RouteConvention>,
40
- }
41
-
42
- impl NativeRouteProcessor {
43
- /// Create a new `NativeRouteProcessor`.
44
- /// `opt_transformer` and `opt_convention` are optional boxed trait
45
- /// instances. When omitted, default native implementations are used.
46
- pub fn new(
47
- opt_transformer: Option<Box<dyn PathTransformer>>,
48
- opt_convention: Option<Box<dyn RouteConvention>>,
49
- ) -> Self {
50
- let transformer = opt_transformer.unwrap_or_else(|| Box::new(NativePathTransformer::new()));
51
- let convention = opt_convention
52
- .unwrap_or_else(|| Box::new(NativeRouteConvention::new(Some(transformer.clone_box()))));
53
-
54
- Self {
55
- transformer,
56
- convention,
57
- }
58
- }
59
- }
60
-
61
- impl RouteProcessor for NativeRouteProcessor {
62
- /// Convert a filesystem `FileInfo` into a `RouteCore`.
63
- /// Steps:
64
- /// 1. Use the `RouteConvention` to extract any method suffix and the
65
- /// canonical path.
66
- /// 2. Normalize the path (apply prefix, remove trailing `/index`, ensure
67
- /// leading slash).
68
- /// 3. Determine whether the route is dynamic and generate the route regex.
69
- fn process_route_file(&self, file: &FileInfo) -> RouteCore {
70
- let extracted = self.convention.extract_method(&file.path);
71
- let mut path = self.convention.transform_path(&extracted.updated_path);
72
-
73
- path = self.transformer.normalize_path(&path, "");
74
-
75
- let dynamic = self.transformer.is_dynamic_route(&path);
76
- let regex = self.transformer.generate_route_regex(&path);
77
-
78
- RouteCore {
79
- method: extracted.method,
80
- path,
81
- dynamic,
82
- file_path: file.full_path.clone(),
83
- regex,
84
- }
85
- }
86
- }
87
-
88
- #[cfg(test)]
89
- mod tests {
90
- use super::*;
91
- use crate::router::convention::MatchedMethodSuffix;
92
-
93
- fn processor() -> NativeRouteProcessor {
94
- NativeRouteProcessor::new(None, None)
95
- }
96
-
97
- fn file_info(path: &str, full_path: &str) -> FileInfo {
98
- FileInfo {
99
- path: path.to_string(),
100
- full_path: full_path.to_string(),
101
- }
102
- }
103
-
104
- #[test]
105
- fn processes_static_routes_with_methods() {
106
- let p = processor();
107
-
108
- // Simple route without method
109
- let route = p.process_route_file(&file_info("users/route.ts", "/project/src/users/route.ts"));
110
- assert_eq!(route.path, "/users");
111
- assert_eq!(route.method, None);
112
- assert!(!route.dynamic);
113
- assert_eq!(route.file_path, "/project/src/users/route.ts");
114
- assert_eq!(route.regex, r"^\/users$");
115
-
116
- // Route with POST method
117
- let route = p.process_route_file(&file_info("users/route.post.ts", "/project/src/users/route.post.ts"));
118
- assert_eq!(route.path, "/users");
119
- assert_eq!(route.method, Some(MatchedMethodSuffix::Post));
120
- assert!(!route.dynamic);
121
- assert_eq!(route.file_path, "/project/src/users/route.post.ts");
122
-
123
- // Route with GET method
124
- let route = p.process_route_file(&file_info("about/route.get.ts", "/project/src/about/route.get.ts"));
125
- assert_eq!(route.path, "/about");
126
- assert_eq!(route.method, Some(MatchedMethodSuffix::Get));
127
- assert_eq!(route.regex, r"^\/about$");
128
- }
129
-
130
- #[test]
131
- fn processes_dynamic_routes() {
132
- let p = processor();
133
-
134
- // Single dynamic segment
135
- let route = p.process_route_file(&file_info("users/[id]/route.ts", "/project/src/users/[id]/route.ts"));
136
- assert_eq!(route.path, "/users/:id");
137
- assert_eq!(route.method, None);
138
- assert!(route.dynamic);
139
- assert_eq!(route.file_path, "/project/src/users/[id]/route.ts");
140
- assert_eq!(route.regex, r"^\/users\/([^\/]+)$");
141
-
142
- // Multiple dynamic segments with method
143
- let route = p.process_route_file(&file_info(
144
- "users/[userId]/posts/[postId]/route.get.ts",
145
- "/project/src/users/[userId]/posts/[postId]/route.get.ts",
146
- ));
147
- assert_eq!(route.path, "/users/:userId/posts/:postId");
148
- assert_eq!(route.method, Some(MatchedMethodSuffix::Get));
149
- assert!(route.dynamic);
150
- assert_eq!(route.file_path, "/project/src/users/[userId]/posts/[postId]/route.get.ts");
151
- assert_eq!(route.regex, r"^\/users\/([^\/]+)\/posts\/([^\/]+)$");
152
- }
153
-
154
- #[test]
155
- fn processes_index_routes_as_regular_routes() {
156
- let p = processor();
157
-
158
- // Root "index" is just "index" now
159
- let route = p.process_route_file(&file_info("index/route.ts", "/project/src/index/route.ts"));
160
- assert_eq!(route.path, "/index");
161
- assert_eq!(route.method, None);
162
- assert!(!route.dynamic);
163
-
164
- // Nested index
165
- let route = p.process_route_file(&file_info("users/index/route.ts", "/project/src/users/index/route.ts"));
166
- assert_eq!(route.path, "/users/index");
167
- }
168
-
169
- #[test]
170
- fn processes_route_groups() {
171
- let p = processor();
172
-
173
- // Simple group
174
- let route = p.process_route_file(&file_info("(v1)/users/route.ts", "/project/src/(v1)/users/route.ts"));
175
- assert_eq!(route.path, "/users");
176
- assert!(!route.dynamic);
177
- assert_eq!(route.file_path, "/project/src/(v1)/users/route.ts");
178
-
179
- // Group with dynamic route and method
180
- let route = p.process_route_file(&file_info(
181
- "(api)/users/[id]/route.delete.ts",
182
- "/project/src/(api)/users/[id]/route.delete.ts",
183
- ));
184
- assert_eq!(route.path, "/users/:id");
185
- assert_eq!(route.method, Some(MatchedMethodSuffix::Delete));
186
- assert!(route.dynamic);
187
- assert_eq!(route.file_path, "/project/src/(api)/users/[id]/route.delete.ts");
188
- }
189
-
190
- #[test]
191
- fn handles_windows_paths() {
192
- let p = processor();
193
- let route = p.process_route_file(&file_info(
194
- r"users\[id]\route.ts",
195
- r"C:\project\src\users\[id]\route.ts",
196
- ));
197
- assert_eq!(route.path, "/users/:id");
198
- assert!(route.dynamic);
199
- assert_eq!(route.file_path, r"C:\project\src\users\[id]\route.ts");
200
- }
201
- }