From fd1447999d9665866d65002b2c2317b8b150225f Mon Sep 17 00:00:00 2001 From: Toby Vincent Date: Thu, 21 Mar 2024 21:39:15 -0500 Subject: feat: impl user api endpoint --- src/routes.rs | 119 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 65 insertions(+), 54 deletions(-) (limited to 'src/routes.rs') 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 { + 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>, + ) -> Result, 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(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> { + 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(()) } } -- cgit v1.2.3-70-g09d2