| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| use std::borrow::Cow; |
| use std::collections::HashMap; |
| use std::ops::Deref; |
| |
| /// Additional object attribute types |
| #[non_exhaustive] |
| #[derive(Debug, Hash, Eq, PartialEq, Clone)] |
| pub enum Attribute { |
| /// Specifies how the object should be handled by a browser |
| /// |
| /// See [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) |
| ContentDisposition, |
| /// Specifies the encodings applied to the object |
| /// |
| /// See [Content-Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding) |
| ContentEncoding, |
| /// Specifies the language of the object |
| /// |
| /// See [Content-Language](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Language) |
| ContentLanguage, |
| /// Specifies the MIME type of the object |
| /// |
| /// This takes precedence over any [ClientOptions](crate::ClientOptions) configuration |
| /// |
| /// See [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) |
| ContentType, |
| /// Overrides cache control policy of the object |
| /// |
| /// See [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) |
| CacheControl, |
| /// Specifies the storage class of the object. |
| /// |
| /// See [AWS](https://aws.amazon.com/s3/storage-classes/), |
| /// [GCP](https://cloud.google.com/storage/docs/storage-classes), and |
| /// [Azure](https://learn.microsoft.com/en-us/rest/api/storageservices/set-blob-tier). |
| /// `StorageClass` is used as the name for this attribute because 2 of the 3 storage providers |
| /// use that name |
| StorageClass, |
| /// Specifies a user-defined metadata field for the object |
| /// |
| /// The String is a user-defined key |
| Metadata(Cow<'static, str>), |
| } |
| |
| /// The value of an [`Attribute`] |
| /// |
| /// Provides efficient conversion from both static and owned strings |
| /// |
| /// ``` |
| /// # use object_store::AttributeValue; |
| /// // Can use static strings without needing an allocation |
| /// let value = AttributeValue::from("bar"); |
| /// // Can also store owned strings |
| /// let value = AttributeValue::from("foo".to_string()); |
| /// ``` |
| #[derive(Debug, Hash, Eq, PartialEq, Clone)] |
| pub struct AttributeValue(Cow<'static, str>); |
| |
| impl AsRef<str> for AttributeValue { |
| fn as_ref(&self) -> &str { |
| &self.0 |
| } |
| } |
| |
| impl From<&'static str> for AttributeValue { |
| fn from(value: &'static str) -> Self { |
| Self(Cow::Borrowed(value)) |
| } |
| } |
| |
| impl From<String> for AttributeValue { |
| fn from(value: String) -> Self { |
| Self(Cow::Owned(value)) |
| } |
| } |
| |
| impl Deref for AttributeValue { |
| type Target = str; |
| |
| fn deref(&self) -> &Self::Target { |
| self.0.as_ref() |
| } |
| } |
| |
| /// Additional attributes of an object |
| /// |
| /// Attributes can be specified in [PutOptions](crate::PutOptions) and retrieved |
| /// from APIs returning [GetResult](crate::GetResult). |
| /// |
| /// Unlike [`ObjectMeta`](crate::ObjectMeta), [`Attributes`] are not returned by |
| /// listing APIs |
| #[derive(Debug, Default, Eq, PartialEq, Clone)] |
| pub struct Attributes(HashMap<Attribute, AttributeValue>); |
| |
| impl Attributes { |
| /// Create a new empty [`Attributes`] |
| pub fn new() -> Self { |
| Self::default() |
| } |
| |
| /// Create a new [`Attributes`] with space for `capacity` [`Attribute`] |
| pub fn with_capacity(capacity: usize) -> Self { |
| Self(HashMap::with_capacity(capacity)) |
| } |
| |
| /// Insert a new [`Attribute`], [`AttributeValue`] pair |
| /// |
| /// Returns the previous value for `key` if any |
| pub fn insert(&mut self, key: Attribute, value: AttributeValue) -> Option<AttributeValue> { |
| self.0.insert(key, value) |
| } |
| |
| /// Returns the [`AttributeValue`] for `key` if any |
| pub fn get(&self, key: &Attribute) -> Option<&AttributeValue> { |
| self.0.get(key) |
| } |
| |
| /// Removes the [`AttributeValue`] for `key` if any |
| pub fn remove(&mut self, key: &Attribute) -> Option<AttributeValue> { |
| self.0.remove(key) |
| } |
| |
| /// Returns an [`AttributesIter`] over this |
| pub fn iter(&self) -> AttributesIter<'_> { |
| self.into_iter() |
| } |
| |
| /// Returns the number of [`Attribute`] in this collection |
| #[inline] |
| pub fn len(&self) -> usize { |
| self.0.len() |
| } |
| |
| /// Returns true if this contains no [`Attribute`] |
| #[inline] |
| pub fn is_empty(&self) -> bool { |
| self.0.is_empty() |
| } |
| } |
| |
| impl<K, V> FromIterator<(K, V)> for Attributes |
| where |
| K: Into<Attribute>, |
| V: Into<AttributeValue>, |
| { |
| fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self { |
| Self( |
| iter.into_iter() |
| .map(|(k, v)| (k.into(), v.into())) |
| .collect(), |
| ) |
| } |
| } |
| |
| impl<'a> IntoIterator for &'a Attributes { |
| type Item = (&'a Attribute, &'a AttributeValue); |
| type IntoIter = AttributesIter<'a>; |
| |
| fn into_iter(self) -> Self::IntoIter { |
| AttributesIter(self.0.iter()) |
| } |
| } |
| |
| /// Iterator over [`Attributes`] |
| #[derive(Debug)] |
| pub struct AttributesIter<'a>(std::collections::hash_map::Iter<'a, Attribute, AttributeValue>); |
| |
| impl<'a> Iterator for AttributesIter<'a> { |
| type Item = (&'a Attribute, &'a AttributeValue); |
| |
| fn next(&mut self) -> Option<Self::Item> { |
| self.0.next() |
| } |
| |
| fn size_hint(&self) -> (usize, Option<usize>) { |
| self.0.size_hint() |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn test_attributes_basic() { |
| let mut attributes = Attributes::from_iter([ |
| (Attribute::ContentDisposition, "inline"), |
| (Attribute::ContentEncoding, "gzip"), |
| (Attribute::ContentLanguage, "en-US"), |
| (Attribute::ContentType, "test"), |
| (Attribute::CacheControl, "control"), |
| (Attribute::Metadata("key1".into()), "value1"), |
| ]); |
| |
| assert!(!attributes.is_empty()); |
| assert_eq!(attributes.len(), 6); |
| |
| assert_eq!( |
| attributes.get(&Attribute::ContentType), |
| Some(&"test".into()) |
| ); |
| |
| let metav = "control".into(); |
| assert_eq!(attributes.get(&Attribute::CacheControl), Some(&metav)); |
| assert_eq!( |
| attributes.insert(Attribute::CacheControl, "v1".into()), |
| Some(metav) |
| ); |
| assert_eq!(attributes.len(), 6); |
| |
| assert_eq!( |
| attributes.remove(&Attribute::CacheControl).unwrap(), |
| "v1".into() |
| ); |
| assert_eq!(attributes.len(), 5); |
| |
| let metav: AttributeValue = "v2".into(); |
| attributes.insert(Attribute::CacheControl, metav.clone()); |
| assert_eq!(attributes.get(&Attribute::CacheControl), Some(&metav)); |
| assert_eq!(attributes.len(), 6); |
| |
| assert_eq!( |
| attributes.get(&Attribute::ContentDisposition), |
| Some(&"inline".into()) |
| ); |
| assert_eq!( |
| attributes.get(&Attribute::ContentEncoding), |
| Some(&"gzip".into()) |
| ); |
| assert_eq!( |
| attributes.get(&Attribute::ContentLanguage), |
| Some(&"en-US".into()) |
| ); |
| assert_eq!( |
| attributes.get(&Attribute::Metadata("key1".into())), |
| Some(&"value1".into()) |
| ); |
| } |
| } |