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        crate::model::CommonObjectRequestParams::new()
129            .set_encryption_algorithm("AES256")
130            .set_encryption_key_bytes(value.key.to_vec())
131            .set_encryption_key_sha256_bytes(Sha256::digest(value.key).as_slice().to_owned())
132    }
133}
134
135impl std::fmt::Display for KeyAes256 {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        write!(f, "{}", BASE64_STANDARD.encode(self.key))
138    }
139}
140
141/// Define read ranges for use with [ReadObject].
142///
143/// # Example: read the first 100 bytes of an object
144/// ```
145/// # use google_cloud_storage::client::Storage;
146/// # use google_cloud_storage::model_ext::ReadRange;
147/// # async fn sample(client: &Storage) -> anyhow::Result<()> {
148/// let response = client
149///     .read_object("projects/_/buckets/my-bucket", "my-object")
150///     .set_read_range(ReadRange::head(100))
151///     .send()
152///     .await?;
153/// println!("response details={response:?}");
154/// # Ok(()) }
155/// ```
156///
157/// Cloud Storage supports reading a portion of an object. These portions can
158/// be specified as offsets from the beginning of the object, offsets from the
159/// end of the object, or as ranges with a starting and ending bytes. This type
160/// defines a type-safe interface to represent only valid ranges.
161///
162/// [ReadObject]: crate::builder::storage::ReadObject
163#[derive(Clone, Debug)]
164pub struct ReadRange(Range);
165
166impl ReadRange {
167    /// Returns a range representing all the bytes in the object.
168    ///
169    /// # Example
170    /// ```
171    /// # use google_cloud_storage::client::Storage;
172    /// # use google_cloud_storage::model_ext::ReadRange;
173    /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
174    /// let response = client
175    ///     .read_object("projects/_/buckets/my-bucket", "my-object")
176    ///     .set_read_range(ReadRange::all())
177    ///     .send()
178    ///     .await?;
179    /// println!("response details={response:?}");
180    /// # Ok(()) }
181    pub fn all() -> Self {
182        Self(Range::All)
183    }
184
185    /// Returns a range representing the bytes starting at `offset`.
186    ///
187    /// # Example
188    /// ```
189    /// # use google_cloud_storage::client::Storage;
190    /// # use google_cloud_storage::model_ext::ReadRange;
191    /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
192    /// let response = client
193    ///     .read_object("projects/_/buckets/my-bucket", "my-object")
194    ///     .set_read_range(ReadRange::offset(1_000_000))
195    ///     .send()
196    ///     .await?;
197    /// println!("response details={response:?}");
198    /// # Ok(()) }
199    pub fn offset(offset: u64) -> Self {
200        Self(Range::Offset(offset))
201    }
202
203    /// Returns a range representing the last `count` bytes of the object.
204    ///
205    /// # Example
206    /// ```
207    /// # use google_cloud_storage::client::Storage;
208    /// # use google_cloud_storage::model_ext::ReadRange;
209    /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
210    /// let response = client
211    ///     .read_object("projects/_/buckets/my-bucket", "my-object")
212    ///     .set_read_range(ReadRange::tail(100))
213    ///     .send()
214    ///     .await?;
215    /// println!("response details={response:?}");
216    /// # Ok(()) }
217    pub fn tail(count: u64) -> Self {
218        Self(Range::Tail(count))
219    }
220
221    /// Returns a range representing the first `count` bytes of the object.
222    ///
223    /// # Example
224    /// ```
225    /// # use google_cloud_storage::client::Storage;
226    /// # use google_cloud_storage::model_ext::ReadRange;
227    /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
228    /// let response = client
229    ///     .read_object("projects/_/buckets/my-bucket", "my-object")
230    ///     .set_read_range(ReadRange::head(100))
231    ///     .send()
232    ///     .await?;
233    /// println!("response details={response:?}");
234    /// # Ok(()) }
235    pub fn head(count: u64) -> Self {
236        Self::segment(0, count)
237    }
238
239    /// Returns a range representing the `count` bytes starting at `offset`.
240    ///
241    /// # Example
242    /// ```
243    /// # use google_cloud_storage::client::Storage;
244    /// # use google_cloud_storage::model_ext::ReadRange;
245    /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
246    /// let response = client
247    ///     .read_object("projects/_/buckets/my-bucket", "my-object")
248    ///     .set_read_range(ReadRange::segment(1_000_000, 1_000))
249    ///     .send()
250    ///     .await?;
251    /// println!("response details={response:?}");
252    /// # Ok(()) }
253    pub fn segment(offset: u64, count: u64) -> Self {
254        Self(Range::Segment {
255            offset,
256            limit: count,
257        })
258    }
259}
260
261impl crate::model::ReadObjectRequest {
262    pub(crate) fn with_range(&mut self, range: ReadRange) {
263        // The limit for GCS objects is (currently) 5TiB, and the gRPC protocol
264        // uses i64 for the offset and limit. Clamping the values to the
265        // `[0, i64::MAX]`` range is safe, in that it does not lose any
266        // functionality.
267        match range.0 {
268            Range::All => {
269                self.read_offset = 0;
270                self.read_limit = 0;
271            }
272            Range::Offset(o) => {
273                self.read_offset = o.clamp(0, i64::MAX as u64) as i64;
274            }
275            Range::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            Range::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, Debug)]
289enum Range {
290    All,
291    Offset(u64),
292    Tail(u64),
293    Segment { offset: u64, limit: u64 },
294}
295
296/// Represents the parameters of a [WriteObject] request.
297///
298/// This type is only used in mocks of the `Storage` client.
299///
300/// [WriteObject]: crate::builder::storage::WriteObject
301#[derive(Debug, PartialEq)]
302#[non_exhaustive]
303#[allow(dead_code)]
304pub struct WriteObjectRequest {
305    pub spec: crate::model::WriteObjectSpec,
306    pub params: Option<crate::model::CommonObjectRequestParams>,
307}
308
309#[cfg(test)]
310pub(crate) mod tests {
311    use super::*;
312    use crate::model::ReadObjectRequest;
313    use base64::{Engine, prelude::BASE64_STANDARD};
314    use test_case::test_case;
315
316    type Result = anyhow::Result<()>;
317
318    /// This is used by the request builder tests.
319    pub(crate) fn create_key_helper() -> (Vec<u8>, String, Vec<u8>, String) {
320        // Make a 32-byte key.
321        let key = vec![b'a'; 32];
322        let key_base64 = BASE64_STANDARD.encode(key.clone());
323
324        let key_sha256 = Sha256::digest(key.clone());
325        let key_sha256_base64 = BASE64_STANDARD.encode(key_sha256);
326        (key, key_base64, key_sha256.to_vec(), key_sha256_base64)
327    }
328
329    #[test]
330    // This tests converting to KeyAes256 from some different types
331    // that can get converted to &[u8].
332    fn test_key_aes_256() -> Result {
333        let v_slice: &[u8] = &[b'c'; 32];
334        KeyAes256::new(v_slice)?;
335
336        let v_vec: Vec<u8> = vec![b'a'; 32];
337        KeyAes256::new(&v_vec)?;
338
339        let v_array: [u8; 32] = [b'a'; 32];
340        KeyAes256::new(&v_array)?;
341
342        let v_bytes: bytes::Bytes = bytes::Bytes::copy_from_slice(&v_array);
343        KeyAes256::new(&v_bytes)?;
344
345        Ok(())
346    }
347
348    #[test_case(&[b'a'; 0]; "no bytes")]
349    #[test_case(&[b'a'; 1]; "not enough bytes")]
350    #[test_case(&[b'a'; 33]; "too many bytes")]
351    fn test_key_aes_256_err(input: &[u8]) {
352        KeyAes256::new(input).unwrap_err();
353    }
354
355    #[test]
356    fn test_key_aes_256_to_control_model_object() -> Result {
357        let (key, _, key_sha256, _) = create_key_helper();
358        let key_aes_256 = KeyAes256::new(&key)?;
359        let params = crate::model::CommonObjectRequestParams::from(key_aes_256);
360        assert_eq!(params.encryption_algorithm, "AES256");
361        assert_eq!(params.encryption_key_bytes, key);
362        assert_eq!(params.encryption_key_sha256_bytes, key_sha256);
363        Ok(())
364    }
365
366    #[test_case(100, 100)]
367    #[test_case(u64::MAX, i64::MAX)]
368    #[test_case(0, 0)]
369    fn apply_offset(input: u64, want: i64) {
370        let range = ReadRange::offset(input);
371        let mut request = ReadObjectRequest::new();
372        request.with_range(range);
373        assert_eq!(request.read_offset, want);
374        assert_eq!(request.read_limit, 0);
375    }
376
377    #[test_case(100, 100)]
378    #[test_case(u64::MAX, i64::MAX)]
379    #[test_case(0, 0)]
380    fn apply_head(input: u64, want: i64) {
381        let range = ReadRange::head(input);
382        let mut request = ReadObjectRequest::new();
383        request.with_range(range);
384        assert_eq!(request.read_offset, 0);
385        assert_eq!(request.read_limit, want);
386    }
387
388    #[test_case(100, -100)]
389    #[test_case(u64::MAX, -i64::MAX)]
390    #[test_case(0, 0)]
391    fn apply_tail(input: u64, want: i64) {
392        let range = ReadRange::tail(input);
393        let mut request = ReadObjectRequest::new();
394        request.with_range(range);
395        assert_eq!(request.read_offset, want);
396        assert_eq!(request.read_limit, 0);
397    }
398
399    #[test_case(100, 100)]
400    #[test_case(u64::MAX, i64::MAX)]
401    #[test_case(0, 0)]
402    fn apply_segment_offset(input: u64, want: i64) {
403        let range = ReadRange::segment(input, 2000);
404        let mut request = ReadObjectRequest::new();
405        request.with_range(range);
406        assert_eq!(request.read_offset, want);
407        assert_eq!(request.read_limit, 2000);
408    }
409
410    #[test_case(100, 100)]
411    #[test_case(u64::MAX, i64::MAX)]
412    #[test_case(0, 0)]
413    fn apply_segment_limit(input: u64, want: i64) {
414        let range = ReadRange::segment(1000, input);
415        let mut request = ReadObjectRequest::new();
416        request.with_range(range);
417        assert_eq!(request.read_offset, 1000);
418        assert_eq!(request.read_limit, want);
419    }
420
421    #[test]
422    fn test_key_aes_256_display() -> Result {
423        let (key, key_base64, _, _) = create_key_helper();
424        let key_aes_256 = KeyAes256::new(&key)?;
425        assert_eq!(key_aes_256.to_string(), key_base64);
426        Ok(())
427    }
428}