google_cloud_storage/model_ext.rs
1// Copyright 2025 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Extends [model][crate::model] with types that improve type safety and/or
16//! ergonomics.
17
18use crate::error::KeyAes256Error;
19use base64::{Engine, prelude::BASE64_STANDARD};
20use sha2::{Digest, Sha256};
21
22/// ObjectHighlights contains select metadata from a [crate::model::Object].
23#[derive(Clone, Debug, Default, PartialEq)]
24#[non_exhaustive]
25pub struct ObjectHighlights {
26 /// The content generation of this object. Used for object versioning.
27 pub generation: i64,
28
29 /// The version of the metadata for this generation of this
30 /// object. Used for preconditions and for detecting changes in metadata. A
31 /// metageneration number is only meaningful in the context of a particular
32 /// generation of a particular object.
33 pub metageneration: i64,
34
35 /// Content-Length of the object data in bytes, matching [RFC 7230 §3.3.2].
36 ///
37 /// [rfc 7230 §3.3.2]: https://tools.ietf.org/html/rfc7230#section-3.3.2
38 pub size: i64,
39
40 /// Content-Encoding of the object data, matching [RFC 7231 §3.1.2.2].
41 ///
42 /// [rfc 7231 §3.1.2.2]: https://tools.ietf.org/html/rfc7231#section-3.1.2.2
43 pub content_encoding: String,
44
45 /// Hashes for the data part of this object. The checksums of the complete
46 /// object regardless of data range. If the object is read in full, the
47 /// client should compute one of these checksums over the read object and
48 /// compare it against the value provided here.
49 pub checksums: std::option::Option<crate::model::ObjectChecksums>,
50
51 /// Storage class of the object.
52 pub storage_class: String,
53
54 /// Content-Language of the object data, matching [RFC 7231 §3.1.3.2].
55 ///
56 /// [rfc 7231 §3.1.3.2]: https://tools.ietf.org/html/rfc7231#section-3.1.3.2
57 pub content_language: String,
58
59 /// Content-Type of the object data, matching [RFC 7231 §3.1.1.5]. If an
60 /// object is stored without a Content-Type, it is served as
61 /// `application/octet-stream`.
62 ///
63 /// [rfc 7231 §3.1.1.5]: https://tools.ietf.org/html/rfc7231#section-3.1.1.5
64 pub content_type: String,
65
66 /// Content-Disposition of the object data, matching [RFC 6266].
67 ///
68 /// [rfc 6266]: https://tools.ietf.org/html/rfc6266
69 pub content_disposition: String,
70
71 /// The etag of the object.
72 pub etag: String,
73}
74
75#[derive(Debug, Clone)]
76/// KeyAes256 represents an AES-256 encryption key used with the
77/// Customer-Supplied Encryption Keys (CSEK) feature.
78///
79/// This key must be exactly 32 bytes in length and should be provided in its
80/// raw (unencoded) byte format.
81///
82/// # Examples
83///
84/// Creating a `KeyAes256` instance from a valid byte slice:
85/// ```
86/// # use google_cloud_storage::{model_ext::KeyAes256, error::KeyAes256Error};
87/// let raw_key_bytes: [u8; 32] = [0x42; 32]; // Example 32-byte key
88/// let key_aes_256 = KeyAes256::new(&raw_key_bytes)?;
89/// # Ok::<(), KeyAes256Error>(())
90/// ```
91///
92/// Handling an error for an invalid key length:
93/// ```
94/// # use google_cloud_storage::{model_ext::KeyAes256, error::KeyAes256Error};
95/// let invalid_key_bytes: &[u8] = b"too_short_key"; // Less than 32 bytes
96/// let result = KeyAes256::new(invalid_key_bytes);
97///
98/// assert!(matches!(result, Err(KeyAes256Error::InvalidLength)));
99/// ```
100pub struct KeyAes256 {
101 key: [u8; 32],
102}
103
104impl KeyAes256 {
105 /// Attempts to create a new [KeyAes256].
106 ///
107 /// This conversion will succeed only if the input slice is exactly 32 bytes long.
108 ///
109 /// # Example
110 /// ```
111 /// # use google_cloud_storage::{model_ext::KeyAes256, error::KeyAes256Error};
112 /// let raw_key_bytes: [u8; 32] = [0x42; 32]; // Example 32-byte key
113 /// let key_aes_256 = KeyAes256::new(&raw_key_bytes)?;
114 /// # Ok::<(), KeyAes256Error>(())
115 /// ```
116 pub fn new(key: &[u8]) -> std::result::Result<Self, KeyAes256Error> {
117 match key.len() {
118 32 => Ok(Self {
119 key: key[..32].try_into().unwrap(),
120 }),
121 _ => Err(KeyAes256Error::InvalidLength),
122 }
123 }
124}
125
126impl std::convert::From<KeyAes256> for crate::model::CommonObjectRequestParams {
127 fn from(value: KeyAes256) -> Self {
128 // sha2::digest::generic_array::GenericArray::<T, N>::as_slice is deprecated.
129 // Our dependencies need to update to generic_array 1.x.
130 // See https://github.com/RustCrypto/traits/issues/2036 for more info.
131 #[allow(deprecated)]
132 crate::model::CommonObjectRequestParams::new()
133 .set_encryption_algorithm("AES256")
134 .set_encryption_key_bytes(value.key.to_vec())
135 .set_encryption_key_sha256_bytes(Sha256::digest(value.key).as_slice().to_owned())
136 }
137}
138
139impl std::fmt::Display for KeyAes256 {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 write!(f, "{}", BASE64_STANDARD.encode(self.key))
142 }
143}
144
145/// Define read ranges for use with [ReadObject].
146///
147/// # Example: read the first 100 bytes of an object
148/// ```
149/// # use google_cloud_storage::client::Storage;
150/// # use google_cloud_storage::model_ext::ReadRange;
151/// # async fn sample(client: &Storage) -> anyhow::Result<()> {
152/// let response = client
153/// .read_object("projects/_/buckets/my-bucket", "my-object")
154/// .set_read_range(ReadRange::head(100))
155/// .send()
156/// .await?;
157/// println!("response details={response:?}");
158/// # Ok(()) }
159/// ```
160///
161/// Cloud Storage supports reading a portion of an object. These portions can
162/// be specified as offsets from the beginning of the object, offsets from the
163/// end of the object, or as ranges with a starting and ending bytes. This type
164/// defines a type-safe interface to represent only valid ranges.
165///
166/// [ReadObject]: crate::builder::storage::ReadObject
167#[derive(Clone, Debug)]
168pub struct ReadRange(pub(crate) RequestedRange);
169
170impl ReadRange {
171 /// Returns a range representing all the bytes in the object.
172 ///
173 /// # Example
174 /// ```
175 /// # use google_cloud_storage::client::Storage;
176 /// # use google_cloud_storage::model_ext::ReadRange;
177 /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
178 /// let response = client
179 /// .read_object("projects/_/buckets/my-bucket", "my-object")
180 /// .set_read_range(ReadRange::all())
181 /// .send()
182 /// .await?;
183 /// println!("response details={response:?}");
184 /// # Ok(()) }
185 pub fn all() -> Self {
186 Self::offset(0)
187 }
188
189 /// Returns a range representing the bytes starting at `offset`.
190 ///
191 /// # Example
192 /// ```
193 /// # use google_cloud_storage::client::Storage;
194 /// # use google_cloud_storage::model_ext::ReadRange;
195 /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
196 /// let response = client
197 /// .read_object("projects/_/buckets/my-bucket", "my-object")
198 /// .set_read_range(ReadRange::offset(1_000_000))
199 /// .send()
200 /// .await?;
201 /// println!("response details={response:?}");
202 /// # Ok(()) }
203 pub fn offset(offset: u64) -> Self {
204 Self(RequestedRange::Offset(offset))
205 }
206
207 /// Returns a range representing the last `count` bytes of the object.
208 ///
209 /// # Example
210 /// ```
211 /// # use google_cloud_storage::client::Storage;
212 /// # use google_cloud_storage::model_ext::ReadRange;
213 /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
214 /// let response = client
215 /// .read_object("projects/_/buckets/my-bucket", "my-object")
216 /// .set_read_range(ReadRange::tail(100))
217 /// .send()
218 /// .await?;
219 /// println!("response details={response:?}");
220 /// # Ok(()) }
221 pub fn tail(count: u64) -> Self {
222 Self(RequestedRange::Tail(count))
223 }
224
225 /// Returns a range representing the first `count` bytes of the object.
226 ///
227 /// # Example
228 /// ```
229 /// # use google_cloud_storage::client::Storage;
230 /// # use google_cloud_storage::model_ext::ReadRange;
231 /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
232 /// let response = client
233 /// .read_object("projects/_/buckets/my-bucket", "my-object")
234 /// .set_read_range(ReadRange::head(100))
235 /// .send()
236 /// .await?;
237 /// println!("response details={response:?}");
238 /// # Ok(()) }
239 pub fn head(count: u64) -> Self {
240 Self::segment(0, count)
241 }
242
243 /// Returns a range representing the `count` bytes starting at `offset`.
244 ///
245 /// # Example
246 /// ```
247 /// # use google_cloud_storage::client::Storage;
248 /// # use google_cloud_storage::model_ext::ReadRange;
249 /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
250 /// let response = client
251 /// .read_object("projects/_/buckets/my-bucket", "my-object")
252 /// .set_read_range(ReadRange::segment(1_000_000, 1_000))
253 /// .send()
254 /// .await?;
255 /// println!("response details={response:?}");
256 /// # Ok(()) }
257 pub fn segment(offset: u64, count: u64) -> Self {
258 Self(RequestedRange::Segment {
259 offset,
260 limit: count,
261 })
262 }
263}
264
265impl crate::model::ReadObjectRequest {
266 pub(crate) fn with_range(&mut self, range: ReadRange) {
267 // The limit for GCS objects is (currently) 5TiB, and the gRPC protocol
268 // uses i64 for the offset and limit. Clamping the values to the
269 // `[0, i64::MAX]`` range is safe, in that it does not lose any
270 // functionality.
271 match range.0 {
272 RequestedRange::Offset(o) => {
273 self.read_offset = o.clamp(0, i64::MAX as u64) as i64;
274 }
275 RequestedRange::Tail(t) => {
276 // Yes, -i64::MAX is different from i64::MIN, but both are
277 // safe in this context.
278 self.read_offset = -(t.clamp(0, i64::MAX as u64) as i64);
279 }
280 RequestedRange::Segment { offset, limit } => {
281 self.read_offset = offset.clamp(0, i64::MAX as u64) as i64;
282 self.read_limit = limit.clamp(0, i64::MAX as u64) as i64;
283 }
284 }
285 }
286}
287
288#[derive(Clone, Copy, Debug, PartialEq)]
289pub(crate) enum RequestedRange {
290 Offset(u64),
291 Tail(u64),
292 Segment { offset: u64, limit: u64 },
293}
294
295/// Represents the parameters of a [WriteObject] request.
296///
297/// This type is only used in mocks of the `Storage` client.
298///
299/// [WriteObject]: crate::builder::storage::WriteObject
300#[derive(Debug, PartialEq)]
301#[non_exhaustive]
302#[allow(dead_code)]
303pub struct WriteObjectRequest {
304 pub spec: crate::model::WriteObjectSpec,
305 pub params: Option<crate::model::CommonObjectRequestParams>,
306}
307
308#[cfg(test)]
309pub(crate) mod tests {
310 use super::*;
311 use crate::model::ReadObjectRequest;
312 use base64::{Engine, prelude::BASE64_STANDARD};
313 use test_case::test_case;
314
315 type Result = anyhow::Result<()>;
316
317 /// This is used by the request builder tests.
318 pub(crate) fn create_key_helper() -> (Vec<u8>, String, Vec<u8>, String) {
319 // Make a 32-byte key.
320 let key = vec![b'a'; 32];
321 let key_base64 = BASE64_STANDARD.encode(key.clone());
322
323 let key_sha256 = Sha256::digest(key.clone());
324 let key_sha256_base64 = BASE64_STANDARD.encode(key_sha256);
325 (key, key_base64, key_sha256.to_vec(), key_sha256_base64)
326 }
327
328 #[test]
329 // This tests converting to KeyAes256 from some different types
330 // that can get converted to &[u8].
331 fn test_key_aes_256() -> Result {
332 let v_slice: &[u8] = &[b'c'; 32];
333 KeyAes256::new(v_slice)?;
334
335 let v_vec: Vec<u8> = vec![b'a'; 32];
336 KeyAes256::new(&v_vec)?;
337
338 let v_array: [u8; 32] = [b'a'; 32];
339 KeyAes256::new(&v_array)?;
340
341 let v_bytes: bytes::Bytes = bytes::Bytes::copy_from_slice(&v_array);
342 KeyAes256::new(&v_bytes)?;
343
344 Ok(())
345 }
346
347 #[test_case(&[b'a'; 0]; "no bytes")]
348 #[test_case(&[b'a'; 1]; "not enough bytes")]
349 #[test_case(&[b'a'; 33]; "too many bytes")]
350 fn test_key_aes_256_err(input: &[u8]) {
351 KeyAes256::new(input).unwrap_err();
352 }
353
354 #[test]
355 fn test_key_aes_256_to_control_model_object() -> Result {
356 let (key, _, key_sha256, _) = create_key_helper();
357 let key_aes_256 = KeyAes256::new(&key)?;
358 let params = crate::model::CommonObjectRequestParams::from(key_aes_256);
359 assert_eq!(params.encryption_algorithm, "AES256");
360 assert_eq!(params.encryption_key_bytes, key);
361 assert_eq!(params.encryption_key_sha256_bytes, key_sha256);
362 Ok(())
363 }
364
365 #[test_case(100, 100)]
366 #[test_case(u64::MAX, i64::MAX)]
367 #[test_case(0, 0)]
368 fn apply_offset(input: u64, want: i64) {
369 let range = ReadRange::offset(input);
370 let mut request = ReadObjectRequest::new();
371 request.with_range(range);
372 assert_eq!(request.read_offset, want);
373 assert_eq!(request.read_limit, 0);
374 }
375
376 #[test_case(100, 100)]
377 #[test_case(u64::MAX, i64::MAX)]
378 #[test_case(0, 0)]
379 fn apply_head(input: u64, want: i64) {
380 let range = ReadRange::head(input);
381 let mut request = ReadObjectRequest::new();
382 request.with_range(range);
383 assert_eq!(request.read_offset, 0);
384 assert_eq!(request.read_limit, want);
385 }
386
387 #[test_case(100, -100)]
388 #[test_case(u64::MAX, -i64::MAX)]
389 #[test_case(0, 0)]
390 fn apply_tail(input: u64, want: i64) {
391 let range = ReadRange::tail(input);
392 let mut request = ReadObjectRequest::new();
393 request.with_range(range);
394 assert_eq!(request.read_offset, want);
395 assert_eq!(request.read_limit, 0);
396 }
397
398 #[test_case(100, 100)]
399 #[test_case(u64::MAX, i64::MAX)]
400 #[test_case(0, 0)]
401 fn apply_segment_offset(input: u64, want: i64) {
402 let range = ReadRange::segment(input, 2000);
403 let mut request = ReadObjectRequest::new();
404 request.with_range(range);
405 assert_eq!(request.read_offset, want);
406 assert_eq!(request.read_limit, 2000);
407 }
408
409 #[test_case(100, 100)]
410 #[test_case(u64::MAX, i64::MAX)]
411 #[test_case(0, 0)]
412 fn apply_segment_limit(input: u64, want: i64) {
413 let range = ReadRange::segment(1000, input);
414 let mut request = ReadObjectRequest::new();
415 request.with_range(range);
416 assert_eq!(request.read_offset, 1000);
417 assert_eq!(request.read_limit, want);
418 }
419
420 #[test]
421 fn test_key_aes_256_display() -> Result {
422 let (key, key_base64, _, _) = create_key_helper();
423 let key_aes_256 = KeyAes256::new(&key)?;
424 assert_eq!(key_aes_256.to_string(), key_base64);
425 Ok(())
426 }
427}