b2_client/
types.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2   License, v. 2.0. If a copy of the MPL was not distributed with this
3   file, You can obtain one at http://mozilla.org/MPL/2.0/.
4*/
5
6//! Collection of internal, general-purpose types used throughout the crate.
7
8use 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// This gives us nicer error handling when deserializing JSON responses.
22// TODO: If/when Try trait is stable, impl it here.
23#[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
53/// A Content-Disposition value.
54///
55/// The grammar is specified in RFC 6266, except parameter names that contain an
56/// '*' are not allowed.
57// TODO: Implement; parse/validate.
58pub 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
115/// Set of characters to percent-encode in GET requests and headers.
116pub(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        // These tests come from
144        // https://www.backblaze.com/b2/docs/string_encoding.html
145        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            /* TODO: Invalid string.
252            {
253                "fullyEncoded": "%F0%90%90%80",
254                "minimallyEncoded": "%F0%90%90%80",
255                "string": "\u{d801}\u{dc00}"
256            }
257            */
258        ]);
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}