iota_rest_api/
epochs.rs

1// Copyright (c) 2025 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use axum::extract::{Path, State};
5use iota_sdk2::types::{CheckpointSequenceNumber, EpochId, SignedCheckpointSummary};
6use tap::Pipe;
7
8use crate::{
9    RestService, Result,
10    accept::AcceptFormat,
11    openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler},
12    reader::StateReader,
13    response::ResponseContent,
14};
15
16pub struct GetEpochLastCheckpoint;
17
18impl ApiEndpoint<RestService> for GetEpochLastCheckpoint {
19    fn method(&self) -> axum::http::Method {
20        axum::http::Method::GET
21    }
22
23    fn path(&self) -> &'static str {
24        "/epochs/{epoch}/last-checkpoint"
25    }
26
27    fn operation(
28        &self,
29        generator: &mut schemars::gen::SchemaGenerator,
30    ) -> openapiv3::v3_1::Operation {
31        OperationBuilder::new()
32            .tag("Epochs")
33            .operation_id("GetEpochLastCheckpoint")
34            .path_parameter::<CheckpointSequenceNumber>("checkpoint", generator)
35            .response(
36                200,
37                ResponseBuilder::new()
38                    .json_content::<SignedCheckpointSummary>(generator)
39                    .bcs_content()
40                    .build(),
41            )
42            .response(404, ResponseBuilder::new().build())
43            .build()
44    }
45
46    fn handler(&self) -> RouteHandler<RestService> {
47        RouteHandler::new(self.method(), get_epoch_last_checkpoint)
48    }
49}
50
51async fn get_epoch_last_checkpoint(
52    Path(epoch): Path<EpochId>,
53    accept: AcceptFormat,
54    State(state): State<StateReader>,
55) -> Result<ResponseContent<SignedCheckpointSummary>> {
56    let summary = state
57        .inner()
58        .get_epoch_last_checkpoint(epoch)?
59        .ok_or_else(|| EpochLastCheckpointNotFoundError::new(epoch))?
60        .into_inner()
61        .try_into()?;
62
63    match accept {
64        AcceptFormat::Json => ResponseContent::Json(summary),
65        AcceptFormat::Bcs => ResponseContent::Bcs(summary),
66    }
67    .pipe(Ok)
68}
69
70#[derive(Debug)]
71pub struct EpochLastCheckpointNotFoundError {
72    epoch: EpochId,
73}
74
75impl EpochLastCheckpointNotFoundError {
76    pub fn new(epoch: EpochId) -> Self {
77        Self { epoch }
78    }
79}
80
81impl std::fmt::Display for EpochLastCheckpointNotFoundError {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        write!(f, "Epoch {} last checkpoint not found", self.epoch)
84    }
85}
86
87impl std::error::Error for EpochLastCheckpointNotFoundError {}
88
89impl From<EpochLastCheckpointNotFoundError> for crate::RestError {
90    fn from(value: EpochLastCheckpointNotFoundError) -> Self {
91        Self::new(axum::http::StatusCode::NOT_FOUND, value.to_string())
92    }
93}