#
mcaptcha/
docs.rs

    1// Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
    2// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
    3//
    4// SPDX-License-Identifier: AGPL-3.0-or-later
    5
    6use std::borrow::Cow;
    7
    8use actix_web::body::BoxBody;
    9use actix_web::{http::header, web, HttpResponse, Responder};
    10use mime_guess::from_path;
    11use rust_embed::RustEmbed;
    12
    13use crate::CACHE_AGE;
    14
    15pub const DOCS: routes::Docs = routes::Docs::new();
    16
    17pub mod routes {
    18    pub struct Docs {
    19        pub home: &'static str,
    20        pub spec: &'static str,
    21        pub assets: &'static str,
    22    }
    23
    24    impl Docs {
    25        pub const fn new() -> Self {
    26            Docs {
    27                home: "/docs/",
    28                spec: "/docs/openapi.yaml",
    29                assets: "/docs/{_:.*}",
    30            }
    31        }
    32    }
    33}
    34
    35pub fn services(cfg: &mut web::ServiceConfig) {
    36    cfg.service(index).service(spec).service(dist);
    37}
    38
    39#[derive(RustEmbed)]
    40#[folder = "static/openapi/"]
    41struct Asset;
    42
    43pub fn handle_embedded_file(path: &str) -> HttpResponse {
    44    match Asset::get(path) {
    45        Some(content) => {
    46            let body: BoxBody = match content.data {
    47                Cow::Borrowed(bytes) => BoxBody::new(bytes),
    48                Cow::Owned(bytes) => BoxBody::new(bytes),
    49            };
    50
    51            HttpResponse::Ok()
    52                .insert_header(header::CacheControl(vec![
    53                    header::CacheDirective::Public,
    54                    header::CacheDirective::Extension("immutable".into(), None),
    55                    header::CacheDirective::MaxAge(CACHE_AGE),
    56                ]))
    57                .content_type(from_path(path).first_or_octet_stream().as_ref())
    58                .body(body)
    59        }
    60        None => HttpResponse::NotFound().body("404 Not Found"),
    61    }
    62}
    63
    64#[my_codegen::get(path = "DOCS.assets")]
    65async fn dist(path: web::Path<String>) -> impl Responder {
    66    handle_embedded_file(&path)
    67}
    68const OPEN_API_SPEC: &str = include_str!("../docs/openapi/dist/openapi.yaml");
    69
    70#[my_codegen::get(path = "DOCS.spec")]
    71async fn spec() -> HttpResponse {
    72    HttpResponse::Ok()
    73        .content_type("text/yaml")
    74        .body(OPEN_API_SPEC)
    75}
    76
    77#[my_codegen::get(path = "&DOCS.home[0..DOCS.home.len() -1]")]
    78async fn index() -> HttpResponse {
    79    handle_embedded_file("index.html")
    80}
    81
    82#[cfg(test)]
    83mod tests {
    84    use actix_web::http::StatusCode;
    85    use actix_web::test;
    86
    87    use super::*;
    88    use crate::*;
    89
    90    #[actix_rt::test]
    91    async fn docs_works() {
    92        const FILE: &str = "favicon-32x32.png";
    93
    94        let app = test::init_service(
    95            App::new()
    96                .wrap(actix_middleware::NormalizePath::new(
    97                    actix_middleware::TrailingSlash::Trim,
    98                ))
    99                .configure(services),
    100        )
    101        .await;
    102
    103        let resp = test::call_service(
    104            &app,
    105            test::TestRequest::get().uri(DOCS.home).to_request(),
    106        )
    107        .await;
    108        assert_eq!(resp.status(), StatusCode::OK);
    109
    110        let resp = test::call_service(
    111            &app,
    112            test::TestRequest::get().uri(DOCS.spec).to_request(),
    113        )
    114        .await;
    115        assert_eq!(resp.status(), StatusCode::OK);
    116
    117        let uri = format!("{}{}", DOCS.home, FILE);
    118
    119        let resp =
    120            test::call_service(&app, test::TestRequest::get().uri(&uri).to_request())
    121                .await;
    122        assert_eq!(resp.status(), StatusCode::OK);
    123    }
    124}