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