object_store/
attributes.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::borrow::Cow;
19use std::collections::HashMap;
20use std::ops::Deref;
21
22/// Additional object attribute types
23#[non_exhaustive]
24#[derive(Debug, Hash, Eq, PartialEq, Clone)]
25pub enum Attribute {
26    /// Specifies how the object should be handled by a browser
27    ///
28    /// See [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition)
29    ContentDisposition,
30    /// Specifies the encodings applied to the object
31    ///
32    /// See [Content-Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding)
33    ContentEncoding,
34    /// Specifies the language of the object
35    ///
36    /// See [Content-Language](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Language)
37    ContentLanguage,
38    /// Specifies the MIME type of the object
39    ///
40    /// This takes precedence over any [ClientOptions](crate::ClientOptions) configuration
41    ///
42    /// See [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type)
43    ContentType,
44    /// Overrides cache control policy of the object
45    ///
46    /// See [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
47    CacheControl,
48    /// Specifies a user-defined metadata field for the object
49    ///
50    /// The String is a user-defined key
51    Metadata(Cow<'static, str>),
52}
53
54/// The value of an [`Attribute`]
55///
56/// Provides efficient conversion from both static and owned strings
57///
58/// ```
59/// # use object_store::AttributeValue;
60/// // Can use static strings without needing an allocation
61/// let value = AttributeValue::from("bar");
62/// // Can also store owned strings
63/// let value = AttributeValue::from("foo".to_string());
64/// ```
65#[derive(Debug, Hash, Eq, PartialEq, Clone)]
66pub struct AttributeValue(Cow<'static, str>);
67
68impl AsRef<str> for AttributeValue {
69    fn as_ref(&self) -> &str {
70        &self.0
71    }
72}
73
74impl From<&'static str> for AttributeValue {
75    fn from(value: &'static str) -> Self {
76        Self(Cow::Borrowed(value))
77    }
78}
79
80impl From<String> for AttributeValue {
81    fn from(value: String) -> Self {
82        Self(Cow::Owned(value))
83    }
84}
85
86impl Deref for AttributeValue {
87    type Target = str;
88
89    fn deref(&self) -> &Self::Target {
90        self.0.as_ref()
91    }
92}
93
94/// Additional attributes of an object
95///
96/// Attributes can be specified in [PutOptions](crate::PutOptions) and retrieved
97/// from APIs returning [GetResult](crate::GetResult).
98///
99/// Unlike [`ObjectMeta`](crate::ObjectMeta), [`Attributes`] are not returned by
100/// listing APIs
101#[derive(Debug, Default, Eq, PartialEq, Clone)]
102pub struct Attributes(HashMap<Attribute, AttributeValue>);
103
104impl Attributes {
105    /// Create a new empty [`Attributes`]
106    pub fn new() -> Self {
107        Self::default()
108    }
109
110    /// Create a new [`Attributes`] with space for `capacity` [`Attribute`]
111    pub fn with_capacity(capacity: usize) -> Self {
112        Self(HashMap::with_capacity(capacity))
113    }
114
115    /// Insert a new [`Attribute`], [`AttributeValue`] pair
116    ///
117    /// Returns the previous value for `key` if any
118    pub fn insert(&mut self, key: Attribute, value: AttributeValue) -> Option<AttributeValue> {
119        self.0.insert(key, value)
120    }
121
122    /// Returns the [`AttributeValue`] for `key` if any
123    pub fn get(&self, key: &Attribute) -> Option<&AttributeValue> {
124        self.0.get(key)
125    }
126
127    /// Removes the [`AttributeValue`] for `key` if any
128    pub fn remove(&mut self, key: &Attribute) -> Option<AttributeValue> {
129        self.0.remove(key)
130    }
131
132    /// Returns an [`AttributesIter`] over this
133    pub fn iter(&self) -> AttributesIter<'_> {
134        self.into_iter()
135    }
136
137    /// Returns the number of [`Attribute`] in this collection
138    #[inline]
139    pub fn len(&self) -> usize {
140        self.0.len()
141    }
142
143    /// Returns true if this contains no [`Attribute`]
144    #[inline]
145    pub fn is_empty(&self) -> bool {
146        self.0.is_empty()
147    }
148}
149
150impl<K, V> FromIterator<(K, V)> for Attributes
151where
152    K: Into<Attribute>,
153    V: Into<AttributeValue>,
154{
155    fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
156        Self(
157            iter.into_iter()
158                .map(|(k, v)| (k.into(), v.into()))
159                .collect(),
160        )
161    }
162}
163
164impl<'a> IntoIterator for &'a Attributes {
165    type Item = (&'a Attribute, &'a AttributeValue);
166    type IntoIter = AttributesIter<'a>;
167
168    fn into_iter(self) -> Self::IntoIter {
169        AttributesIter(self.0.iter())
170    }
171}
172
173/// Iterator over [`Attributes`]
174#[derive(Debug)]
175pub struct AttributesIter<'a>(std::collections::hash_map::Iter<'a, Attribute, AttributeValue>);
176
177impl<'a> Iterator for AttributesIter<'a> {
178    type Item = (&'a Attribute, &'a AttributeValue);
179
180    fn next(&mut self) -> Option<Self::Item> {
181        self.0.next()
182    }
183
184    fn size_hint(&self) -> (usize, Option<usize>) {
185        self.0.size_hint()
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn test_attributes_basic() {
195        let mut attributes = Attributes::from_iter([
196            (Attribute::ContentDisposition, "inline"),
197            (Attribute::ContentEncoding, "gzip"),
198            (Attribute::ContentLanguage, "en-US"),
199            (Attribute::ContentType, "test"),
200            (Attribute::CacheControl, "control"),
201            (Attribute::Metadata("key1".into()), "value1"),
202        ]);
203
204        assert!(!attributes.is_empty());
205        assert_eq!(attributes.len(), 6);
206
207        assert_eq!(
208            attributes.get(&Attribute::ContentType),
209            Some(&"test".into())
210        );
211
212        let metav = "control".into();
213        assert_eq!(attributes.get(&Attribute::CacheControl), Some(&metav));
214        assert_eq!(
215            attributes.insert(Attribute::CacheControl, "v1".into()),
216            Some(metav)
217        );
218        assert_eq!(attributes.len(), 6);
219
220        assert_eq!(
221            attributes.remove(&Attribute::CacheControl).unwrap(),
222            "v1".into()
223        );
224        assert_eq!(attributes.len(), 5);
225
226        let metav: AttributeValue = "v2".into();
227        attributes.insert(Attribute::CacheControl, metav.clone());
228        assert_eq!(attributes.get(&Attribute::CacheControl), Some(&metav));
229        assert_eq!(attributes.len(), 6);
230
231        assert_eq!(
232            attributes.get(&Attribute::ContentDisposition),
233            Some(&"inline".into())
234        );
235        assert_eq!(
236            attributes.get(&Attribute::ContentEncoding),
237            Some(&"gzip".into())
238        );
239        assert_eq!(
240            attributes.get(&Attribute::ContentLanguage),
241            Some(&"en-US".into())
242        );
243        assert_eq!(
244            attributes.get(&Attribute::Metadata("key1".into())),
245            Some(&"value1".into())
246        );
247    }
248}