essential_builder_api/
endpoint.rs

1//! Provides a small module for each endpoint with associated `PATH` and `handler`.
2
3use axum::{
4    extract::{Path, State},
5    response::IntoResponse,
6    Json,
7};
8use essential_builder_db as db;
9use essential_types::{solution::SolutionSet, ContentAddress};
10use std::sync::Arc;
11use thiserror::Error;
12
13/// Any endpoint error that might occur.
14#[derive(Debug, Error)]
15pub enum Error {
16    #[error("failed to decode from hex string: {0}")]
17    HexDecode(#[from] hex::FromHexError),
18    #[error("DB query failed: {0}")]
19    ConnPoolRusqlite(#[from] db::error::AcquireThenRusqliteError),
20    #[error("{0}")]
21    SystemTime(#[from] SystemTimeError),
22}
23
24/// The system time returned a timestamp that preceded `UNIX_EPOCH`.
25#[derive(Debug, Error)]
26#[error("system time preceded `UNIX_EPOCH`")]
27pub struct SystemTimeError;
28
29impl IntoResponse for Error {
30    fn into_response(self) -> axum::response::Response {
31        use axum::http::StatusCode;
32        match self {
33            Error::ConnPoolRusqlite(e) => {
34                (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response()
35            }
36            Error::HexDecode(e) => (StatusCode::BAD_REQUEST, e.to_string()).into_response(),
37            Error::SystemTime(e) => {
38                (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response()
39            }
40        }
41    }
42}
43
44/// The return a health check response.
45pub mod health_check {
46    pub const PATH: &str = "/";
47    pub async fn handler() {}
48}
49
50/// The `/latest_solution_set_failures` get endpoint.
51///
52/// Takes a solution set content address (encoded as hex) and a `limit` as a path parameter and returns
53/// at most `limit` (or [`latest_solution_set_failures::MAX_LIMIT`] - whatever's lowest) of
54/// the latest failures for the associated solution set.
55pub mod latest_solution_set_failures {
56    use essential_builder_types::SolutionSetFailure;
57    pub const MAX_LIMIT: u32 = 10;
58    use super::*;
59    pub const PATH: &str = "/latest-solution-set-failures/:solution_set_ca/:limit";
60    pub async fn handler(
61        State(state): State<crate::State>,
62        Path((solution_set_ca, limit)): Path<(String, u32)>,
63    ) -> Result<Json<Vec<SolutionSetFailure<'static>>>, Error> {
64        let solution_set_ca: ContentAddress = solution_set_ca.parse()?;
65        let limit = limit.min(MAX_LIMIT);
66        let failures = state
67            .conn_pool
68            .latest_solution_set_failures(solution_set_ca, limit)
69            .await?;
70        Ok(Json(failures))
71    }
72}
73
74/// The `/list_solution_set_failures` get endpoint.
75///
76/// Takes a `start` and a `limit` as a path parameter and returns
77/// at most `limit` of the latest failures for all solution sets.
78/// Note start counts down from the latest failure.
79/// So the latest failure is at `0`.
80pub mod list_solution_set_failures {
81    use super::*;
82    use essential_builder_types::SolutionSetFailure;
83    pub const PATH: &str = "/list-solution-set-failures/:start/:limit";
84    pub async fn handler(
85        State(state): State<crate::State>,
86        Path((offset, limit)): Path<(u32, u32)>,
87    ) -> Result<Json<Vec<SolutionSetFailure<'static>>>, Error> {
88        let failures = state
89            .conn_pool
90            .list_solution_set_failures(offset, limit)
91            .await?;
92        Ok(Json(failures))
93    }
94}
95
96/// The `/submit-solution-set` get endpoint.
97///
98/// Takes a JSON-serialized [`SolutionSet`], and responds with its [`ContentAddress`] upon
99/// successfully adding the solution set to the solution set pool.
100pub mod submit_solution_set {
101    use super::*;
102    pub const PATH: &str = "/submit-solution-set";
103    pub async fn handler(
104        State(state): State<crate::State>,
105        Json(solution_set): Json<SolutionSet>,
106    ) -> Result<Json<ContentAddress>, Error> {
107        let solution_set = Arc::new(solution_set);
108        let timestamp = now_timestamp()?;
109        let solution_set_ca = state
110            .conn_pool
111            .insert_solution_set_submission(solution_set, timestamp)
112            .await?;
113        Ok(Json(solution_set_ca))
114    }
115}
116
117/// Get the current moment in time as a `Duration` since `UNIX_EPOCH`.
118fn now_timestamp() -> Result<std::time::Duration, SystemTimeError> {
119    std::time::SystemTime::now()
120        .duration_since(std::time::UNIX_EPOCH)
121        .map_err(|_| SystemTimeError)
122}