aboutsummaryrefslogtreecommitdiffstats
path: root/src/server/mod.rs
blob: 3301035183bba940cfd2d491b5d38f9945fb2726 (plain)
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
pub mod driver;
pub mod error;
mod middleware;
pub mod routes;
pub mod shutdown;
pub mod state;

use std::time::Duration;

use activitypub_federation::config::{FederationConfig, FederationMiddleware};
use axum::{
    Router,
    http::{HeaderName, StatusCode},
};
use tower_http::{
    cors::{self, CorsLayer},
    request_id::PropagateRequestIdLayer,
    timeout::TimeoutLayer,
    trace::TraceLayer,
};
use tracing::{error, info_span};
use utoipa::OpenApi;
use utoipa_axum::router::OpenApiRouter;

use crate::{
    config::Config,
    server::{
        middleware::request_id::{REQUEST_ID_HEADER, add_request_id},
        routes::auth::OAuthDoc,
        state::{AppState, federation},
    },
};

#[derive(OpenApi)]
#[openapi(
    tags(
        (name = routes::HEALTH, description = "Check API health"),
    ),
)]
pub struct ApiDoc;

pub async fn router(config: &Config, state: AppState) -> anyhow::Result<Router<()>> {
    let state = federation::add_federation(state, config).await?;

    let mut doc = ApiDoc::openapi();
    doc.merge(OAuthDoc::openapi());

    let (router, _api) = OpenApiRouter::with_openapi(doc)
        .routes(utoipa_axum::routes!(routes::health_check))
        .routes(utoipa_axum::routes!(routes::auth::auth))
        .split_for_parts();

    #[cfg(feature = "swagger")]
    let router = router.merge(
        utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
            .url("/api-docs/swaggerdoc.json", _api.clone()),
    );

    #[cfg(feature = "redoc")]
    let router = {
        use utoipa_redoc::Servable as _;
        router.merge(utoipa_redoc::Redoc::with_url("/redoc", _api.clone()))
    };

    #[cfg(feature = "scalar")]
    let router = {
        use utoipa_scalar::Servable as _;
        router.merge(utoipa_scalar::Scalar::with_url("/scalar", _api.clone()))
    };

    #[cfg(feature = "rapidoc")]
    let router = router.merge(
        utoipa_rapidoc::RapiDoc::with_openapi("/api-docs/rapidoc.json", _api).path("/rapidoc"),
    );

    let router = router
        .layer(
            TraceLayer::new_for_http().make_span_with(|request: &axum::http::Request<_>| {
                if let Some(request_id) = request.headers().get(REQUEST_ID_HEADER) {
                    info_span!(
                        "http_request",
                        request_id = ?request_id,
                    )
                } else {
                    error!("could not extract request_id");
                    info_span!("http_request")
                }
            }),
        )
        .layer(TimeoutLayer::with_status_code(
            StatusCode::REQUEST_TIMEOUT,
            Duration::from_secs(config.server.request_timeout),
        ))
        .layer(FederationMiddleware::new(state))
        // send headers from request to response headers
        .layer(PropagateRequestIdLayer::new(HeaderName::from_static(
            REQUEST_ID_HEADER,
        )))
        .layer(axum::middleware::from_fn(add_request_id))
        .layer(
            CorsLayer::new()
                .allow_origin(cors::Any)
                .allow_headers(cors::Any)
                .allow_methods(cors::Any),
        );

    Ok(router)
}

#[cfg(test)]
pub mod bootstrap {
    use async_trait::async_trait;

    use crate::server::driver::SellershutDriver;

    #[derive(Debug, Default)]
    pub struct TestDriver {}

    #[async_trait]
    impl SellershutDriver for TestDriver {
        async fn hello(&self) {
            todo!()
        }
    }
}