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}