Skip to main content

next_rs_router/
lib.rs

1mod boundary;
2pub mod codegen;
3mod hooks;
4mod layout;
5mod link;
6mod matcher;
7mod scanner;
8mod segment;
9
10pub use boundary::{
11    BoundaryResolver, BoundaryStack, ErrorBoundary, LoadingBoundary, NotFoundBoundary,
12};
13pub use codegen::RouteCodegen;
14pub use hooks::{use_params, use_pathname, use_router, use_search_params, RouterState};
15pub use layout::{LayoutResolver, RouteMetadata};
16pub use link::{link, Link};
17pub use matcher::{MatchedRoute, RouteMatcher};
18pub use scanner::{RouteScanner, SpecialFile};
19pub use segment::RouteSegment;
20
21use std::path::PathBuf;
22
23#[derive(Debug, Clone)]
24pub struct Route {
25    pub path: String,
26    pub segments: Vec<RouteSegment>,
27    pub page_file: Option<PathBuf>,
28    pub layout_file: Option<PathBuf>,
29    pub loading_file: Option<PathBuf>,
30    pub error_file: Option<PathBuf>,
31    pub not_found_file: Option<PathBuf>,
32    pub route_file: Option<PathBuf>,
33}
34
35impl Route {
36    pub fn new(path: impl Into<String>) -> Self {
37        let path = path.into();
38        let segments = RouteSegment::parse(&path);
39        Self {
40            path,
41            segments,
42            page_file: None,
43            layout_file: None,
44            loading_file: None,
45            error_file: None,
46            not_found_file: None,
47            route_file: None,
48        }
49    }
50
51    pub fn with_page(mut self, file: PathBuf) -> Self {
52        self.page_file = Some(file);
53        self
54    }
55
56    pub fn with_layout(mut self, file: PathBuf) -> Self {
57        self.layout_file = Some(file);
58        self
59    }
60
61    pub fn is_dynamic(&self) -> bool {
62        self.segments
63            .iter()
64            .any(|s| !matches!(s, RouteSegment::Static(_)))
65    }
66
67    pub fn is_api(&self) -> bool {
68        self.route_file.is_some()
69    }
70}
71
72#[derive(Debug, Clone)]
73pub struct Router {
74    pub routes: Vec<Route>,
75}
76
77impl Router {
78    pub fn new() -> Self {
79        Self { routes: Vec::new() }
80    }
81
82    pub fn from_routes(routes: Vec<Route>) -> Self {
83        Self { routes }
84    }
85
86    pub fn add_route(&mut self, route: Route) {
87        self.routes.push(route);
88    }
89
90    pub fn match_path(&self, path: &str) -> Option<MatchedRoute> {
91        let matcher = RouteMatcher::new(&self.routes);
92        matcher.match_path(path)
93    }
94
95    pub fn static_routes(&self) -> impl Iterator<Item = &Route> {
96        self.routes.iter().filter(|r| !r.is_dynamic())
97    }
98
99    pub fn dynamic_routes(&self) -> impl Iterator<Item = &Route> {
100        self.routes.iter().filter(|r| r.is_dynamic())
101    }
102}
103
104impl Default for Router {
105    fn default() -> Self {
106        Self::new()
107    }
108}
109
110#[derive(Debug, Clone)]
111pub struct Layout {
112    pub file: PathBuf,
113    pub path: String,
114}
115
116#[derive(Debug)]
117pub struct LayoutTree {
118    pub layouts: Vec<Layout>,
119    pub page: PathBuf,
120}
121
122impl LayoutTree {
123    pub fn new(page: PathBuf) -> Self {
124        Self {
125            layouts: Vec::new(),
126            page,
127        }
128    }
129
130    pub fn add_layout(&mut self, layout: Layout) {
131        self.layouts.push(layout);
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_route_creation() {
141        let route = Route::new("/blog/[slug]");
142        assert_eq!(route.path, "/blog/[slug]");
143        assert!(route.is_dynamic());
144    }
145
146    #[test]
147    fn test_static_route() {
148        let route = Route::new("/about");
149        assert!(!route.is_dynamic());
150    }
151
152    #[test]
153    fn test_router() {
154        let mut router = Router::new();
155        router.add_route(Route::new("/"));
156        router.add_route(Route::new("/about"));
157        router.add_route(Route::new("/blog/[slug]"));
158
159        assert_eq!(router.routes.len(), 3);
160        assert_eq!(router.static_routes().count(), 2);
161        assert_eq!(router.dynamic_routes().count(), 1);
162    }
163}