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
use azure_core::{
base64,
error::{Error, ErrorKind},
headers::{self, Header, Headers, PROPERTIES},
};
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Properties(BTreeMap<Cow<'static, str>, Cow<'static, str>>);
impl Default for Properties {
fn default() -> Self {
Self::new()
}
}
impl From<BTreeMap<Cow<'static, str>, Cow<'static, str>>> for Properties {
fn from(value: BTreeMap<Cow<'static, str>, Cow<'static, str>>) -> Self {
Self(value)
}
}
impl Properties {
pub fn new() -> Self {
Self(BTreeMap::new())
}
pub fn insert<K: Into<Cow<'static, str>>, V: Into<Cow<'static, str>>>(
&mut self,
k: K,
v: V,
) -> Option<Cow<'static, str>> {
self.0.insert(k.into(), v.into())
}
pub fn get(&self, key: &str) -> std::option::Option<&Cow<'_, str>> {
self.0.get(key)
}
}
impl Header for Properties {
fn name(&self) -> headers::HeaderName {
PROPERTIES
}
fn value(&self) -> headers::HeaderValue {
// the header is a comma separated list of key=base64(value) see
// [https://docs.microsoft.com/rest/api/storageservices/datalakestoragegen2/filesystem/create#request-headers](https://docs.microsoft.com/rest/api/storageservices/datalakestoragegen2/filesystem/create#request-headers)
self.0
.iter()
.map(|(k, v)| format!("{}={}", k.as_ref(), base64::encode(v.as_ref())))
.collect::<Vec<_>>()
.join(",")
.into()
}
}
impl TryFrom<&Headers> for Properties {
type Error = crate::Error;
fn try_from(headers: &Headers) -> Result<Self, Self::Error> {
let header_value = headers.get_str(&PROPERTIES)?;
Properties::try_from(header_value)
}
}
impl TryFrom<&str> for Properties {
type Error = crate::Error;
fn try_from(header_value: &str) -> Result<Self, Self::Error> {
let mut properties = Self::new();
if header_value.is_empty() {
return Ok(properties);
}
// this is probably too complicated. Should we split
// it in more manageable code blocks?
// The logic is this:
// 1. Look for the header. If not found return error
// 2. Split the header value by comma
// 3. For each comma separated value:
// 4. Split by equals. If we do not have at least 2 entries, return error.
// 5. For each pair:
// 6. Base64 decode the second entry (value). If error, return error.
// 7. Insert the key value pair in the returned struct.
header_value
.split(',') // The list is a CSV so we split by comma
.map(|key_value_pair| {
let mut key_and_value = key_value_pair.split('='); // Each entry is key and value separated by =
// we must have a key and a value (so two entries)
let key = key_and_value
.next()
.ok_or_else(|| Error::message(ErrorKind::Other, "missing key"))?;
let value = key_and_value
.next()
.ok_or_else(|| Error::message(ErrorKind::Other, "missing value"))?;
// we do not check if there are more entries. We just ignore them.
Ok((key, value))
})
.collect::<crate::Result<Vec<(&str, &str)>>>()? // if we have an error, return error
.into_iter()
.map(|(key, value)| {
// the value is base64 encoded se we decode it
let value = String::from_utf8(base64::decode(value)?)?;
Ok((key, value))
})
.collect::<crate::Result<Vec<(&str, String)>>>()? // if we have an error, return error
.into_iter()
.for_each(|(key, value)| {
properties.insert(key.to_owned(), value); // finally store the key and value into the properties
});
Ok(properties)
}
}
impl FromStr for Properties {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
TryFrom::try_from(s)
}
}