1use std::{net::SocketAddr, sync::Arc};
8
9use anyhow::Result;
10use axum::{Router, response::IntoResponse, routing::get};
11use tokio_util::sync::CancellationToken;
12
13use crate::{
14 RestApiConfig,
15 errors::ApiError,
16 kv_store_client::KvStoreClient,
17 routes::{health, kv_store},
18};
19
20pub struct Server {
23 router: Router,
24 server_address: SocketAddr,
25 token: CancellationToken,
26}
27
28impl Server {
29 pub async fn new(config: RestApiConfig, token: CancellationToken) -> Result<Self> {
34 let kv_store_client = KvStoreClient::new(config.kv_store_config).await?;
35
36 let shared_state = Arc::new(kv_store_client);
37
38 let router = Router::new()
39 .route("/health", get(health::health))
40 .route("/:item_type/:key", get(kv_store::data_as_bytes))
41 .with_state(shared_state)
42 .fallback(fallback);
43
44 Ok(Self {
45 router,
46 token,
47 server_address: config.server_address,
48 })
49 }
50
51 pub async fn serve(self) -> Result<()> {
53 let listener = tokio::net::TcpListener::bind(self.server_address)
54 .await
55 .expect("failed to bind to socket");
56
57 tracing::info!("listening on: {}", self.server_address);
58
59 axum::serve(listener, self.router)
60 .with_graceful_shutdown(async move {
61 self.token.cancelled().await;
62 tracing::info!("shutdown signal received.");
63 })
64 .await
65 .inspect_err(|e| tracing::error!("server encountered an error: {e}"))
66 .map_err(Into::into)
67 }
68}
69
70async fn fallback() -> impl IntoResponse {
79 ApiError::NotFound
80}