essential_builder_api/
endpoint.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//! Provides a small module for each endpoint with associated `PATH` and `handler`.

use axum::{
    extract::{Path, State},
    response::IntoResponse,
    Json,
};
use essential_builder_db as db;
use essential_types::{solution::Solution, ContentAddress};
use std::sync::Arc;
use thiserror::Error;

/// Any endpoint error that might occur.
#[derive(Debug, Error)]
pub enum Error {
    #[error("failed to decode from hex string: {0}")]
    HexDecode(#[from] hex::FromHexError),
    #[error("DB query failed: {0}")]
    ConnPoolRusqlite(#[from] db::error::AcquireThenRusqliteError),
    #[error("{0}")]
    SystemTime(#[from] SystemTimeError),
}

/// The system time returned a timestamp that preceded `UNIX_EPOCH`.
#[derive(Debug, Error)]
#[error("system time preceded `UNIX_EPOCH`")]
pub struct SystemTimeError;

impl IntoResponse for Error {
    fn into_response(self) -> axum::response::Response {
        use axum::http::StatusCode;
        match self {
            Error::ConnPoolRusqlite(e) => {
                (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response()
            }
            Error::HexDecode(e) => (StatusCode::BAD_REQUEST, e.to_string()).into_response(),
            Error::SystemTime(e) => {
                (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response()
            }
        }
    }
}

/// The return a health check response.
pub mod health_check {
    pub const PATH: &str = "/";
    pub async fn handler() {}
}

/// The `/latest_solution_failures` get endpoint.
///
/// Takes a solution content address (encoded as hex) and a `limit` as a path parameter and returns
/// at most `limit` (or [`latest_solution_failures::MAX_LIMIT`] - whatever's lowest) of
/// the latest failures for the associated solution.
pub mod latest_solution_failures {
    use essential_builder_types::SolutionFailure;
    pub const MAX_LIMIT: u32 = 10;
    use super::*;
    pub const PATH: &str = "/latest-solution-failures/:solution_ca/:limit";
    pub async fn handler(
        State(state): State<crate::State>,
        Path((solution_ca, limit)): Path<(String, u32)>,
    ) -> Result<Json<Vec<SolutionFailure<'static>>>, Error> {
        let solution_ca: ContentAddress = solution_ca.parse()?;
        let limit = limit.min(MAX_LIMIT);
        let failures = state
            .conn_pool
            .latest_solution_failures(solution_ca, limit)
            .await?;
        Ok(Json(failures))
    }
}

/// The `/submit-solution` get endpoint.
///
/// Takes a JSON-serialized [`Solution`], and responds with its [`ContentAddress`] upon
/// successfully adding the solution to the solution pool.
pub mod submit_solution {
    use super::*;
    pub const PATH: &str = "/submit-solution";
    pub async fn handler(
        State(state): State<crate::State>,
        Json(solution): Json<Solution>,
    ) -> Result<Json<ContentAddress>, Error> {
        let solution = Arc::new(solution);
        let timestamp = now_timestamp()?;
        let solution_ca = state
            .conn_pool
            .insert_solution_submission(solution, timestamp)
            .await?;
        Ok(Json(solution_ca))
    }
}

/// Get the current moment in time as a `Duration` since `UNIX_EPOCH`.
fn now_timestamp() -> Result<std::time::Duration, SystemTimeError> {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map_err(|_| SystemTimeError)
}