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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use crate::Encoding;
use std::{
fmt::{self, Debug, Formatter},
path::Path,
};
/// A file with its contents stored in a `&'static [u8]`.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct File {
path: &'static str,
contents: &'static [u8],
metadata: Option<crate::Metadata>,
encodings: &'static [(Encoding, &'static [u8])],
etag: Option<&'static str>,
}
impl File {
/// Create a new [`File`].
pub const fn new(path: &'static str, contents: &'static [u8]) -> Self {
File {
path,
contents,
metadata: None,
encodings: &[],
etag: None,
}
}
/// The full path for this [`File`], relative to the directory passed to
/// [`crate::include_dir!()`].
pub fn path(&self) -> &'static Path {
Path::new(self.path)
}
/// The file's raw contents.
pub fn contents(&self) -> &'static [u8] {
self.contents
}
/// The file's contents interpreted as a string.
pub fn contents_utf8(&self) -> Option<&'static str> {
std::str::from_utf8(self.contents()).ok()
}
/// Set the [`Metadata`](crate::Metadata) associated with a [`File`].
pub const fn with_metadata(self, metadata: crate::Metadata) -> Self {
let File {
path,
contents,
encodings,
etag,
..
} = self;
File {
path,
contents,
metadata: Some(metadata),
encodings,
etag,
}
}
/// Get the [`File`]'s [`Metadata`](crate::Metadata), if available.
pub fn metadata(&self) -> Option<&crate::Metadata> {
self.metadata.as_ref()
}
/// Attach precompressed variants. Used by the `static_compiled!` macro
/// when compression is requested; not generally called directly.
///
/// Variants are expected to be sorted smallest-first so that
/// [`pick_encoding`](Self::pick_encoding) returns the smallest variant
/// the client accepts.
pub const fn with_encodings(self, encodings: &'static [(Encoding, &'static [u8])]) -> Self {
let File {
path,
contents,
metadata,
etag,
..
} = self;
File {
path,
contents,
metadata,
encodings,
etag,
}
}
/// Attach a precomputed entity-tag string. Used by the `static_compiled!`
/// macro when etag generation is enabled (the default); not generally
/// called directly.
///
/// The string is expected to be a fully formatted entity-tag literal,
/// including its surrounding quotes — i.e. the value of an `ETag`
/// response header. The macro produces this via
/// [`etag::EntityTag::from_data`], so the bytes match what
/// `trillium_caching_headers::Etag` would compute at runtime.
pub const fn with_etag(self, etag: &'static str) -> Self {
let File {
path,
contents,
metadata,
encodings,
..
} = self;
File {
path,
contents,
metadata,
encodings,
etag: Some(etag),
}
}
/// The precomputed entity-tag, if one was baked at compile time.
pub const fn etag(&self) -> Option<&'static str> {
self.etag
}
/// All precompressed variants attached to this file, in server-preference
/// order (smallest-first).
pub const fn encodings(&self) -> &'static [(Encoding, &'static [u8])] {
self.encodings
}
/// Returns the first precompressed variant whose encoding is permitted by
/// the supplied `Accept-Encoding` header value, or `None` if no variants
/// are attached, no header is supplied, or none are accepted.
pub fn pick_encoding(&self, accept: Option<&str>) -> Option<(Encoding, &'static [u8])> {
let accept = accept?;
self.encodings
.iter()
.copied()
.find(|(encoding, _)| crate::encoding::accept_encoding_allows(accept, encoding.token()))
}
}
impl Debug for File {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("File")
.field("path", &self.path)
.field("contents", &format_args!("<{} bytes>", self.contents.len()))
.field("metadata", &self.metadata)
.field(
"encodings",
&format_args!("<{} variants>", self.encodings.len()),
)
.field("etag", &self.etag)
.finish()
}
}