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
//! # Inlucde NPM
//!
//! Include Static `npm build` ouput in your rust binary
//! When you compile in debug mode the File contents will just be read from disk and not embedded. This can manually be overriden with the `embed` feautere.
//!
//! The argument path is the ouput directory of the npm build. The parent directory is the directory with package.json
//!
//! ```ignore
//! const ASSETS: inpm::Dir = inpm::include_package!("./client/dist");
//!
//!
//! let content_of_my_file = ASSETS.get("some_dir/my_file.txt").unwrap().contents();
//!
//! ```
//!
//! ## Warp feature
//!
//! `features=["warp"]`
//!
//! ```ignore
//! const ASSETS: inpm::Dir = inpm::include_package!("./client/dist");
//!
//!
//! let my_file_filter = inpm::warp::embedded(ASSETS);
//!
//! // Allso works with single page applications
//!
//! let my_spa_filter = inpm::warp::spa(ASSETS, "index.html");
//!
//!
//! ```

use proc_macro_hack::proc_macro_hack;

mod dir;
mod file;
pub use crate::dir::Dir;
pub use crate::file::File;

#[proc_macro_hack]
pub use inpm_impl::include_package;

#[cfg(feature = "warp")]
pub mod warp {
    use crate::File;
    use warpd::{
        reject::Rejection,
        reply::{self, Reply},
        Filter,
    };

    fn with<T: Clone + Send + Sync>(
        source: T,
    ) -> impl Filter<Extract = (T,), Error = std::convert::Infallible> + Clone {
        warpd::any().map(move || source.clone())
    }

    pub fn embedded(
        dir: crate::Dir,
    ) -> impl Filter<Extract = (warpd::reply::WithHeader<File>,), Error = Rejection> + Clone {
        warpd::get()
            .and(warpd::path::tail())
            .and(with(dir))
            .and_then(|tail: warpd::path::Tail, dir: crate::Dir| async move {
                dir.get(tail.as_str())
                    .map(|file| {
                        let mime = mime_guess::from_path(file.path()).first_or_octet_stream();
                        warpd::reply::with_header(
                            file,
                            warpd::http::header::CONTENT_TYPE,
                            mime.as_ref(),
                        )
                    })
                    .ok_or(warpd::reject::not_found())
            })
    }

    // Put this last in the filter chain
    pub fn spa(
        dir: crate::Dir,
        entry: &'static str,
    ) -> impl Filter<Extract = (warpd::reply::WithHeader<File>,), Error = Rejection> + Clone {
        warpd::get()
            .and(warpd::path::tail())
            .and(with(dir))
            .and(with(entry))
            .and_then(
                |tail: warpd::path::Tail, dir: crate::Dir, entry: &'static str| async move {
                    dir.get(tail.as_str())
                        .or(dir.get(entry))
                        .map(|file| {
                            let mime = mime_guess::from_path(file.path()).first_or_octet_stream();

                            warpd::reply::with_header(
                                file,
                                warpd::http::header::CONTENT_TYPE,
                                mime.as_ref(),
                            )
                        })
                        .ok_or(warpd::reject::not_found())
                },
            )
    }

    impl Reply for File {
        fn into_response(self) -> reply::Response {
            self.contents().into_owned().into_response()
        }
    }
}