object_store/client/
header.rs1use crate::path::Path;
21use crate::ObjectMeta;
22use chrono::{DateTime, TimeZone, Utc};
23use hyper::header::{CONTENT_LENGTH, ETAG, LAST_MODIFIED};
24use hyper::HeaderMap;
25use snafu::{OptionExt, ResultExt, Snafu};
26
27#[derive(Debug, Copy, Clone)]
28pub struct HeaderConfig {
30 pub etag_required: bool,
34
35 pub last_modified_required: bool,
39
40 pub version_header: Option<&'static str>,
42
43 pub user_defined_metadata_prefix: Option<&'static str>,
45}
46
47#[derive(Debug, Snafu)]
48pub enum Error {
49 #[snafu(display("ETag Header missing from response"))]
50 MissingEtag,
51
52 #[snafu(display("Received header containing non-ASCII data"))]
53 BadHeader { source: reqwest::header::ToStrError },
54
55 #[snafu(display("Last-Modified Header missing from response"))]
56 MissingLastModified,
57
58 #[snafu(display("Content-Length Header missing from response"))]
59 MissingContentLength,
60
61 #[snafu(display("Invalid last modified '{}': {}", last_modified, source))]
62 InvalidLastModified {
63 last_modified: String,
64 source: chrono::ParseError,
65 },
66
67 #[snafu(display("Invalid content length '{}': {}", content_length, source))]
68 InvalidContentLength {
69 content_length: String,
70 source: std::num::ParseIntError,
71 },
72}
73
74#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
76pub fn get_put_result(headers: &HeaderMap, version: &str) -> Result<crate::PutResult, Error> {
77 let e_tag = Some(get_etag(headers)?);
78 let version = get_version(headers, version)?;
79 Ok(crate::PutResult { e_tag, version })
80}
81
82#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
84pub fn get_version(headers: &HeaderMap, version: &str) -> Result<Option<String>, Error> {
85 Ok(match headers.get(version) {
86 Some(x) => Some(x.to_str().context(BadHeaderSnafu)?.to_string()),
87 None => None,
88 })
89}
90
91pub fn get_etag(headers: &HeaderMap) -> Result<String, Error> {
93 let e_tag = headers.get(ETAG).ok_or(Error::MissingEtag)?;
94 Ok(e_tag.to_str().context(BadHeaderSnafu)?.to_string())
95}
96
97pub fn header_meta(
99 location: &Path,
100 headers: &HeaderMap,
101 cfg: HeaderConfig,
102) -> Result<ObjectMeta, Error> {
103 let last_modified = match headers.get(LAST_MODIFIED) {
104 Some(last_modified) => {
105 let last_modified = last_modified.to_str().context(BadHeaderSnafu)?;
106 DateTime::parse_from_rfc2822(last_modified)
107 .context(InvalidLastModifiedSnafu { last_modified })?
108 .with_timezone(&Utc)
109 }
110 None if cfg.last_modified_required => return Err(Error::MissingLastModified),
111 None => Utc.timestamp_nanos(0),
112 };
113
114 let e_tag = match get_etag(headers) {
115 Ok(e_tag) => Some(e_tag),
116 Err(Error::MissingEtag) if !cfg.etag_required => None,
117 Err(e) => return Err(e),
118 };
119
120 let content_length = headers
121 .get(CONTENT_LENGTH)
122 .context(MissingContentLengthSnafu)?;
123
124 let content_length = content_length.to_str().context(BadHeaderSnafu)?;
125 let size = content_length
126 .parse()
127 .context(InvalidContentLengthSnafu { content_length })?;
128
129 let version = match cfg.version_header.and_then(|h| headers.get(h)) {
130 Some(v) => Some(v.to_str().context(BadHeaderSnafu)?.to_string()),
131 None => None,
132 };
133
134 Ok(ObjectMeta {
135 location: location.clone(),
136 last_modified,
137 version,
138 size,
139 e_tag,
140 })
141}