google_cloud_storage/
object_descriptor.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//! Defines the return type for [Storage::open_object][crate::client::Storage::open_object].
16
17use crate::http::HeaderMap;
18use crate::model::Object;
19use crate::model_ext::ReadRange;
20use crate::read_object::ReadObjectResponse;
21use crate::storage::bidi::stub::dynamic::ObjectDescriptor as ObjectDescriptorStub;
22use std::sync::Arc;
23
24/// An open object ready to read one or more ranges.
25///
26/// # Example
27/// ```
28/// # use google_cloud_storage::object_descriptor::ObjectDescriptor;
29/// use google_cloud_storage::{client::Storage, model_ext::ReadRange};
30/// # async fn sample() -> anyhow::Result<()> {
31/// let client = Storage::builder().build().await?;
32/// let open: ObjectDescriptor = client
33///     .open_object("projects/_/buckets/my-bucket", "my-object")
34///     .send().await?;
35/// println!("metadata = {:?}", open.object());
36/// // Read 2000 bytes starting at offset 1000.
37/// let mut reader = open.read_range(ReadRange::segment(1000, 2000)).await;
38/// while let Some(data) = reader.next().await.transpose()? {
39///   println!("received {} bytes", data.len());
40/// }
41/// # Ok(()) }
42/// ```
43///
44/// This is analogous to a "file descriptor". It represents an object in Cloud
45/// Storage that has been "opened" and is ready for more read operations. An
46/// object descriptor can have multiple concurrent read operations at a time.
47/// You may call `read_range()` even if previous reads have not completed.
48///
49/// There are no guarantees about the order of the responses. All the data for
50/// a `read_range()` may be returned before any data of earlier `read_range()`
51/// calls, or the data may arrive in any interleaved order. Naturally, the data
52/// for a single ranged read arrives in order.
53///
54/// You should actively read from all concurrent reads: the client library uses
55/// separate buffers for each `read_range()` call, but once a buffer is full the
56/// library will stop delivering data to **all** the concurrent reads.
57#[derive(Debug, Clone)]
58pub struct ObjectDescriptor {
59    inner: Arc<dyn ObjectDescriptorStub>,
60}
61
62impl ObjectDescriptor {
63    /// Returns the metadata for the open object.
64    ///
65    /// # Example
66    /// ```
67    /// # use google_cloud_storage::object_descriptor::ObjectDescriptor;
68    /// # async fn sample() -> anyhow::Result<()> {
69    /// let descriptor = open();
70    /// println!("object generation = {}", descriptor.object().generation);
71    ///
72    /// fn open() -> ObjectDescriptor {
73    /// # panic!()
74    /// // ... details omitted ...
75    /// }
76    /// # Ok(()) }
77    /// ```
78    ///
79    pub fn object(&self) -> Object {
80        self.inner.object()
81    }
82
83    /// Start reading a range.
84    ///
85    /// # Example
86    /// ```
87    /// # use google_cloud_storage::object_descriptor::ObjectDescriptor;
88    /// use google_cloud_storage::{model_ext::ReadRange, read_object::ReadObjectResponse};
89    /// # async fn sample() -> anyhow::Result<()> {
90    /// const MIB: u64 = 1024 * 1024;
91    /// let descriptor = open();
92    /// println!("object generation = {}", descriptor.object().generation);
93    ///
94    /// // Read 2 MiB starting at offset 0.
95    /// let read1 = descriptor.read_range(ReadRange::segment(0, 2 * MIB)).await;
96    /// // Concurrently read 2 MiB starting at offset 4 MiB.
97    /// let read2 = descriptor.read_range(ReadRange::segment(4 * MIB, 2 * MIB)).await;
98    ///
99    /// let t1 = tokio::spawn(async move { do_read(read1) });
100    /// let t2 = tokio::spawn(async move { do_read(read2) });
101    ///
102    /// async fn do_read(mut reader: ReadObjectResponse) {
103    /// # panic!()
104    /// // ... details omitted ...
105    /// }
106    /// fn open() -> ObjectDescriptor {
107    /// # panic!()
108    /// // ... details omitted ...
109    /// }
110    /// # Ok(()) }
111    /// ```
112    pub async fn read_range(&self, range: ReadRange) -> ReadObjectResponse {
113        self.inner.read_range(range).await
114    }
115
116    /// Returns metadata about the original `open_object()` request.
117    ///
118    /// # Example
119    /// ```
120    /// # use google_cloud_storage::object_descriptor::ObjectDescriptor;
121    /// # async fn sample() -> anyhow::Result<()> {
122    /// let descriptor = open();
123    /// // Often useful when troubleshooting problems with Google Support.
124    /// let headers = descriptor.headers();
125    /// println!("debugging header = {:?}", headers.get("x-guploader-uploadid"));
126    ///
127    /// fn open() -> ObjectDescriptor {
128    /// # panic!()
129    /// // ... details omitted ...
130    /// }
131    /// # Ok(()) }
132    /// ```
133    pub fn headers(&self) -> HeaderMap {
134        self.inner.headers()
135    }
136
137    /// Create a new instance.
138    ///
139    /// Application developers should only need to create an `ObjectDescriptor`
140    /// in unit tests.
141    pub fn new<T>(inner: T) -> Self
142    where
143        T: crate::stub::ObjectDescriptor + 'static,
144    {
145        Self {
146            inner: Arc::new(inner),
147        }
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use crate::model_ext::ObjectHighlights;
155    use crate::read_object::ReadObjectResponse;
156    use http::{HeaderName, HeaderValue};
157    use mockall::mock;
158    use static_assertions::assert_impl_all;
159
160    #[test]
161    fn impls() {
162        assert_impl_all!(ObjectDescriptor: Clone, std::fmt::Debug);
163    }
164
165    // Verify this can be mocked inside the crate.
166    // TODO(#3838) - support mocking outside the crate too.
167    #[tokio::test]
168    async fn can_be_mocked() -> anyhow::Result<()> {
169        let object = Object::new().set_name("test-object").set_generation(123456);
170        let headers = HeaderMap::from_iter(
171            [
172                ("content-type", "application/octet-stream"),
173                ("x-guploader-uploadid", "abc-123"),
174            ]
175            .map(|(k, v)| (HeaderName::from_static(k), HeaderValue::from_static(v))),
176        );
177        let mut mock = MockDescriptor::new();
178        mock.expect_object().times(1).return_const(object.clone());
179        mock.expect_read_range()
180            .times(1)
181            .withf(|range| range.0 == ReadRange::segment(100, 200).0)
182            .returning(|_| ReadObjectResponse::new(Box::new(MockResponse::new())));
183        mock.expect_headers().times(1).return_const(headers.clone());
184
185        let descriptor = ObjectDescriptor::new(mock);
186        assert_eq!(descriptor.object(), object);
187        assert_eq!(descriptor.headers(), headers);
188
189        let _reader = descriptor.read_range(ReadRange::segment(100, 200)).await;
190        Ok(())
191    }
192
193    mock! {
194        #[derive(Debug)]
195        Descriptor {}
196
197        impl crate::stub::ObjectDescriptor for Descriptor {
198            fn object(&self) -> Object;
199            async fn read_range(&self, range: ReadRange) -> ReadObjectResponse;
200            fn headers(&self) -> HeaderMap;
201        }
202    }
203
204    mock! {
205        #[derive(Debug)]
206        Response {}
207
208        #[async_trait::async_trait]
209        impl crate::read_object::dynamic::ReadObjectResponse for Response {
210            fn object(&self) -> ObjectHighlights;
211            async fn next(&mut self) -> Option<crate::Result<bytes::Bytes>>;
212        }
213    }
214}