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}