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
89 where
90 H: Handler<T, S>,
91 T: 'static,
92 {
93 let path = self.show_update_destroy_path();
94 self.route(&path, get(handler))
95 }
96
97 pub fn edit<H, T>(self, handler: H) -> Self
101 where
102 H: Handler<T, S>,
103 T: 'static,
104 {
105 let path = format!("/{0}/{{{0}_id}}/edit", self.name);
106 self.route(&path, get(handler))
107 }
108
109 pub fn update<H, T>(self, handler: H) -> Self
113 where
114 H: Handler<T, S>,
115 T: 'static,
116 {
117 let path = self.show_update_destroy_path();
118 self.route(
119 &path,
120 on(MethodFilter::PUT.or(MethodFilter::PATCH), handler),
121 )
122 }
123
124 pub fn destroy<H, T>(self, handler: H) -> Self
128 where
129 H: Handler<T, S>,
130 T: 'static,
131 {
132 let path = self.show_update_destroy_path();
133 self.route(&path, delete(handler))
134 }
135
136 fn index_create_path(&self) -> String {
137 format!("/{}", self.name)
138 }
139
140 fn show_update_destroy_path(&self) -> String {
141 format!("/{0}/{{{0}_id}}", self.name)
142 }
143
144 fn route(mut self, path: &str, method_router: MethodRouter<S>) -> Self {
145 self.router = self.router.route(path, method_router);
146 self
147 }
148}
149
150impl<S> From<Resource<S>> for Router<S> {
151 fn from(resource: Resource<S>) -> Self {
152 resource.router
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 #[allow(unused_imports)]
159 use super::*;
160 use axum::{body::Body, extract::Path, http::Method};
161 use http::Request;
162 use http_body_util::BodyExt;
163 use tower::ServiceExt;
164
165 #[tokio::test]
166 async fn works() {
167 let users = Resource::named("users")
168 .index(|| async { "users#index" })
169 .create(|| async { "users#create" })
170 .new(|| async { "users#new" })
171 .show(|Path(id): Path<u64>| async move { format!("users#show id={id}") })
172 .edit(|Path(id): Path<u64>| async move { format!("users#edit id={id}") })
173 .update(|Path(id): Path<u64>| async move { format!("users#update id={id}") })
174 .destroy(|Path(id): Path<u64>| async move { format!("users#destroy id={id}") });
175
176 let app = Router::new().merge(users);
177
178 assert_eq!(call_route(&app, Method::GET, "/users").await, "users#index");
179
180 assert_eq!(
181 call_route(&app, Method::POST, "/users").await,
182 "users#create"
183 );
184
185 assert_eq!(
186 call_route(&app, Method::GET, "/users/new").await,
187 "users#new"
188 );
189
190 assert_eq!(
191 call_route(&app, Method::GET, "/users/1").await,
192 "users#show id=1"
193 );
194
195 assert_eq!(
196 call_route(&app, Method::GET, "/users/1/edit").await,
197 "users#edit id=1"
198 );
199
200 assert_eq!(
201 call_route(&app, Method::PATCH, "/users/1").await,
202 "users#update id=1"
203 );
204
205 assert_eq!(
206 call_route(&app, Method::PUT, "/users/1").await,
207 "users#update id=1"
208 );
209
210 assert_eq!(
211 call_route(&app, Method::DELETE, "/users/1").await,
212 "users#destroy id=1"
213 );
214 }
215
216 async fn call_route(app: &Router, method: Method, uri: &str) -> String {
217 let res = app
218 .clone()
219 .oneshot(
220 Request::builder()
221 .method(method)
222 .uri(uri)
223 .body(Body::empty())
224 .unwrap(),
225 )
226 .await
227 .unwrap();
228 let bytes = res.collect().await.unwrap().to_bytes();
229 String::from_utf8(bytes.to_vec()).unwrap()
230 }
231}