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