Skip to content

Commit 7feb542

Browse files
authored
Add more attributes (#5690)
Signed-off-by: netthier <[email protected]>
1 parent 11450ae commit 7feb542

File tree

7 files changed

+125
-19
lines changed

7 files changed

+125
-19
lines changed

object_store/src/attributes.rs

+32-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ use std::ops::Deref;
2323
#[non_exhaustive]
2424
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
2525
pub 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,
2638
/// Specifies the MIME type of the object
2739
///
2840
/// This takes precedence over any [ClientOptions](crate::ClientOptions) configuration
@@ -177,12 +189,15 @@ mod tests {
177189
#[test]
178190
fn test_attributes_basic() {
179191
let mut attributes = Attributes::from_iter([
192+
(Attribute::ContentDisposition, "inline"),
193+
(Attribute::ContentEncoding, "gzip"),
194+
(Attribute::ContentLanguage, "en-US"),
180195
(Attribute::ContentType, "test"),
181196
(Attribute::CacheControl, "control"),
182197
]);
183198

184199
assert!(!attributes.is_empty());
185-
assert_eq!(attributes.len(), 2);
200+
assert_eq!(attributes.len(), 5);
186201

187202
assert_eq!(
188203
attributes.get(&Attribute::ContentType),
@@ -195,17 +210,30 @@ mod tests {
195210
attributes.insert(Attribute::CacheControl, "v1".into()),
196211
Some(metav)
197212
);
198-
assert_eq!(attributes.len(), 2);
213+
assert_eq!(attributes.len(), 5);
199214

200215
assert_eq!(
201216
attributes.remove(&Attribute::CacheControl).unwrap(),
202217
"v1".into()
203218
);
204-
assert_eq!(attributes.len(), 1);
219+
assert_eq!(attributes.len(), 4);
205220

206221
let metav: AttributeValue = "v2".into();
207222
attributes.insert(Attribute::CacheControl, metav.clone());
208223
assert_eq!(attributes.get(&Attribute::CacheControl), Some(&metav));
209-
assert_eq!(attributes.len(), 2);
224+
assert_eq!(attributes.len(), 5);
225+
226+
assert_eq!(
227+
attributes.get(&Attribute::ContentDisposition),
228+
Some(&"inline".into())
229+
);
230+
assert_eq!(
231+
attributes.get(&Attribute::ContentEncoding),
232+
Some(&"gzip".into())
233+
);
234+
assert_eq!(
235+
attributes.get(&Attribute::ContentLanguage),
236+
Some(&"en-US".into())
237+
);
210238
}
211239
}

object_store/src/aws/client.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,17 @@ use async_trait::async_trait;
4242
use base64::prelude::BASE64_STANDARD;
4343
use base64::Engine;
4444
use bytes::{Buf, Bytes};
45-
use hyper::header::{CACHE_CONTROL, CONTENT_LENGTH};
45+
use hyper::header::{
46+
CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH,
47+
CONTENT_TYPE,
48+
};
4649
use hyper::http::HeaderName;
4750
use hyper::{http, HeaderMap};
4851
use itertools::Itertools;
4952
use md5::{Digest, Md5};
5053
use percent_encoding::{utf8_percent_encode, PercentEncode};
5154
use quick_xml::events::{self as xml_events};
52-
use reqwest::{header::CONTENT_TYPE, Client as ReqwestClient, Method, RequestBuilder, Response};
55+
use reqwest::{Client as ReqwestClient, Method, RequestBuilder, Response};
5356
use ring::digest;
5457
use ring::digest::Context;
5558
use serde::{Deserialize, Serialize};
@@ -322,6 +325,9 @@ impl<'a> Request<'a> {
322325
for (k, v) in &attributes {
323326
builder = match k {
324327
Attribute::CacheControl => builder.header(CACHE_CONTROL, v.as_ref()),
328+
Attribute::ContentDisposition => builder.header(CONTENT_DISPOSITION, v.as_ref()),
329+
Attribute::ContentEncoding => builder.header(CONTENT_ENCODING, v.as_ref()),
330+
Attribute::ContentLanguage => builder.header(CONTENT_LANGUAGE, v.as_ref()),
325331
Attribute::ContentType => {
326332
has_content_type = true;
327333
builder.header(CONTENT_TYPE, v.as_ref())

object_store/src/azure/client.rs

+9
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ use url::Url;
5050
const VERSION_HEADER: &str = "x-ms-version-id";
5151
static MS_CACHE_CONTROL: HeaderName = HeaderName::from_static("x-ms-blob-cache-control");
5252
static MS_CONTENT_TYPE: HeaderName = HeaderName::from_static("x-ms-blob-content-type");
53+
static MS_CONTENT_DISPOSITION: HeaderName =
54+
HeaderName::from_static("x-ms-blob-content-disposition");
55+
static MS_CONTENT_ENCODING: HeaderName = HeaderName::from_static("x-ms-blob-content-encoding");
56+
static MS_CONTENT_LANGUAGE: HeaderName = HeaderName::from_static("x-ms-blob-content-language");
5357

5458
static TAGS_HEADER: HeaderName = HeaderName::from_static("x-ms-tags");
5559

@@ -206,6 +210,11 @@ impl<'a> PutRequest<'a> {
206210
for (k, v) in &attributes {
207211
builder = match k {
208212
Attribute::CacheControl => builder.header(&MS_CACHE_CONTROL, v.as_ref()),
213+
Attribute::ContentDisposition => {
214+
builder.header(&MS_CONTENT_DISPOSITION, v.as_ref())
215+
}
216+
Attribute::ContentEncoding => builder.header(&MS_CONTENT_ENCODING, v.as_ref()),
217+
Attribute::ContentLanguage => builder.header(&MS_CONTENT_LANGUAGE, v.as_ref()),
209218
Attribute::ContentType => {
210219
has_content_type = true;
211220
builder.header(&MS_CONTENT_TYPE, v.as_ref())

object_store/src/client/get.rs

+53-9
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ use crate::path::Path;
2222
use crate::{Attribute, Attributes, GetOptions, GetRange, GetResult, GetResultPayload, Result};
2323
use async_trait::async_trait;
2424
use futures::{StreamExt, TryStreamExt};
25-
use hyper::header::{CACHE_CONTROL, CONTENT_RANGE, CONTENT_TYPE};
25+
use hyper::header::{
26+
CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_RANGE,
27+
CONTENT_TYPE,
28+
};
2629
use hyper::StatusCode;
2730
use reqwest::header::ToStrError;
2831
use reqwest::Response;
@@ -120,6 +123,15 @@ enum GetResultError {
120123
#[snafu(display("Cache-Control header contained non UTF-8 characters"))]
121124
InvalidCacheControl { source: ToStrError },
122125

126+
#[snafu(display("Content-Disposition header contained non UTF-8 characters"))]
127+
InvalidContentDisposition { source: ToStrError },
128+
129+
#[snafu(display("Content-Encoding header contained non UTF-8 characters"))]
130+
InvalidContentEncoding { source: ToStrError },
131+
132+
#[snafu(display("Content-Language header contained non UTF-8 characters"))]
133+
InvalidContentLanguage { source: ToStrError },
134+
123135
#[snafu(display("Content-Type header contained non UTF-8 characters"))]
124136
InvalidContentType { source: ToStrError },
125137

@@ -167,16 +179,48 @@ fn get_result<T: GetClient>(
167179
0..meta.size
168180
};
169181

170-
let mut attributes = Attributes::new();
171-
if let Some(x) = response.headers().get(CACHE_CONTROL) {
172-
let x = x.to_str().context(InvalidCacheControlSnafu)?;
173-
attributes.insert(Attribute::CacheControl, x.to_string().into());
174-
}
175-
if let Some(x) = response.headers().get(CONTENT_TYPE) {
176-
let x = x.to_str().context(InvalidContentTypeSnafu)?;
177-
attributes.insert(Attribute::ContentType, x.to_string().into());
182+
macro_rules! parse_attributes {
183+
($headers:expr, $(($header:expr, $attr:expr, $err:expr)),*) => {{
184+
let mut attributes = Attributes::new();
185+
$(
186+
if let Some(x) = $headers.get($header) {
187+
let x = x.to_str().context($err)?;
188+
attributes.insert($attr, x.to_string().into());
189+
}
190+
)*
191+
attributes
192+
}}
178193
}
179194

195+
let attributes = parse_attributes!(
196+
response.headers(),
197+
(
198+
CACHE_CONTROL,
199+
Attribute::CacheControl,
200+
InvalidCacheControlSnafu
201+
),
202+
(
203+
CONTENT_DISPOSITION,
204+
Attribute::ContentDisposition,
205+
InvalidContentDispositionSnafu
206+
),
207+
(
208+
CONTENT_ENCODING,
209+
Attribute::ContentEncoding,
210+
InvalidContentEncodingSnafu
211+
),
212+
(
213+
CONTENT_LANGUAGE,
214+
Attribute::ContentLanguage,
215+
InvalidContentLanguageSnafu
216+
),
217+
(
218+
CONTENT_TYPE,
219+
Attribute::ContentType,
220+
InvalidContentTypeSnafu
221+
)
222+
);
223+
180224
let stream = response
181225
.bytes_stream()
182226
.map_err(|source| crate::Error::Generic {

object_store/src/gcp/client.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ use async_trait::async_trait;
3636
use base64::prelude::BASE64_STANDARD;
3737
use base64::Engine;
3838
use bytes::Buf;
39-
use hyper::header::{CACHE_CONTROL, CONTENT_LENGTH, CONTENT_TYPE};
39+
use hyper::header::{
40+
CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH,
41+
CONTENT_TYPE,
42+
};
4043
use percent_encoding::{percent_encode, utf8_percent_encode, NON_ALPHANUMERIC};
4144
use reqwest::header::HeaderName;
4245
use reqwest::{Client, Method, RequestBuilder, Response, StatusCode};
@@ -195,6 +198,9 @@ impl<'a> Request<'a> {
195198
for (k, v) in &attributes {
196199
builder = match k {
197200
Attribute::CacheControl => builder.header(CACHE_CONTROL, v.as_ref()),
201+
Attribute::ContentDisposition => builder.header(CONTENT_DISPOSITION, v.as_ref()),
202+
Attribute::ContentEncoding => builder.header(CONTENT_ENCODING, v.as_ref()),
203+
Attribute::ContentLanguage => builder.header(CONTENT_LANGUAGE, v.as_ref()),
198204
Attribute::ContentType => {
199205
has_content_type = true;
200206
builder.header(CONTENT_TYPE, v.as_ref())

object_store/src/http/client.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ use crate::{Attribute, Attributes, ClientOptions, GetOptions, ObjectMeta, PutPay
2525
use async_trait::async_trait;
2626
use bytes::Buf;
2727
use chrono::{DateTime, Utc};
28-
use hyper::header::{CACHE_CONTROL, CONTENT_LENGTH};
28+
use hyper::header::{
29+
CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH,
30+
CONTENT_TYPE,
31+
};
2932
use percent_encoding::percent_decode_str;
30-
use reqwest::header::CONTENT_TYPE;
3133
use reqwest::{Method, Response, StatusCode};
3234
use serde::Deserialize;
3335
use snafu::{OptionExt, ResultExt, Snafu};
@@ -172,6 +174,11 @@ impl Client {
172174
for (k, v) in &attributes {
173175
builder = match k {
174176
Attribute::CacheControl => builder.header(CACHE_CONTROL, v.as_ref()),
177+
Attribute::ContentDisposition => {
178+
builder.header(CONTENT_DISPOSITION, v.as_ref())
179+
}
180+
Attribute::ContentEncoding => builder.header(CONTENT_ENCODING, v.as_ref()),
181+
Attribute::ContentLanguage => builder.header(CONTENT_LANGUAGE, v.as_ref()),
175182
Attribute::ContentType => {
176183
has_content_type = true;
177184
builder.header(CONTENT_TYPE, v.as_ref())

object_store/src/lib.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1744,8 +1744,14 @@ mod tests {
17441744
pub(crate) async fn put_get_attributes(integration: &dyn ObjectStore) {
17451745
// Test handling of attributes
17461746
let attributes = Attributes::from_iter([
1747-
(Attribute::ContentType, "text/html; charset=utf-8"),
17481747
(Attribute::CacheControl, "max-age=604800"),
1748+
(
1749+
Attribute::ContentDisposition,
1750+
r#"attachment; filename="test.html""#,
1751+
),
1752+
(Attribute::ContentEncoding, "gzip"),
1753+
(Attribute::ContentLanguage, "en-US"),
1754+
(Attribute::ContentType, "text/html; charset=utf-8"),
17491755
]);
17501756

17511757
let path = Path::from("attributes");

0 commit comments

Comments
 (0)