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