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