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