1use std::fmt;
9use super::error::{B2Error, Error};
10
11use percent_encoding::{AsciiSet, CONTROLS};
12use serde::{Serialize, Deserialize};
13
14pub use http_types::{
15 cache::{CacheDirective, Expires},
16 content::ContentEncoding,
17 mime::Mime,
18};
19
20
21#[derive(serde::Deserialize)]
24#[serde(untagged)]
25#[must_use]
26pub(crate) enum B2Result<T> {
27 Ok(T),
28 Err(B2Error),
29}
30
31impl<T, E> From<B2Result<T>> for std::result::Result<T, Error<E>>
32 where E: fmt::Debug + fmt::Display,
33{
34 fn from(r: B2Result<T>) -> std::result::Result<T, Error<E>> {
35 match r {
36 B2Result::Ok(v) => Ok(v),
37 B2Result::Err(e) => Err(crate::error::Error::B2(e)),
38 }
39 }
40}
41
42impl<T> B2Result<T> {
43 pub fn map<U, F>(self, op: F) -> B2Result<U>
44 where F: FnOnce(T) -> U,
45 {
46 match self {
47 B2Result::Ok(v) => B2Result::Ok(op(v)),
48 B2Result::Err(e) => B2Result::Err(e),
49 }
50 }
51}
52
53pub struct ContentDisposition(pub(crate) String);
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
61pub(crate) struct Duration(pub(crate) chrono::Duration);
62
63impl std::ops::Deref for Duration {
64 type Target = chrono::Duration;
65
66 fn deref(&self) -> &Self::Target { &self.0 }
67}
68
69impl From<chrono::Duration> for Duration {
70 fn from(d: chrono::Duration) -> Self {
71 Self(d)
72 }
73}
74
75impl From<Duration> for chrono::Duration {
76 fn from(d: Duration) -> Self { d.0 }
77}
78
79impl Serialize for Duration {
80 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81 where S: serde::Serializer,
82 {
83 serializer.serialize_i64(self.num_milliseconds())
84 }
85}
86
87struct DurationVisitor;
88
89impl<'de> serde::de::Visitor<'de> for DurationVisitor {
90 type Value = i64;
91
92 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
93 write!(
94 formatter,
95 "the number of milliseconds representing the duration"
96 )
97 }
98
99 fn visit_i64<E>(self, s: i64) -> Result<Self::Value, E>
100 where E: serde::de::Error,
101 {
102 Ok(s)
103 }
104}
105
106impl<'de> Deserialize<'de> for Duration {
107 fn deserialize<D>(deserializer: D) -> Result<Duration, D::Error>
108 where D: serde::Deserializer<'de>,
109 {
110 deserializer.deserialize_i64(DurationVisitor)
111 .map(|i| Duration(chrono::Duration::milliseconds(i)))
112 }
113}
114
115pub(crate) const QUERY_ENCODE_SET: AsciiSet = CONTROLS
117 .add(b' ')
118 .add(b'"')
119 .add(b'#')
120 .add(b'%')
121 .add(b'&')
122 .add(b'+')
123 .add(b',')
124 .add(b'<')
125 .add(b'>')
126 .add(b'?')
127 .add(b'[')
128 .add(b']')
129 .add(b'\\')
130 .add(b'^')
131 .add(b'`')
132 .add(b'{')
133 .add(b'|')
134 .add(b'}');
135
136#[cfg(test)]
137mod tests {
138 #[test]
139 fn b2_filename_encoding_tests() {
140 use crate::types::QUERY_ENCODE_SET;
141 use percent_encoding::utf8_percent_encode;
142
143 let tests = serde_json::json!([
146 {"fullyEncoded": "%20", "minimallyEncoded": "+", "string": " "},
147 {"fullyEncoded": "%21", "minimallyEncoded": "!", "string": "!"},
148 {"fullyEncoded": "%22", "minimallyEncoded": "%22", "string": "\""},
149 {"fullyEncoded": "%23", "minimallyEncoded": "%23", "string": "#"},
150 {"fullyEncoded": "%24", "minimallyEncoded": "$", "string": "$"},
151 {"fullyEncoded": "%25", "minimallyEncoded": "%25", "string": "%"},
152 {"fullyEncoded": "%26", "minimallyEncoded": "%26", "string": "&"},
153 {"fullyEncoded": "%27", "minimallyEncoded": "'", "string": "'"},
154 {"fullyEncoded": "%28", "minimallyEncoded": "(", "string": "("},
155 {"fullyEncoded": "%29", "minimallyEncoded": ")", "string": ")"},
156 {"fullyEncoded": "%2A", "minimallyEncoded": "*", "string": "*"},
157 {"fullyEncoded": "%2B", "minimallyEncoded": "%2B", "string": "+"},
158 {"fullyEncoded": "%2C", "minimallyEncoded": "%2C", "string": ","},
159 {"fullyEncoded": "%2D", "minimallyEncoded": "-", "string": "-"},
160 {"fullyEncoded": "%2E", "minimallyEncoded": ".", "string": "."},
161 {"fullyEncoded": "/", "minimallyEncoded": "/", "string": "/"},
162 {"fullyEncoded": "%30", "minimallyEncoded": "0", "string": "0"},
163 {"fullyEncoded": "%31", "minimallyEncoded": "1", "string": "1"},
164 {"fullyEncoded": "%32", "minimallyEncoded": "2", "string": "2"},
165 {"fullyEncoded": "%33", "minimallyEncoded": "3", "string": "3"},
166 {"fullyEncoded": "%34", "minimallyEncoded": "4", "string": "4"},
167 {"fullyEncoded": "%35", "minimallyEncoded": "5", "string": "5"},
168 {"fullyEncoded": "%36", "minimallyEncoded": "6", "string": "6"},
169 {"fullyEncoded": "%37", "minimallyEncoded": "7", "string": "7"},
170 {"fullyEncoded": "%38", "minimallyEncoded": "8", "string": "8"},
171 {"fullyEncoded": "%39", "minimallyEncoded": "9", "string": "9"},
172 {"fullyEncoded": "%3A", "minimallyEncoded": ":", "string": ":"},
173 {"fullyEncoded": "%3B", "minimallyEncoded": ";", "string": ";"},
174 {"fullyEncoded": "%3C", "minimallyEncoded": "%3C", "string": "<"},
175 {"fullyEncoded": "%3D", "minimallyEncoded": "=", "string": "="},
176 {"fullyEncoded": "%3E", "minimallyEncoded": "%3E", "string": ">"},
177 {"fullyEncoded": "%3F", "minimallyEncoded": "%3F", "string": "?"},
178 {"fullyEncoded": "%40", "minimallyEncoded": "@", "string": "@"},
179 {"fullyEncoded": "%41", "minimallyEncoded": "A", "string": "A"},
180 {"fullyEncoded": "%42", "minimallyEncoded": "B", "string": "B"},
181 {"fullyEncoded": "%43", "minimallyEncoded": "C", "string": "C"},
182 {"fullyEncoded": "%44", "minimallyEncoded": "D", "string": "D"},
183 {"fullyEncoded": "%45", "minimallyEncoded": "E", "string": "E"},
184 {"fullyEncoded": "%46", "minimallyEncoded": "F", "string": "F"},
185 {"fullyEncoded": "%47", "minimallyEncoded": "G", "string": "G"},
186 {"fullyEncoded": "%48", "minimallyEncoded": "H", "string": "H"},
187 {"fullyEncoded": "%49", "minimallyEncoded": "I", "string": "I"},
188 {"fullyEncoded": "%4A", "minimallyEncoded": "J", "string": "J"},
189 {"fullyEncoded": "%4B", "minimallyEncoded": "K", "string": "K"},
190 {"fullyEncoded": "%4C", "minimallyEncoded": "L", "string": "L"},
191 {"fullyEncoded": "%4D", "minimallyEncoded": "M", "string": "M"},
192 {"fullyEncoded": "%4E", "minimallyEncoded": "N", "string": "N"},
193 {"fullyEncoded": "%4F", "minimallyEncoded": "O", "string": "O"},
194 {"fullyEncoded": "%50", "minimallyEncoded": "P", "string": "P"},
195 {"fullyEncoded": "%51", "minimallyEncoded": "Q", "string": "Q"},
196 {"fullyEncoded": "%52", "minimallyEncoded": "R", "string": "R"},
197 {"fullyEncoded": "%53", "minimallyEncoded": "S", "string": "S"},
198 {"fullyEncoded": "%54", "minimallyEncoded": "T", "string": "T"},
199 {"fullyEncoded": "%55", "minimallyEncoded": "U", "string": "U"},
200 {"fullyEncoded": "%56", "minimallyEncoded": "V", "string": "V"},
201 {"fullyEncoded": "%57", "minimallyEncoded": "W", "string": "W"},
202 {"fullyEncoded": "%58", "minimallyEncoded": "X", "string": "X"},
203 {"fullyEncoded": "%59", "minimallyEncoded": "Y", "string": "Y"},
204 {"fullyEncoded": "%5A", "minimallyEncoded": "Z", "string": "Z"},
205 {"fullyEncoded": "%5B", "minimallyEncoded": "%5B", "string": "["},
206 {"fullyEncoded": "%5C", "minimallyEncoded": "%5C", "string": "\\"},
207 {"fullyEncoded": "%5D", "minimallyEncoded": "%5D", "string": "]"},
208 {"fullyEncoded": "%5E", "minimallyEncoded": "%5E", "string": "^"},
209 {"fullyEncoded": "%5F", "minimallyEncoded": "_", "string": "_"},
210 {"fullyEncoded": "%60", "minimallyEncoded": "%60", "string": "`"},
211 {"fullyEncoded": "%61", "minimallyEncoded": "a", "string": "a"},
212 {"fullyEncoded": "%62", "minimallyEncoded": "b", "string": "b"},
213 {"fullyEncoded": "%63", "minimallyEncoded": "c", "string": "c"},
214 {"fullyEncoded": "%64", "minimallyEncoded": "d", "string": "d"},
215 {"fullyEncoded": "%65", "minimallyEncoded": "e", "string": "e"},
216 {"fullyEncoded": "%66", "minimallyEncoded": "f", "string": "f"},
217 {"fullyEncoded": "%67", "minimallyEncoded": "g", "string": "g"},
218 {"fullyEncoded": "%68", "minimallyEncoded": "h", "string": "h"},
219 {"fullyEncoded": "%69", "minimallyEncoded": "i", "string": "i"},
220 {"fullyEncoded": "%6A", "minimallyEncoded": "j", "string": "j"},
221 {"fullyEncoded": "%6B", "minimallyEncoded": "k", "string": "k"},
222 {"fullyEncoded": "%6C", "minimallyEncoded": "l", "string": "l"},
223 {"fullyEncoded": "%6D", "minimallyEncoded": "m", "string": "m"},
224 {"fullyEncoded": "%6E", "minimallyEncoded": "n", "string": "n"},
225 {"fullyEncoded": "%6F", "minimallyEncoded": "o", "string": "o"},
226 {"fullyEncoded": "%70", "minimallyEncoded": "p", "string": "p"},
227 {"fullyEncoded": "%71", "minimallyEncoded": "q", "string": "q"},
228 {"fullyEncoded": "%72", "minimallyEncoded": "r", "string": "r"},
229 {"fullyEncoded": "%73", "minimallyEncoded": "s", "string": "s"},
230 {"fullyEncoded": "%74", "minimallyEncoded": "t", "string": "t"},
231 {"fullyEncoded": "%75", "minimallyEncoded": "u", "string": "u"},
232 {"fullyEncoded": "%76", "minimallyEncoded": "v", "string": "v"},
233 {"fullyEncoded": "%77", "minimallyEncoded": "w", "string": "w"},
234 {"fullyEncoded": "%78", "minimallyEncoded": "x", "string": "x"},
235 {"fullyEncoded": "%79", "minimallyEncoded": "y", "string": "y"},
236 {"fullyEncoded": "%7A", "minimallyEncoded": "z", "string": "z"},
237 {"fullyEncoded": "%7B", "minimallyEncoded": "%7B", "string": "{"},
238 {"fullyEncoded": "%7C", "minimallyEncoded": "%7C", "string": "|"},
239 {"fullyEncoded": "%7D", "minimallyEncoded": "%7D", "string": "}"},
240 {"fullyEncoded": "%7E", "minimallyEncoded": "~", "string": "~"},
241 {
242 "fullyEncoded": "%7F",
243 "minimallyEncoded": "%7F",
244 "string": "\u{007f}"
245 },
246 {
247 "fullyEncoded": "%E8%87%AA%E7%94%B1",
248 "minimallyEncoded": "%E8%87%AA%E7%94%B1",
249 "string": "\u{81ea}\u{7531}"
250 },
251 ]);
259
260 let tests = tests.as_array().unwrap();
261
262 for test in tests.iter() {
263 let encoded = utf8_percent_encode(
264 test["string"].as_str().unwrap(),
265 &QUERY_ENCODE_SET
266 ).to_string();
267
268 assert!(
269 encoded == test["fullyEncoded"]
270 || encoded == test["minimallyEncoded"],
271 "Failed test: {}. Actual: `{}`", test.to_string(), encoded
272 );
273 }
274 }
275}