1use std::collections::HashMap;
14
15use axum::extract::{Path, Query, State};
16use axum::response::{IntoResponse, Response};
17use axum::Json;
18use serde_json::Value as Json_;
19
20use crate::auth::Authority;
21use crate::body_credentials::MethodOverride;
22use crate::params::Params;
23use crate::response::{HttpError, ParseErrorResponse};
24use crate::routes::dispatch::{self, Incoming, Route, RouteError};
25use crate::state::AppState;
26
27async fn run(state: &AppState, authority: &Authority, incoming: Incoming) -> Response {
29 let authority = &match incoming.route {
47 Route::Login => Authority {
48 session_token: None,
49 ..authority.clone()
50 },
51 _ => authority.clone(),
52 };
53
54 let rc = match state.request_context(authority).await {
56 Ok(rc) => rc,
57 Err(e) => return ParseErrorResponse(e).into_response(),
58 };
59 let outcome = dispatch::dispatch(state, &rc, authority, &incoming).await;
60 match outcome {
61 Ok(response) => (response.status, Json(response.body)).into_response(),
62 Err(RouteError::Parse(e)) => ParseErrorResponse(e).into_response(),
63 Err(RouteError::Http(e)) => e.into_response(),
64 Err(RouteError::NotFound { method, path }) => HttpError {
69 status: http::StatusCode::NOT_FOUND,
70 message: format!("cannot route {method} {path}"),
71 }
72 .into_response(),
73 }
74}
75
76fn effective_method(
82 transport: http::Method,
83 override_: Option<axum::Extension<MethodOverride>>,
84) -> http::Method {
85 match override_ {
86 Some(axum::Extension(MethodOverride(m))) => m,
87 None => transport,
88 }
89}
90
91fn params(query: HashMap<String, String>) -> Params {
92 Params::from_map(query)
93}
94
95pub async fn health(State(state): State<AppState>) -> Response {
100 let _ = state;
104 Json(crate::routes::health::body()).into_response()
105}
106
107pub async fn server_info(State(state): State<AppState>, authority: Authority) -> Response {
108 if !authority.is_master() {
111 return HttpError::master_key_required(state.config().error_detail()).into_response();
112 }
113 Json(crate::routes::features::server_info_body(state.config())).into_response()
114}
115
116pub async fn users_collection(
117 State(state): State<AppState>,
118 authority: Authority,
119 method: Option<axum::Extension<MethodOverride>>,
120 body: Option<Json<Json_>>,
121) -> Response {
122 let method = effective_method(http::Method::POST, method);
123 run(
124 &state,
125 &authority,
126 Incoming {
127 method,
128 route: Route::Users,
129 params: Params::default(),
130 body: body.map(|b| b.0),
131 path: "/users".to_string(),
132 },
133 )
134 .await
135}
136
137pub async fn users_me(
138 State(state): State<AppState>,
139 authority: Authority,
140 method: Option<axum::Extension<MethodOverride>>,
141 transport: http::Method,
142) -> Response {
143 let method = effective_method(transport, method);
145 run(
146 &state,
147 &authority,
148 Incoming {
149 method,
150 route: Route::UsersMe,
151 params: Params::default(),
152 body: None,
153 path: "/users/me".to_string(),
154 },
155 )
156 .await
157}
158
159pub async fn login(
160 State(state): State<AppState>,
161 authority: Authority,
162 body: Option<Json<Json_>>,
163) -> Response {
164 run(
165 &state,
166 &authority,
167 Incoming {
168 method: http::Method::POST,
169 route: Route::Login,
170 params: Params::default(),
171 body: body.map(|b| b.0),
172 path: "/login".to_string(),
173 },
174 )
175 .await
176}
177
178pub async fn logout(State(state): State<AppState>, authority: Authority) -> Response {
179 run(
180 &state,
181 &authority,
182 Incoming {
183 method: http::Method::POST,
184 route: Route::Logout,
185 params: Params::default(),
186 body: None,
187 path: "/logout".to_string(),
188 },
189 )
190 .await
191}
192
193pub async fn classes_collection(
194 State(state): State<AppState>,
195 authority: Authority,
196 Path(class_name): Path<String>,
197 Query(query): Query<HashMap<String, String>>,
198 method: Option<axum::Extension<MethodOverride>>,
199 transport: http::Method,
200 body: Option<Json<Json_>>,
201) -> Response {
202 let method = effective_method(transport, method);
203 let path = format!("/classes/{class_name}");
204 run(
205 &state,
206 &authority,
207 Incoming {
208 method,
209 route: Route::Classes { class_name },
210 params: params(query),
211 body: body.map(|b| b.0),
212 path,
213 },
214 )
215 .await
216}
217
218pub async fn classes_object(
219 State(state): State<AppState>,
220 authority: Authority,
221 Path((class_name, object_id)): Path<(String, String)>,
222 Query(query): Query<HashMap<String, String>>,
223 method: Option<axum::Extension<MethodOverride>>,
224 transport: http::Method,
225 body: Option<Json<Json_>>,
226) -> Response {
227 let method = effective_method(transport, method);
231 let path = format!("/classes/{class_name}/{object_id}");
232 run(
233 &state,
234 &authority,
235 Incoming {
236 method,
237 route: Route::ClassObject {
238 class_name,
239 object_id,
240 },
241 params: params(query),
242 body: body.map(|b| b.0),
243 path,
244 },
245 )
246 .await
247}
248
249pub async fn roles_collection(
250 State(state): State<AppState>,
251 authority: Authority,
252 Query(query): Query<HashMap<String, String>>,
253 method: Option<axum::Extension<MethodOverride>>,
254 transport: http::Method,
255 body: Option<Json<Json_>>,
256) -> Response {
257 let method = effective_method(transport, method);
258 run(
259 &state,
260 &authority,
261 Incoming {
262 method,
263 route: Route::Roles,
264 params: params(query),
265 body: body.map(|b| b.0),
266 path: "/roles".to_string(),
267 },
268 )
269 .await
270}
271
272pub async fn roles_object(
273 State(state): State<AppState>,
274 authority: Authority,
275 Path(object_id): Path<String>,
276 Query(query): Query<HashMap<String, String>>,
277 method: Option<axum::Extension<MethodOverride>>,
278 transport: http::Method,
279 body: Option<Json<Json_>>,
280) -> Response {
281 let method = effective_method(transport, method);
282 let path = format!("/roles/{object_id}");
283 run(
284 &state,
285 &authority,
286 Incoming {
287 method,
288 route: Route::RoleObject { object_id },
289 params: params(query),
290 body: body.map(|b| b.0),
291 path,
292 },
293 )
294 .await
295}
296
297pub async fn sessions_collection(
298 State(state): State<AppState>,
299 authority: Authority,
300 Query(query): Query<HashMap<String, String>>,
301 method: Option<axum::Extension<MethodOverride>>,
302 transport: http::Method,
303) -> Response {
304 let method = effective_method(transport, method);
305 run(
306 &state,
307 &authority,
308 Incoming {
309 method,
310 route: Route::Sessions,
311 params: params(query),
312 body: None,
313 path: "/sessions".to_string(),
314 },
315 )
316 .await
317}
318
319pub async fn sessions_me(
320 State(state): State<AppState>,
321 authority: Authority,
322 method: Option<axum::Extension<MethodOverride>>,
323 transport: http::Method,
324) -> Response {
325 let method = effective_method(transport, method);
326 run(
327 &state,
328 &authority,
329 Incoming {
330 method,
331 route: Route::SessionsMe,
332 params: Params::default(),
333 body: None,
334 path: "/sessions/me".to_string(),
335 },
336 )
337 .await
338}
339
340pub async fn sessions_object(
341 State(state): State<AppState>,
342 authority: Authority,
343 Path(object_id): Path<String>,
344 Query(query): Query<HashMap<String, String>>,
345 method: Option<axum::Extension<MethodOverride>>,
346 transport: http::Method,
347) -> Response {
348 let method = effective_method(transport, method);
349 let path = format!("/sessions/{object_id}");
350 run(
351 &state,
352 &authority,
353 Incoming {
354 method,
355 route: Route::SessionObject { object_id },
356 params: params(query),
357 body: None,
358 path,
359 },
360 )
361 .await
362}
363
364pub async fn schemas_collection(
365 State(state): State<AppState>,
366 authority: Authority,
367 method: Option<axum::Extension<MethodOverride>>,
368 transport: http::Method,
369 body: Option<Json<Json_>>,
370) -> Response {
371 let method = effective_method(transport, method);
372 run(
373 &state,
374 &authority,
375 Incoming {
376 method,
377 route: Route::Schemas,
378 params: Params::default(),
379 body: body.map(|b| b.0),
380 path: "/schemas".to_string(),
381 },
382 )
383 .await
384}
385
386pub async fn schemas_class(
387 State(state): State<AppState>,
388 authority: Authority,
389 Path(class_name): Path<String>,
390 method: Option<axum::Extension<MethodOverride>>,
391 transport: http::Method,
392 body: Option<Json<Json_>>,
393) -> Response {
394 let method = effective_method(transport, method);
395 let path = format!("/schemas/{class_name}");
396 run(
397 &state,
398 &authority,
399 Incoming {
400 method,
401 route: Route::SchemaClass { class_name },
402 params: Params::default(),
403 body: body.map(|b| b.0),
404 path,
405 },
406 )
407 .await
408}
409
410pub async fn purge(
411 State(state): State<AppState>,
412 authority: Authority,
413 Path(class_name): Path<String>,
414 method: Option<axum::Extension<MethodOverride>>,
415 transport: http::Method,
416) -> Response {
417 let method = effective_method(transport, method);
418 let path = format!("/purge/{class_name}");
419 run(
420 &state,
421 &authority,
422 Incoming {
423 method,
424 route: Route::Purge { class_name },
425 params: Params::default(),
426 body: None,
427 path,
428 },
429 )
430 .await
431}
432
433pub async fn batch(
438 State(state): State<AppState>,
439 authority: Authority,
440 body: Option<Json<Json_>>,
441) -> Response {
442 let rc = match state.request_context(&authority).await {
443 Ok(rc) => rc,
444 Err(e) => return ParseErrorResponse(e).into_response(),
445 };
446 let body = body.map(|b| b.0);
447 let mount_path = state.config().mount_path.clone();
448 match crate::routes::batch::handle(&state, &rc, &authority, &mount_path, body.as_ref()).await {
449 Ok(results) => Json(results).into_response(),
450 Err(e) => ParseErrorResponse(e).into_response(),
451 }
452}