summaryrefslogtreecommitdiffstats
path: root/src/routes.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes.rs')
-rw-r--r--src/routes.rs119
1 files changed, 65 insertions, 54 deletions
diff --git a/src/routes.rs b/src/routes.rs
index 3625eff..0a81317 100644
--- a/src/routes.rs
+++ b/src/routes.rs
@@ -1,69 +1,80 @@
-use axum::Router;
-use axum_extra::routing::{RouterExt, TypedPath};
-use tokio::{
- net::{TcpListener, ToSocketAddrs},
- signal,
+use std::sync::Arc;
+
+use axum::{
+ extract::State,
+ http::{StatusCode, Uri},
+ Json,
};
+use axum_extra::routing::{RouterExt, TypedPath};
+use serde::Deserialize;
+
+use crate::{model::User, state::AppState, Error};
+
+pub fn router(state: AppState) -> axum::Router {
+ axum::Router::new()
+ // .route("/api/user", get(get_user))
+ .typed_get(HealthCheck::get)
+ .typed_get(UserId::get)
+ .fallback(fallback)
+ .with_state(Arc::new(state))
+}
+
+#[derive(Debug, Deserialize, TypedPath)]
+#[typed_path("/api/healthcheck")]
+pub struct HealthCheck;
-use crate::Error;
+impl HealthCheck {
+ pub async fn get(self) -> Json<serde_json::Value> {
+ const MESSAGE: &str = "Unnamed server";
-#[derive(TypedPath)]
-#[typed_path("/ping")]
-pub struct Ping;
+ let json_response = serde_json::json!({
+ "status": "success",
+ "message": MESSAGE
+ });
-/// # Test endpoint
-///
-/// Returns "pong"
-#[tracing::instrument(ret)]
-pub async fn ping(_: Ping) -> &'static str {
- "pong"
+ Json(json_response)
+ }
}
-#[derive(TypedPath)]
-#[typed_path("/")]
-pub struct Root;
+#[derive(Debug, Deserialize, TypedPath)]
+#[typed_path("/api/user/:uuid")]
+pub struct UserId {
+ pub uuid: uuid::Uuid,
+}
-#[tracing::instrument(ret)]
-pub async fn root(_: Root) -> &'static str {
- "Hello, World!"
+impl UserId {
+ /// Get a user via their `id`
+ #[tracing::instrument(ret, skip(data))]
+ pub async fn get(
+ self,
+ State(data): State<Arc<AppState>>,
+ ) -> Result<Json<serde_json::Value>, Error> {
+ sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", self.uuid)
+ .fetch_optional(&data.db_pool)
+ .await?
+ .ok_or_else(|| Error::UserNotFound(self.uuid))
+ .map(User::into_query_response)
+ }
}
-pub async fn serve<A>(addr: A) -> Result<(), Error>
-where
- A: ToSocketAddrs,
-{
- let app = Router::new().typed_get(root).typed_get(ping);
+pub async fn fallback(uri: Uri) -> (StatusCode, String) {
+ (StatusCode::NOT_FOUND, format!("Route not found: {uri}"))
+}
- let listener = TcpListener::bind(addr).await?;
+#[cfg(test)]
+mod tests {
+ use super::*;
- tracing::info!("Server listening on http://{}", listener.local_addr()?);
+ use axum_test::TestServer;
- axum::serve(listener, app)
- .with_graceful_shutdown(shutdown_signal())
- .await
- .map_err(From::from)
-}
+ #[tokio::test]
+ async fn test_fallback() -> Result<(), Box<dyn std::error::Error>> {
+ let server = TestServer::new(axum::Router::new().fallback(fallback))?;
+
+ let response = server.get("/fallback").await;
+
+ assert_eq!(StatusCode::NOT_FOUND, response.status_code());
-async fn shutdown_signal() {
- let ctrl_c = async {
- signal::ctrl_c()
- .await
- .expect("failed to install Ctrl+C handler");
- };
-
- #[cfg(unix)]
- let terminate = async {
- signal::unix::signal(signal::unix::SignalKind::terminate())
- .expect("failed to install signal handler")
- .recv()
- .await;
- };
-
- #[cfg(not(unix))]
- let terminate = std::future::pending::<()>();
-
- tokio::select! {
- _ = ctrl_c => {},
- _ = terminate => {},
+ Ok(())
}
}