cat_gateway/service/
poem_service.rs

1//! Poem Service for cat-gateway service endpoints.
2//!
3//! This provides only the primary entrypoint to the service.
4
5use cardano_chain_follower::Network;
6use poem::{
7    Endpoint, EndpointExt, Route,
8    listener::TcpListener,
9    middleware::{Compression, Cors, SensitiveHeader},
10    web::CompressionLevel,
11};
12
13use super::{
14    common::auth::{api_key::API_KEY_HEADER, rbac::scheme::AUTHORIZATION_HEADER},
15    utilities::middleware::{db_check::DatabaseConnectionCheck, node_info::CatGatewayInfo},
16};
17use crate::{
18    service::{
19        api::mk_api,
20        docs::{docs, favicon},
21        utilities::{
22            middleware::{
23                catch_panic::{CatchPanicMiddleware, set_panic_hook},
24                metrics_updater::MetricsUpdaterMiddleware,
25                tracing_mw::Tracing,
26            },
27            panic::panic_endpoint,
28        },
29    },
30    settings::Settings,
31};
32
33/// This exists to allow us to add extra routes to the service for testing purposes.
34fn mk_app(base_route: Option<Route>) -> impl Endpoint {
35    // Get the base route if defined, or a new route if not.
36    let mut base_route = match base_route {
37        Some(route) => route,
38        None => Route::new(),
39    };
40
41    let api_service = mk_api();
42    let docs = docs(&api_service);
43
44    if Settings::cardano_network() != &Network::Mainnet && Settings::is_panic_endpoint_enabled() {
45        base_route = base_route.nest("/panic", panic_endpoint);
46    }
47
48    base_route
49        .nest(Settings::api_url_prefix(), api_service)
50        .nest("/docs", docs)
51        .nest("/metrics", MetricsUpdaterMiddleware::new())
52        .nest("/favicon.ico", favicon())
53        .with(Cors::new())
54        .with(Compression::new().with_quality(CompressionLevel::Fastest))
55        .with(CatchPanicMiddleware::new())
56        .with(Tracing)
57        .with(DatabaseConnectionCheck)
58        .with(CatGatewayInfo)
59        .with(
60            SensitiveHeader::new()
61                .header(API_KEY_HEADER)
62                .header(AUTHORIZATION_HEADER)
63                .request_only(),
64        )
65}
66
67/// Get the API docs as a string in the JSON format.
68pub(crate) fn get_app_docs() -> String {
69    let api_service = mk_api();
70    api_service.spec()
71}
72
73/// Run the Poem Service
74///
75/// This provides only the primary entrypoint to the service.
76///
77/// # Arguments
78///
79/// *`settings`: &`DocsSetting` - settings for docs
80///
81/// # Errors
82///
83/// * `Error::CannotRunService` - cannot run the service
84/// * `Error::EventDbError` - cannot connect to the event db
85/// * `Error::IoError` - An IO error has occurred.
86pub(crate) async fn run() -> anyhow::Result<()> {
87    // The address to listen on
88    tracing::info!(
89        ServiceAddr = Settings::bound_address().to_string(),
90        "Starting Cat-Gateway API Service ..."
91    );
92
93    // Set a custom panic hook, so we can catch panics and not crash the service.
94    // And also get data from the panic so we can log it.
95    // Panics will cause a 500 to be sent with minimal information we can use to
96    // help find them in the logs if they happen in production.
97    set_panic_hook();
98
99    let app = mk_app(None);
100
101    Ok(
102        poem::Server::new(TcpListener::bind(Settings::bound_address()))
103            .run(app)
104            .await?,
105    )
106}
107
108#[cfg(test)]
109pub(crate) mod tests {
110    // Poem TEST violates our License by using Copyleft libraries.
111    // use poem::test::TestClient;
112    //
113    // use super::*;
114    //
115    // pub(crate) fn mk_test_app(state: &Arc<State>) -> TestClient<impl Endpoint> {
116    // let app = mk_app(vec![], None, state);
117    // TestClient::new(app)
118    // }
119}