Skip to main content

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 the storage class of the object.
49    ///
50    /// See [AWS](https://aws.amazon.com/s3/storage-classes/),
51    /// [GCP](https://cloud.google.com/storage/docs/storage-classes), and
52    /// [Azure](https://learn.microsoft.com/en-us/rest/api/storageservices/set-blob-tier).  
53    /// `StorageClass` is used as the name for this attribute because 2 of the 3 storage providers
54    /// use that name
55    StorageClass,
56    /// Specifies a user-defined metadata field for the object
57    ///
58    /// The String is a user-defined key
59    Metadata(Cow<'static, str>),
60}
61
62/// The value of an [`Attribute`]
63///
64/// Provides efficient conversion from both static and owned strings
65///
66/// ```
67/// # use object_store::AttributeValue;
68/// // Can use static strings without needing an allocation
69/// let value = AttributeValue::from("bar");
70/// // Can also store owned strings
71/// let value = AttributeValue::from("foo".to_string());
72/// ```
73#[derive(Debug, Hash, Eq, PartialEq, Clone)]
74pub struct AttributeValue(Cow<'static, str>);
75
76impl AsRef<str> for AttributeValue {
77    fn as_ref(&self) -> &str {
78        &self.0
79    }
80}
81
82impl From<&'static str> for AttributeValue {
83    fn from(value: &'static str) -> Self {
84        Self(Cow::Borrowed(value))
85    }
86}
87
88impl From<String> for AttributeValue {
89    fn from(value: String) -> Self {
90        Self(Cow::Owned(value))
91    }
92}
93
94impl Deref for AttributeValue {
95    type Target = str;
96
97    fn deref(&self) -> &Self::Target {
98        self.0.as_ref()
99    }
100}
101
102/// Additional attributes of an object
103///
104/// Attributes can be specified in [PutOptions](crate::PutOptions) and retrieved
105/// from APIs returning [GetResult](crate::GetResult).
106///
107/// Unlike [`ObjectMeta`](crate::ObjectMeta), [`Attributes`] are not returned by
108/// listing APIs
109#[derive(Debug, Default, Eq, PartialEq, Clone)]
110pub struct Attributes(HashMap<Attribute, AttributeValue>);
111
112impl Attributes {
113    /// Create a new empty [`Attributes`]
114    pub fn new() -> Self {
115        Self::default()
116    }
117
118    /// Create a new [`Attributes`] with space for `capacity` [`Attribute`]
119    pub fn with_capacity(capacity: usize) -> Self {
120        Self(HashMap::with_capacity(capacity))
121    }
122
123    /// Insert a new [`Attribute`], [`AttributeValue`] pair
124    ///
125    /// Returns the previous value for `key` if any
126    pub fn insert(&mut self, key: Attribute, value: AttributeValue) -> Option<AttributeValue> {
127        self.0.insert(key, value)
128    }
129
130    /// Returns the [`AttributeValue`] for `key` if any
131    pub fn get(&self, key: &Attribute) -> Option<&AttributeValue> {
132        self.0.get(key)
133    }
134
135    /// Removes the [`AttributeValue`] for `key` if any
136    pub fn remove(&mut self, key: &Attribute) -> Option<AttributeValue> {
137        self.0.remove(key)
138    }
139
140    /// Returns an [`AttributesIter`] over this
141    pub fn iter(&self) -> AttributesIter<'_> {
142        self.into_iter()
143    }
144
145    /// Returns the number of [`Attribute`] in this collection
146    #[inline]
147    pub fn len(&self) -> usize {
148        self.0.len()
149    }
150
151    /// Returns true if this contains no [`Attribute`]
152    #[inline]
153    pub fn is_empty(&self) -> bool {
154        self.0.is_empty()
155    }
156}
157
158impl<K, V> FromIterator<(K, V)> for Attributes
159where
160    K: Into<Attribute>,
161    V: Into<AttributeValue>,
162{
163    fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
164        Self(
165            iter.into_iter()
166                .map(|(k, v)| (k.into(), v.into()))
167                .collect(),
168        )
169    }
170}
171
172impl<'a> IntoIterator for &'a Attributes {
173    type Item = (&'a Attribute, &'a AttributeValue);
174    type IntoIter = AttributesIter<'a>;
175
176    fn into_iter(self) -> Self::IntoIter {
177        AttributesIter(self.0.iter())
178    }
179}
180
181/// Iterator over [`Attributes`]
182#[derive(Debug)]
183pub struct AttributesIter<'a>(std::collections::hash_map::Iter<'a, Attribute, AttributeValue>);
184
185impl<'a> Iterator for AttributesIter<'a> {
186    type Item = (&'a Attribute, &'a AttributeValue);
187
188    fn next(&mut self) -> Option<Self::Item> {
189        self.0.next()
190    }
191
192    fn size_hint(&self) -> (usize, Option<usize>) {
193        self.0.size_hint()
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    #[test]
202    fn test_attributes_basic() {
203        let mut attributes = Attributes::from_iter([
204            (Attribute::ContentDisposition, "inline"),
205            (Attribute::ContentEncoding, "gzip"),
206            (Attribute::ContentLanguage, "en-US"),
207            (Attribute::ContentType, "test"),
208            (Attribute::CacheControl, "control"),
209            (Attribute::Metadata("key1".into()), "value1"),
210        ]);
211
212        assert!(!attributes.is_empty());
213        assert_eq!(attributes.len(), 6);
214
215        assert_eq!(
216            attributes.get(&Attribute::ContentType),
217            Some(&"test".into())
218        );
219
220        let metav = "control".into();
221        assert_eq!(attributes.get(&Attribute::CacheControl), Some(&metav));
222        assert_eq!(
223            attributes.insert(Attribute::CacheControl, "v1".into()),
224            Some(metav)
225        );
226        assert_eq!(attributes.len(), 6);
227
228        assert_eq!(
229            attributes.remove(&Attribute::CacheControl).unwrap(),
230            "v1".into()
231        );
232        assert_eq!(attributes.len(), 5);
233
234        let metav: AttributeValue = "v2".into();
235        attributes.insert(Attribute::CacheControl, metav.clone());
236        assert_eq!(attributes.get(&Attribute::CacheControl), Some(&metav));
237        assert_eq!(attributes.len(), 6);
238
239        assert_eq!(
240            attributes.get(&Attribute::ContentDisposition),
241            Some(&"inline".into())
242        );
243        assert_eq!(
244            attributes.get(&Attribute::ContentEncoding),
245            Some(&"gzip".into())
246        );
247        assert_eq!(
248            attributes.get(&Attribute::ContentLanguage),
249            Some(&"en-US".into())
250        );
251        assert_eq!(
252            attributes.get(&Attribute::Metadata("key1".into())),
253            Some(&"value1".into())
254        );
255    }
256}