1use std::time::{Duration, SystemTime};
6
7use axum::{
8 extract::{Query, State},
9 http::StatusCode,
10 response::IntoResponse,
11};
12use iota_types::storage::ReadStore;
13use tap::Pipe;
14
15use crate::{
16 RestService, Result,
17 openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler},
18 reader::StateReader,
19};
20
21pub struct HealthCheck;
22
23impl ApiEndpoint<RestService> for HealthCheck {
24 fn method(&self) -> axum::http::Method {
25 axum::http::Method::GET
26 }
27
28 fn path(&self) -> &'static str {
29 "/health"
30 }
31
32 fn operation(
33 &self,
34 generator: &mut schemars::gen::SchemaGenerator,
35 ) -> openapiv3::v3_1::Operation {
36 OperationBuilder::new()
37 .tag("General")
38 .operation_id("HealthCheck")
39 .query_parameters::<Threshold>(generator)
40 .response(200, ResponseBuilder::new().text_content().build())
41 .build()
42 }
43
44 fn handler(&self) -> crate::openapi::RouteHandler<RestService> {
45 RouteHandler::new(self.method(), health)
46 }
47}
48
49#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
50pub struct Threshold {
51 pub threshold_seconds: Option<u32>,
52}
53
54async fn health(
55 Query(Threshold { threshold_seconds }): Query<Threshold>,
56 State(state): State<StateReader>,
57) -> Result<impl IntoResponse> {
58 let summary = state.inner().get_latest_checkpoint()?;
59
60 if let Some(threshold_seconds) = threshold_seconds {
62 let latest_chain_time = summary.timestamp();
63
64 let threshold = SystemTime::now() - Duration::from_secs(threshold_seconds as u64);
65
66 if latest_chain_time < threshold {
67 return Err(anyhow::anyhow!(
68 "The latest checkpoint timestamp is less than the provided threshold"
69 )
70 .into());
71 }
72 }
73
74 StatusCode::OK.pipe(Ok)
75}