1use axum::{
2 handler::Handler,
3 routing::{delete, get, on, post, MethodFilter, MethodRouter},
4 Router,
5};
6
7#[derive(Debug)]
35#[must_use]
36pub struct Resource<S = ()> {
37 pub(crate) name: String,
38 pub(crate) router: Router<S>,
39}
40
41impl<S> Resource<S>
42where
43 S: Clone + Send + Sync + 'static,
44{
45 pub fn named(resource_name: &str) -> Self {
49 Self {
50 name: resource_name.to_owned(),
51 router: Router::new(),
52 }
53 }
54
55 pub fn index<H, T>(self, handler: H) -> Self
57 where
58 H: Handler<T, S>,
59 T: 'static,
60 {
61 let path = self.index_create_path();
62 self.route(&path, get(handler))
63 }
64
65 pub fn create<H, T>(self, handler: H) -> Self
67 where
68 H: Handler<T, S>,
69 T: 'static,
70 {
71 let path = self.index_create_path();
72 self.route(&path, post(handler))
73 }
74
75 pub fn new<H, T>(self, handler: H) -> Self
77 where
78 H: Handler<T, S>,
79 T: 'static,
80 {
81 let path = format!("/{}/new", self.name);
82 self.route(&path, get(handler))
83 }
84
85 pub fn show<H, T>(self, handler: H) -> Self
87 where
88 H: Handler<T, S>,
89 T: 'static,
90 {
91 let path = self.show_update_destroy_path();
92 self.route(&path, get(handler))
93 }
94
95 pub fn edit<H, T>(self, handler: H) -> Self
97 where
98 H: Handler<T, S>,
99 T: 'static,
100 {
101 let path = format!("/{0}/:{0}_id/edit", self.name);
102 self.route(&path, get(handler))
103 }
104
105 pub fn update<H, T>(self, handler: H) -> Self
107 where
108 H: Handler<T, S>,
109 T: 'static,
110 {
111 let path = self.show_update_destroy_path();
112 self.route(
113 &path,
114 on(MethodFilter::PUT.or(MethodFilter::PATCH), handler),
115 )
116 }
117
118 pub fn destroy<H, T>(self, handler: H) -> Self
120 where
121 H: Handler<T, S>,
122 T: 'static,
123 {
124 let path = self.show_update_destroy_path();
125 self.route(&path, delete(handler))
126 }
127
128 fn index_create_path(&self) -> String {
129 format!("/{}", self.name)
130 }
131
132 fn show_update_destroy_path(&self) -> String {
133 format!("/{0}/:{0}_id", self.name)
134 }
135
136 fn route(mut self, path: &str, method_router: MethodRouter<S>) -> Self {
137 self.router = self.router.route(path, method_router);
138 self
139 }
140}
141
142impl<S> From<Resource<S>> for Router<S> {
143 fn from(resource: Resource<S>) -> Self {
144 resource.router
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 #[allow(unused_imports)]
151 use super::*;
152 use axum::{body::Body, extract::Path, http::Method};
153 use http::Request;
154 use http_body_util::BodyExt;
155 use tower::ServiceExt;
156
157 #[tokio::test]
158 async fn works() {
159 let users = Resource::named("users")
160 .index(|| async { "users#index" })
161 .create(|| async { "users#create" })
162 .new(|| async { "users#new" })
163 .show(|Path(id): Path<u64>| async move { format!("users#show id={id}") })
164 .edit(|Path(id): Path<u64>| async move { format!("users#edit id={id}") })
165 .update(|Path(id): Path<u64>| async move { format!("users#update id={id}") })
166 .destroy(|Path(id): Path<u64>| async move { format!("users#destroy id={id}") });
167
168 let app = Router::new().merge(users);
169
170 assert_eq!(call_route(&app, Method::GET, "/users").await, "users#index");
171
172 assert_eq!(
173 call_route(&app, Method::POST, "/users").await,
174 "users#create"
175 );
176
177 assert_eq!(
178 call_route(&app, Method::GET, "/users/new").await,
179 "users#new"
180 );
181
182 assert_eq!(
183 call_route(&app, Method::GET, "/users/1").await,
184 "users#show id=1"
185 );
186
187 assert_eq!(
188 call_route(&app, Method::GET, "/users/1/edit").await,
189 "users#edit id=1"
190 );
191
192 assert_eq!(
193 call_route(&app, Method::PATCH, "/users/1").await,
194 "users#update id=1"
195 );
196
197 assert_eq!(
198 call_route(&app, Method::PUT, "/users/1").await,
199 "users#update id=1"
200 );
201
202 assert_eq!(
203 call_route(&app, Method::DELETE, "/users/1").await,
204 "users#destroy id=1"
205 );
206 }
207
208 async fn call_route(app: &Router, method: Method, uri: &str) -> String {
209 let res = app
210 .clone()
211 .oneshot(
212 Request::builder()
213 .method(method)
214 .uri(uri)
215 .body(Body::empty())
216 .unwrap(),
217 )
218 .await
219 .unwrap();
220 let bytes = res.collect().await.unwrap().to_bytes();
221 String::from_utf8(bytes.to_vec()).unwrap()
222 }
223}