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