iota_rest_api/
committee.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use axum::extract::{Path, State};
6use iota_sdk2::types::{EpochId, ValidatorCommittee};
7use iota_types::storage::ReadStore;
8use tap::Pipe;
9
10use crate::{
11    RestService, Result,
12    accept::AcceptFormat,
13    openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler},
14    reader::StateReader,
15    response::ResponseContent,
16};
17
18pub struct GetLatestCommittee;
19
20impl ApiEndpoint<RestService> for GetLatestCommittee {
21    fn method(&self) -> axum::http::Method {
22        axum::http::Method::GET
23    }
24
25    fn path(&self) -> &'static str {
26        "/system/committee"
27    }
28
29    fn operation(
30        &self,
31        generator: &mut schemars::gen::SchemaGenerator,
32    ) -> openapiv3::v3_1::Operation {
33        OperationBuilder::new()
34            .tag("System")
35            .operation_id("GetLatestCommittee")
36            .response(
37                200,
38                ResponseBuilder::new()
39                    .json_content::<ValidatorCommittee>(generator)
40                    .bcs_content()
41                    .build(),
42            )
43            .build()
44    }
45
46    fn handler(&self) -> RouteHandler<RestService> {
47        RouteHandler::new(self.method(), get_latest_committee)
48    }
49}
50
51async fn get_latest_committee(
52    accept: AcceptFormat,
53    State(state): State<StateReader>,
54) -> Result<ResponseContent<ValidatorCommittee>> {
55    let current_epoch = state.inner().get_latest_checkpoint()?.epoch();
56    let committee = state
57        .get_committee(current_epoch)?
58        .ok_or_else(|| CommitteeNotFoundError::new(current_epoch))?;
59
60    match accept {
61        AcceptFormat::Json => ResponseContent::Json(committee),
62        AcceptFormat::Bcs => ResponseContent::Bcs(committee),
63    }
64    .pipe(Ok)
65}
66
67pub struct GetCommittee;
68
69impl ApiEndpoint<RestService> for GetCommittee {
70    fn method(&self) -> axum::http::Method {
71        axum::http::Method::GET
72    }
73
74    fn path(&self) -> &'static str {
75        "/system/committee/{epoch}"
76    }
77
78    fn operation(
79        &self,
80        generator: &mut schemars::gen::SchemaGenerator,
81    ) -> openapiv3::v3_1::Operation {
82        OperationBuilder::new()
83            .tag("System")
84            .operation_id("GetCommittee")
85            .path_parameter::<EpochId>("epoch", generator)
86            .response(
87                200,
88                ResponseBuilder::new()
89                    .json_content::<ValidatorCommittee>(generator)
90                    .bcs_content()
91                    .build(),
92            )
93            .response(404, ResponseBuilder::new().build())
94            .build()
95    }
96
97    fn handler(&self) -> RouteHandler<RestService> {
98        RouteHandler::new(self.method(), get_committee)
99    }
100}
101
102async fn get_committee(
103    Path(epoch): Path<EpochId>,
104    accept: AcceptFormat,
105    State(state): State<StateReader>,
106) -> Result<ResponseContent<ValidatorCommittee>> {
107    let committee = state
108        .get_committee(epoch)?
109        .ok_or_else(|| CommitteeNotFoundError::new(epoch))?;
110
111    match accept {
112        AcceptFormat::Json => ResponseContent::Json(committee),
113        AcceptFormat::Bcs => ResponseContent::Bcs(committee),
114    }
115    .pipe(Ok)
116}
117
118#[derive(Debug)]
119pub struct CommitteeNotFoundError {
120    epoch: EpochId,
121}
122
123impl CommitteeNotFoundError {
124    pub fn new(epoch: EpochId) -> Self {
125        Self { epoch }
126    }
127}
128
129impl std::fmt::Display for CommitteeNotFoundError {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        write!(f, "Committee for epoch {} not found", self.epoch)
132    }
133}
134
135impl std::error::Error for CommitteeNotFoundError {}
136
137impl From<CommitteeNotFoundError> for crate::RestError {
138    fn from(value: CommitteeNotFoundError) -> Self {
139        Self::new(axum::http::StatusCode::NOT_FOUND, value.to_string())
140    }
141}