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;
28
29impl ApiEndpoint<RestService> for HealthCheck {
30 fn method(&self) -> axum::http::Method {
31 axum::http::Method::GET
32 }
33
34 fn path(&self) -> &'static str {
35 "/-/health"
36 }
37
38 fn stable(&self) -> bool {
39 true
40 }
41
42 fn operation(
43 &self,
44 generator: &mut schemars::gen::SchemaGenerator,
45 ) -> openapiv3::v3_1::Operation {
46 OperationBuilder::new()
47 .tag("General")
48 .operation_id("Health Check")
49 .query_parameters::<Threshold>(generator)
50 .response(200, ResponseBuilder::new().text_content().build())
51 .response(500, ResponseBuilder::new().build())
52 .build()
53 }
54
55 fn handler(&self) -> crate::openapi::RouteHandler<RestService> {
56 RouteHandler::new(self.method(), health)
57 }
58}
59
60#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
61pub struct Threshold {
62 pub threshold_seconds: Option<u32>,
69}
70
71async fn health(
72 Query(Threshold { threshold_seconds }): Query<Threshold>,
73 State(state): State<StateReader>,
74) -> Result<impl IntoResponse> {
75 let summary = state.inner().get_latest_checkpoint()?;
76
77 if let Some(threshold_seconds) = threshold_seconds {
79 let latest_chain_time = summary.timestamp();
80
81 let threshold = SystemTime::now() - Duration::from_secs(threshold_seconds as u64);
82
83 if latest_chain_time < threshold {
84 return Err(anyhow::anyhow!(
85 "The latest checkpoint timestamp is less than the provided threshold"
86 )
87 .into());
88 }
89 }
90
91 StatusCode::OK.pipe(Ok)
92}