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
//! static file serving.

use core::convert::Infallible;

use std::{borrow::Cow, path::PathBuf};

use http_file::{ServeDir as _ServeDir, ServeError};
use xitca_http::{http::StatusCode, util::service::router::RouterGen};

use crate::{
    body::ResponseBody,
    context::WebContext,
    error::{BadRequest, Error, Internal, MatchError, MethodNotAllowed, RouterError},
    http::{Method, WebResponse},
    service::Service,
};

/// builder type for serve dir service.
pub struct ServeDir {
    inner: _ServeDir,
}

impl ServeDir {
    /// construct a new static file service that serve given relative file path's file based on file name.
    ///
    /// # Example
    /// ```rust
    /// # use xitca_web::{
    /// #     handler::{handler_service},
    /// #     service::file::ServeDir,
    /// #     App, WebContext
    /// # };
    /// App::new()
    ///     // http request would be matched against files inside ./static file path.
    ///     .at("/", ServeDir::new("static"))
    ///     // other named routes have higher priority than serve dir service.
    ///     .at("/foo", handler_service(|| async { "foo!" }))
    ///     # .at("/bar", handler_service(|_: &WebContext<'_>| async { "used for inferring types!" }));
    /// ```
    pub fn new(path: impl Into<PathBuf>) -> Self {
        Self {
            inner: _ServeDir::new(path),
        }
    }
}

impl RouterGen for ServeDir {
    type Route<R> = R;

    fn path_gen(&mut self, prefix: &'static str) -> Cow<'static, str> {
        let mut path = String::from(prefix);
        if path.ends_with('/') {
            path.pop();
        }

        path.push_str("/*p");

        Cow::Owned(path)
    }

    fn route_gen<R>(route: R) -> Self::Route<R> {
        route
    }
}

impl Service for ServeDir {
    type Response = ServeDirService;
    type Error = Infallible;

    async fn call(&self, _: ()) -> Result<Self::Response, Self::Error> {
        Ok(ServeDirService(self.inner.clone()))
    }
}

#[doc(hidden)]
pub struct ServeDirService(_ServeDir);

impl<'r, C, B> Service<WebContext<'r, C, B>> for ServeDirService {
    type Response = WebResponse;
    type Error = RouterError<Error<C>>;

    async fn call(&self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
        match self.0.serve(ctx.req()).await {
            Ok(res) => Ok(res.map(ResponseBody::box_stream)),
            Err(ServeError::NotModified) => {
                let mut res = ctx.into_response(ResponseBody::none());
                *res.status_mut() = StatusCode::NOT_MODIFIED;
                Ok(res)
            }
            Err(e) => Err(match e {
                ServeError::NotFound => RouterError::Match(MatchError),
                ServeError::MethodNotAllowed => {
                    RouterError::NotAllowed(MethodNotAllowed(vec![Method::GET, Method::HEAD]))
                }
                ServeError::Io(_) => RouterError::Service(Error::from_service(Internal)),
                _ => RouterError::Service(Error::from_service(BadRequest)),
            }),
        }
    }
}