object_store/aws/
precondition.rs1use crate::config::Parse;
19
20use itertools::Itertools;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum S3CopyIfNotExists {
29 Header(String, String),
43 HeaderWithStatus(String, String, reqwest::StatusCode),
48 Multipart,
63}
64
65impl std::fmt::Display for S3CopyIfNotExists {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 Self::Header(k, v) => write!(f, "header: {k}: {v}"),
69 Self::HeaderWithStatus(k, v, code) => {
70 write!(f, "header-with-status: {k}: {v}: {}", code.as_u16())
71 }
72 Self::Multipart => f.write_str("multipart"),
73 }
74 }
75}
76
77impl S3CopyIfNotExists {
78 fn from_str(s: &str) -> Option<Self> {
79 if s.trim() == "multipart" {
80 return Some(Self::Multipart);
81 };
82
83 let (variant, value) = s.split_once(':')?;
84 match variant.trim() {
85 "header" => {
86 let (k, v) = value.split_once(':')?;
87 Some(Self::Header(k.trim().to_string(), v.trim().to_string()))
88 }
89 "header-with-status" => {
90 let (k, v, status) = value.split(':').collect_tuple()?;
91
92 let code = status.trim().parse().ok()?;
93
94 Some(Self::HeaderWithStatus(
95 k.trim().to_string(),
96 v.trim().to_string(),
97 code,
98 ))
99 }
100 _ => None,
101 }
102 }
103}
104
105impl Parse for S3CopyIfNotExists {
106 fn parse(v: &str) -> crate::Result<Self> {
107 Self::from_str(v).ok_or_else(|| crate::Error::Generic {
108 store: "Config",
109 source: format!("Failed to parse \"{v}\" as S3CopyIfNotExists").into(),
110 })
111 }
112}
113
114#[derive(Debug, Clone, Eq, PartialEq, Default)]
118#[allow(missing_copy_implementations)]
119#[non_exhaustive]
120pub enum S3ConditionalPut {
121 #[default]
128 ETagMatch,
129
130 Disabled,
132}
133
134impl std::fmt::Display for S3ConditionalPut {
135 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 match self {
137 Self::ETagMatch => write!(f, "etag"),
138 Self::Disabled => write!(f, "disabled"),
139 }
140 }
141}
142
143impl S3ConditionalPut {
144 fn from_str(s: &str) -> Option<Self> {
145 match s.trim() {
146 "etag" => Some(Self::ETagMatch),
147 "disabled" => Some(Self::Disabled),
148 _ => None,
149 }
150 }
151}
152
153impl Parse for S3ConditionalPut {
154 fn parse(v: &str) -> crate::Result<Self> {
155 Self::from_str(v).ok_or_else(|| crate::Error::Generic {
156 store: "Config",
157 source: format!("Failed to parse \"{v}\" as S3PutConditional").into(),
158 })
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::S3CopyIfNotExists;
165
166 #[test]
167 fn parse_s3_copy_if_not_exists_header() {
168 let input = "header: cf-copy-destination-if-none-match: *";
169 let expected = Some(S3CopyIfNotExists::Header(
170 "cf-copy-destination-if-none-match".to_owned(),
171 "*".to_owned(),
172 ));
173
174 assert_eq!(expected, S3CopyIfNotExists::from_str(input));
175 }
176
177 #[test]
178 fn parse_s3_copy_if_not_exists_header_with_status() {
179 let input = "header-with-status:key:value:403";
180 let expected = Some(S3CopyIfNotExists::HeaderWithStatus(
181 "key".to_owned(),
182 "value".to_owned(),
183 reqwest::StatusCode::FORBIDDEN,
184 ));
185
186 assert_eq!(expected, S3CopyIfNotExists::from_str(input));
187 }
188
189 #[test]
190 fn parse_s3_copy_if_not_exists_header_whitespace_invariant() {
191 let expected = Some(S3CopyIfNotExists::Header(
192 "cf-copy-destination-if-none-match".to_owned(),
193 "*".to_owned(),
194 ));
195
196 const INPUTS: &[&str] = &[
197 "header:cf-copy-destination-if-none-match:*",
198 "header: cf-copy-destination-if-none-match:*",
199 "header: cf-copy-destination-if-none-match: *",
200 "header : cf-copy-destination-if-none-match: *",
201 "header : cf-copy-destination-if-none-match : *",
202 "header : cf-copy-destination-if-none-match : * ",
203 ];
204
205 for input in INPUTS {
206 assert_eq!(expected, S3CopyIfNotExists::from_str(input));
207 }
208 }
209
210 #[test]
211 fn parse_s3_copy_if_not_exists_header_with_status_whitespace_invariant() {
212 let expected = Some(S3CopyIfNotExists::HeaderWithStatus(
213 "key".to_owned(),
214 "value".to_owned(),
215 reqwest::StatusCode::FORBIDDEN,
216 ));
217
218 const INPUTS: &[&str] = &[
219 "header-with-status:key:value:403",
220 "header-with-status: key:value:403",
221 "header-with-status: key: value:403",
222 "header-with-status: key: value: 403",
223 "header-with-status : key: value: 403",
224 "header-with-status : key : value: 403",
225 "header-with-status : key : value : 403",
226 "header-with-status : key : value : 403 ",
227 ];
228
229 for input in INPUTS {
230 assert_eq!(expected, S3CopyIfNotExists::from_str(input));
231 }
232 }
233}