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