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}