Skip to main content

tower_embed_core/
lib.rs

1//! Core functionalities of tower-embed.
2
3use std::{
4    error::Error,
5    pin::Pin,
6    task::{Context, Poll, ready},
7};
8
9use bytes::Bytes;
10use futures_core::{Stream, stream::BoxStream};
11use http_body::Frame;
12
13pub mod headers;
14
15/// A trait used to access to binary assets in a directory.
16pub trait Embed {
17    /// Get an embedded asset by its path.
18    fn get(path: &str) -> impl Future<Output = std::io::Result<Embedded>> + Send + 'static;
19}
20
21/// An embedded binary asset.
22pub struct Embedded {
23    /// The content of the embedded asset.
24    pub content: Content,
25    /// The metadata associated with the embedded asset.
26    pub metadata: Metadata,
27}
28
29/// Type-erased error type.
30pub type BoxError = Box<dyn Error + Send + Sync>;
31
32/// A stream of binary content.
33pub struct Content(BoxStream<'static, Result<Frame<Bytes>, BoxError>>);
34
35impl Content {
36    /// Creates a [`Content`] from a static slice of bytes.
37    pub fn from_static(bytes: &'static [u8]) -> Self {
38        Self(Box::pin(StaticContent::new(bytes)))
39    }
40
41    /// Creates a [`Content`] from a stream of frames.
42    pub fn from_stream<S, E>(stream: S) -> Self
43    where
44        S: Stream<Item = Result<Frame<Bytes>, E>> + Send + 'static,
45        E: Into<BoxError>,
46    {
47        Self(Box::pin(StreamContent(stream)))
48    }
49}
50
51impl Stream for Content {
52    type Item = Result<Frame<Bytes>, BoxError>;
53
54    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
55        self.0.as_mut().poll_next(cx)
56    }
57}
58
59struct StaticContent(Option<&'static [u8]>);
60
61impl StaticContent {
62    pub fn new(bytes: &'static [u8]) -> Self {
63        Self(Some(bytes))
64    }
65}
66
67impl Stream for StaticContent {
68    type Item = Result<Frame<Bytes>, BoxError>;
69
70    fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
71        self.0
72            .take()
73            .map(|bytes| Ok(Frame::data(Bytes::from_static(bytes))))
74            .into()
75    }
76}
77
78struct StreamContent<S>(S);
79
80impl<S, E> Stream for StreamContent<S>
81where
82    S: Stream<Item = Result<Frame<Bytes>, E>>,
83    E: Into<BoxError>,
84{
85    type Item = Result<Frame<Bytes>, BoxError>;
86
87    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
88        let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
89        match ready!(inner.poll_next(cx)) {
90            Some(Ok(frame)) => Some(Ok(frame)),
91            Some(Err(err)) => Some(Err(err.into())),
92            None => None,
93        }
94        .into()
95    }
96}
97
98/// Metadata associated with an embedded asset.
99#[derive(Clone, Debug)]
100pub struct Metadata {
101    /// MIME type of the resource.
102    pub content_type: headers::ContentType,
103    /// File unique identifier, to be used to match with `If-None-Match` header.
104    pub etag: Option<headers::ETag>,
105    /// The date and time when the resource was modified.
106    pub last_modified: Option<headers::LastModified>,
107}
108
109/// Returns the last modification time of file.
110pub fn last_modified(path: &std::path::Path) -> std::io::Result<headers::LastModified> {
111    std::fs::metadata(path)
112        .and_then(|metadata| metadata.modified())
113        .map(headers::LastModified)
114}
115
116/// Returns the MIME type of file.
117pub fn content_type(path: &std::path::Path) -> headers::ContentType {
118    mime_guess::from_path(path)
119        .first()
120        .map(headers::ContentType)
121        .unwrap_or_else(headers::ContentType::octet_stream)
122}
123
124/// Returns the unique identifier tag of the content.
125pub fn etag(content: &[u8]) -> headers::ETag {
126    use std::hash::Hasher;
127
128    let hash: u64 = {
129        let mut hasher = rapidhash::fast::RapidHasher::default_const();
130        hasher.write(content);
131        hasher.finish()
132    };
133
134    let etag = format!("{:016x}", hash);
135    headers::ETag::new(&etag).unwrap()
136}