Skip to main content

gestalt/
s3.rs

1// Code generated by sdkgen. DO NOT EDIT.
2
3//! Generated native types and clients for s3.proto.
4
5use crate::codec::host_service::{HostServiceChannel, connect_host_service, plain_channel};
6use crate::codec::s3::{
7    from_wire_copy_object_response, from_wire_create_object_access_url_response,
8    from_wire_head_object_response, from_wire_list_objects_response,
9    from_wire_presign_object_response, from_wire_read_object_chunk,
10    from_wire_write_object_response, to_wire_copy_object_request,
11    to_wire_create_object_access_url_request, to_wire_delete_object_request,
12    to_wire_head_object_request, to_wire_list_objects_request, to_wire_presign_object_request,
13    to_wire_read_object_request, to_wire_write_object_request,
14};
15use crate::generated::v1;
16use crate::rpc_support::{GestaltError, gestalt_error_code};
17use tokio_stream::StreamExt;
18
19/// Open enum for `gestalt.provider.v1.PresignMethod`; unknown numeric values are preserved.
20pub type PresignMethod = i32;
21
22/// PresignMethod identifies the HTTP verb encoded into a presigned URL.
23///
24/// Named values of `PresignMethod`.
25pub mod presign_method {
26    /// PRESIGN_METHOD_UNSPECIFIED.
27    pub const PRESIGN_METHOD_UNSPECIFIED: i32 = 0;
28    /// PRESIGN_METHOD_GET.
29    pub const PRESIGN_METHOD_GET: i32 = 1;
30    /// PRESIGN_METHOD_PUT.
31    pub const PRESIGN_METHOD_PUT: i32 = 2;
32    /// PRESIGN_METHOD_DELETE.
33    pub const PRESIGN_METHOD_DELETE: i32 = 3;
34    /// PRESIGN_METHOD_HEAD.
35    pub const PRESIGN_METHOD_HEAD: i32 = 4;
36}
37
38/// ByteRange requests a half-open slice of an object's bytes.
39///
40/// Native message type for `gestalt.provider.v1.ByteRange`.
41#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct ByteRange {
44    /// The `start` field; None when unset.
45    pub start: Option<i64>,
46    /// The `end` field; None when unset.
47    pub end: Option<i64>,
48}
49
50/// CopyObjectRequest copies one object to another location.
51///
52/// Native message type for `gestalt.provider.v1.CopyObjectRequest`.
53#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct CopyObjectRequest {
56    /// The `source` field; None when unset.
57    pub source: Option<S3ObjectRef>,
58    /// The `destination` field; None when unset.
59    pub destination: Option<S3ObjectRef>,
60    /// The `if_match` field.
61    pub if_match: String,
62    /// The `if_none_match` field.
63    pub if_none_match: String,
64}
65
66/// CopyObjectResponse returns metadata for the copied object.
67///
68/// Native message type for `gestalt.provider.v1.CopyObjectResponse`.
69#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub struct CopyObjectResponse {
72    /// The `meta` field; None when unset.
73    pub meta: Option<S3ObjectMeta>,
74}
75
76/// CreateObjectAccessURLRequest asks the host to mint an HTTP object-access URL
77/// for a plugin-scoped S3 binding. The host authorizes and scopes the URL, then
78/// streams object bytes through the backing S3 provider.
79///
80/// Native message type for `gestalt.provider.v1.CreateObjectAccessURLRequest`.
81#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct CreateObjectAccessURLRequest {
84    /// The `ref` field; None when unset.
85    pub r#ref: Option<S3ObjectRef>,
86    /// The `method` field.
87    pub method: PresignMethod,
88    /// The `expires_seconds` field.
89    pub expires_seconds: i64,
90    /// The `content_type` field.
91    pub content_type: String,
92    /// The `content_disposition` field.
93    pub content_disposition: String,
94    /// The `headers` field.
95    pub headers: std::collections::BTreeMap<String, String>,
96}
97
98/// CreateObjectAccessURLResponse returns a hosted object-access URL plus any
99/// headers the caller must include when using it.
100///
101/// Native message type for `gestalt.provider.v1.CreateObjectAccessURLResponse`.
102#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct CreateObjectAccessURLResponse {
105    /// The `url` field.
106    pub url: String,
107    /// The `method` field.
108    pub method: PresignMethod,
109    #[serde(with = "crate::serde_time")]
110    /// The `expires_at` field; None when unset.
111    pub expires_at: Option<std::time::SystemTime>,
112    /// The `headers` field.
113    pub headers: std::collections::BTreeMap<String, String>,
114}
115
116/// DeleteObjectRequest removes one object.
117///
118/// Native message type for `gestalt.provider.v1.DeleteObjectRequest`.
119#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
120#[serde(rename_all = "camelCase")]
121pub struct DeleteObjectRequest {
122    /// The `ref` field; None when unset.
123    pub r#ref: Option<S3ObjectRef>,
124}
125
126/// HeadObjectRequest fetches metadata for one object.
127///
128/// Native message type for `gestalt.provider.v1.HeadObjectRequest`.
129#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct HeadObjectRequest {
132    /// The `ref` field; None when unset.
133    pub r#ref: Option<S3ObjectRef>,
134}
135
136/// HeadObjectResponse returns object metadata.
137///
138/// Native message type for `gestalt.provider.v1.HeadObjectResponse`.
139#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct HeadObjectResponse {
142    /// The `meta` field; None when unset.
143    pub meta: Option<S3ObjectMeta>,
144}
145
146/// ListObjectsRequest lists objects in the provider's configured bucket.
147///
148/// Native message type for `gestalt.provider.v1.ListObjectsRequest`.
149#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
150#[serde(rename_all = "camelCase")]
151pub struct ListObjectsRequest {
152    /// The `prefix` field.
153    pub prefix: String,
154    /// The `delimiter` field.
155    pub delimiter: String,
156    /// The `continuation_token` field.
157    pub continuation_token: String,
158    /// The `start_after` field.
159    pub start_after: String,
160    /// The `max_keys` field.
161    pub max_keys: i32,
162}
163
164/// ListObjectsResponse is one page of list-objects results.
165///
166/// Native message type for `gestalt.provider.v1.ListObjectsResponse`.
167#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct ListObjectsResponse {
170    /// The `objects` field.
171    pub objects: Vec<S3ObjectMeta>,
172    /// The `common_prefixes` field.
173    pub common_prefixes: Vec<String>,
174    /// The `next_continuation_token` field.
175    pub next_continuation_token: String,
176    /// The `has_more` field.
177    pub has_more: bool,
178}
179
180/// PresignObjectRequest asks the provider to mint a presigned URL.
181///
182/// Native message type for `gestalt.provider.v1.PresignObjectRequest`.
183#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
184#[serde(rename_all = "camelCase")]
185pub struct PresignObjectRequest {
186    /// The `ref` field; None when unset.
187    pub r#ref: Option<S3ObjectRef>,
188    /// The `method` field.
189    pub method: PresignMethod,
190    /// The `expires_seconds` field.
191    pub expires_seconds: i64,
192    /// The `content_type` field.
193    pub content_type: String,
194    /// The `content_disposition` field.
195    pub content_disposition: String,
196    /// The `headers` field.
197    pub headers: std::collections::BTreeMap<String, String>,
198}
199
200/// PresignObjectResponse returns a presigned URL plus any required headers.
201///
202/// Native message type for `gestalt.provider.v1.PresignObjectResponse`.
203#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
204#[serde(rename_all = "camelCase")]
205pub struct PresignObjectResponse {
206    /// The `url` field.
207    pub url: String,
208    /// The `method` field.
209    pub method: PresignMethod,
210    #[serde(with = "crate::serde_time")]
211    /// The `expires_at` field; None when unset.
212    pub expires_at: Option<std::time::SystemTime>,
213    /// The `headers` field.
214    pub headers: std::collections::BTreeMap<String, String>,
215}
216
217/// Values of the `result` oneof in `ReadObjectChunk`; the message field is None when unset.
218#[allow(clippy::enum_variant_names, clippy::large_enum_variant)]
219#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
220pub enum ReadObjectChunkResult {
221    /// The `meta` variant.
222    Meta(S3ObjectMeta),
223    /// The `data` variant.
224    Data(Vec<u8>),
225}
226
227/// ReadObjectChunk is one frame in a streaming object read.
228///
229/// Native message type for `gestalt.provider.v1.ReadObjectChunk`.
230#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
231#[serde(rename_all = "camelCase")]
232pub struct ReadObjectChunk {
233    /// The `result` oneof; None when unset.
234    pub result: Option<ReadObjectChunkResult>,
235}
236
237/// ReadObjectRequest opens a streaming object read.
238///
239/// Native message type for `gestalt.provider.v1.ReadObjectRequest`.
240#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
241#[serde(rename_all = "camelCase")]
242pub struct ReadObjectRequest {
243    /// The `ref` field; None when unset.
244    pub r#ref: Option<S3ObjectRef>,
245    /// The `range` field; None when unset.
246    pub range: Option<ByteRange>,
247    /// The `if_match` field.
248    pub if_match: String,
249    /// The `if_none_match` field.
250    pub if_none_match: String,
251    #[serde(with = "crate::serde_time")]
252    /// The `if_modified_since` field; None when unset.
253    pub if_modified_since: Option<std::time::SystemTime>,
254    #[serde(with = "crate::serde_time")]
255    /// The `if_unmodified_since` field; None when unset.
256    pub if_unmodified_since: Option<std::time::SystemTime>,
257}
258
259/// S3ObjectMeta describes one object returned by the provider.
260///
261/// Native message type for `gestalt.provider.v1.S3ObjectMeta`.
262#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
263#[serde(rename_all = "camelCase")]
264pub struct S3ObjectMeta {
265    /// The `ref` field; None when unset.
266    pub r#ref: Option<S3ObjectRef>,
267    /// The `etag` field.
268    pub etag: String,
269    /// The `size` field.
270    pub size: i64,
271    /// The `content_type` field.
272    pub content_type: String,
273    #[serde(with = "crate::serde_time")]
274    /// The `last_modified` field; None when unset.
275    pub last_modified: Option<std::time::SystemTime>,
276    /// The `metadata` field.
277    pub metadata: std::collections::BTreeMap<String, String>,
278    /// The `storage_class` field.
279    pub storage_class: String,
280}
281
282/// S3ObjectRef identifies one object or object version.
283///
284/// Native message type for `gestalt.provider.v1.S3ObjectRef`.
285#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
286#[serde(rename_all = "camelCase")]
287pub struct S3ObjectRef {
288    /// The `key` field.
289    pub key: String,
290    /// The `version_id` field.
291    pub version_id: String,
292}
293
294/// WriteObjectOpen carries the metadata frame that must be sent first in a
295/// write-object stream.
296///
297/// Native message type for `gestalt.provider.v1.WriteObjectOpen`.
298#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
299#[serde(rename_all = "camelCase")]
300pub struct WriteObjectOpen {
301    /// The `ref` field; None when unset.
302    pub r#ref: Option<S3ObjectRef>,
303    /// The `content_type` field.
304    pub content_type: String,
305    /// The `cache_control` field.
306    pub cache_control: String,
307    /// The `content_disposition` field.
308    pub content_disposition: String,
309    /// The `content_encoding` field.
310    pub content_encoding: String,
311    /// The `content_language` field.
312    pub content_language: String,
313    /// The `metadata` field.
314    pub metadata: std::collections::BTreeMap<String, String>,
315    /// The `if_match` field.
316    pub if_match: String,
317    /// The `if_none_match` field.
318    pub if_none_match: String,
319}
320
321/// Values of the `msg` oneof in `WriteObjectRequest`; the message field is None when unset.
322#[allow(clippy::enum_variant_names, clippy::large_enum_variant)]
323#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
324pub enum WriteObjectRequestMsg {
325    /// The `open` variant.
326    Open(WriteObjectOpen),
327    /// The `data` variant.
328    Data(Vec<u8>),
329}
330
331/// WriteObjectRequest is one frame in a write-object stream.
332///
333/// Native message type for `gestalt.provider.v1.WriteObjectRequest`.
334#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct WriteObjectRequest {
337    /// The `msg` oneof; None when unset.
338    pub msg: Option<WriteObjectRequestMsg>,
339}
340
341/// WriteObjectResponse returns metadata for the committed object.
342///
343/// Native message type for `gestalt.provider.v1.WriteObjectResponse`.
344#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
345#[serde(rename_all = "camelCase")]
346pub struct WriteObjectResponse {
347    /// The `meta` field; None when unset.
348    pub meta: Option<S3ObjectMeta>,
349}
350
351/// S3 models the shared Gestalt S3-provider protocol.
352///
353/// Client for the `gestalt.provider.v1.S3` service.
354pub struct S3 {
355    inner: v1::s3_client::S3Client<HostServiceChannel>,
356    timeout: Option<std::time::Duration>,
357}
358
359impl S3 {
360    /// Creates a client over an established channel.
361    pub fn new(channel: tonic::transport::Channel) -> Self {
362        Self {
363            inner: v1::s3_client::S3Client::new(plain_channel(channel)),
364            timeout: None,
365        }
366    }
367
368    /// Sets a deadline applied to every unary call; calls that run past it
369    /// fail with DEADLINE_EXCEEDED. Streaming calls are unaffected.
370    pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
371        self.timeout = Some(timeout);
372        self
373    }
374
375    /// Connects to the `s3` host service described by the environment.
376    pub async fn connect() -> Result<Self, GestaltError> {
377        Self::connect_named("").await
378    }
379
380    /// Connects to the named `s3` host-service binding.
381    pub async fn connect_named(name: &str) -> Result<Self, GestaltError> {
382        Ok(Self {
383            inner: v1::s3_client::S3Client::new(connect_host_service("s3", name).await?),
384            timeout: None,
385        })
386    }
387
388    /// Calls `gestalt.provider.v1.S3.HeadObject`.
389    pub async fn head_object(
390        &mut self,
391        r#ref: Option<S3ObjectRef>,
392    ) -> Result<HeadObjectResponse, GestaltError> {
393        let request = HeadObjectRequest { r#ref };
394        let mut tonic_request = tonic::Request::new(to_wire_head_object_request(request));
395        if let Some(timeout) = self.timeout {
396            tonic_request.set_timeout(timeout);
397        }
398        let response = self.inner.head_object(tonic_request).await?;
399        Ok(from_wire_head_object_response(response.into_inner()))
400    }
401
402    /// Calls `gestalt.provider.v1.S3.HeadObject` with the full request and response messages.
403    pub async fn head_object_raw(
404        &mut self,
405        request: HeadObjectRequest,
406    ) -> Result<HeadObjectResponse, GestaltError> {
407        let mut tonic_request = tonic::Request::new(to_wire_head_object_request(request));
408        if let Some(timeout) = self.timeout {
409            tonic_request.set_timeout(timeout);
410        }
411        let response = self.inner.head_object(tonic_request).await?;
412        Ok(from_wire_head_object_response(response.into_inner()))
413    }
414
415    /// The first response frame carries object metadata. All subsequent frames
416    /// carry byte chunks. Zero-byte objects therefore emit exactly one frame.
417    ///
418    /// Calls `gestalt.provider.v1.S3.ReadObject`, returning the `meta` header and a payload stream.
419    pub async fn read_object(
420        &mut self,
421        request: ReadObjectRequest,
422    ) -> Result<(S3ObjectMeta, S3ReadObjectData), GestaltError> {
423        let mut frames = self
424            .inner
425            .read_object(to_wire_read_object_request(request))
426            .await?
427            .into_inner();
428        let frame = frames.message().await?.map(from_wire_read_object_chunk);
429        let meta = match frame.and_then(|frame| frame.result) {
430            Some(ReadObjectChunkResult::Meta(value)) => value,
431            _ => {
432                return Err(GestaltError::new(
433                    gestalt_error_code::INTERNAL,
434                    "stream did not begin with the expected header frame",
435                ));
436            }
437        };
438        Ok((meta, S3ReadObjectData { inner: frames }))
439    }
440
441    /// The first response frame carries object metadata. All subsequent frames
442    /// carry byte chunks. Zero-byte objects therefore emit exactly one frame.
443    ///
444    /// Calls `gestalt.provider.v1.S3.ReadObject`, returning a stream of converted frames.
445    pub async fn read_object_raw(
446        &mut self,
447        request: ReadObjectRequest,
448    ) -> Result<S3ReadObjectStream, GestaltError> {
449        let response = self
450            .inner
451            .read_object(to_wire_read_object_request(request))
452            .await?;
453        Ok(S3ReadObjectStream {
454            inner: response.into_inner(),
455        })
456    }
457
458    /// The first request frame must carry WriteObjectOpen metadata. All
459    /// subsequent frames carry raw bytes. The response is emitted only after the
460    /// object has been durably committed by the provider.
461    ///
462    /// Calls `gestalt.provider.v1.S3.WriteObject`, sending the `open` header frame and then one frame per `data` chunk.
463    pub async fn write_object(
464        &mut self,
465        open: WriteObjectOpen,
466        data: impl tokio_stream::Stream<Item = Vec<u8>> + Send + 'static,
467    ) -> Result<WriteObjectResponse, GestaltError> {
468        let requests = tokio_stream::once(WriteObjectRequest {
469            msg: Some(WriteObjectRequestMsg::Open(open)),
470        })
471        .chain(data.map(|chunk| WriteObjectRequest {
472            msg: Some(WriteObjectRequestMsg::Data(chunk)),
473        }))
474        .map(to_wire_write_object_request);
475        let response = self.inner.write_object(requests).await?;
476        Ok(from_wire_write_object_response(response.into_inner()))
477    }
478
479    /// The first request frame must carry WriteObjectOpen metadata. All
480    /// subsequent frames carry raw bytes. The response is emitted only after the
481    /// object has been durably committed by the provider.
482    ///
483    /// Calls `gestalt.provider.v1.S3.WriteObject` with the full request and response messages.
484    pub async fn write_object_raw(
485        &mut self,
486        requests: impl tokio_stream::Stream<Item = WriteObjectRequest> + Send + 'static,
487    ) -> Result<WriteObjectResponse, GestaltError> {
488        let response = self
489            .inner
490            .write_object(requests.map(to_wire_write_object_request))
491            .await?;
492        Ok(from_wire_write_object_response(response.into_inner()))
493    }
494
495    /// Calls `gestalt.provider.v1.S3.DeleteObject`.
496    pub async fn delete_object(&mut self, r#ref: Option<S3ObjectRef>) -> Result<(), GestaltError> {
497        let request = DeleteObjectRequest { r#ref };
498        let mut tonic_request = tonic::Request::new(to_wire_delete_object_request(request));
499        if let Some(timeout) = self.timeout {
500            tonic_request.set_timeout(timeout);
501        }
502        self.inner.delete_object(tonic_request).await?;
503        Ok(())
504    }
505
506    /// Calls `gestalt.provider.v1.S3.DeleteObject` with the full request and response messages.
507    pub async fn delete_object_raw(
508        &mut self,
509        request: DeleteObjectRequest,
510    ) -> Result<(), GestaltError> {
511        let mut tonic_request = tonic::Request::new(to_wire_delete_object_request(request));
512        if let Some(timeout) = self.timeout {
513            tonic_request.set_timeout(timeout);
514        }
515        self.inner.delete_object(tonic_request).await?;
516        Ok(())
517    }
518
519    /// Calls `gestalt.provider.v1.S3.ListObjects`.
520    pub async fn list_objects(
521        &mut self,
522        prefix: String,
523        delimiter: String,
524        continuation_token: String,
525        start_after: String,
526        max_keys: i32,
527    ) -> Result<ListObjectsResponse, GestaltError> {
528        let request = ListObjectsRequest {
529            prefix,
530            delimiter,
531            continuation_token,
532            start_after,
533            max_keys,
534        };
535        let mut tonic_request = tonic::Request::new(to_wire_list_objects_request(request));
536        if let Some(timeout) = self.timeout {
537            tonic_request.set_timeout(timeout);
538        }
539        let response = self.inner.list_objects(tonic_request).await?;
540        Ok(from_wire_list_objects_response(response.into_inner()))
541    }
542
543    /// Calls `gestalt.provider.v1.S3.ListObjects` with the full request and response messages.
544    pub async fn list_objects_raw(
545        &mut self,
546        request: ListObjectsRequest,
547    ) -> Result<ListObjectsResponse, GestaltError> {
548        let mut tonic_request = tonic::Request::new(to_wire_list_objects_request(request));
549        if let Some(timeout) = self.timeout {
550            tonic_request.set_timeout(timeout);
551        }
552        let response = self.inner.list_objects(tonic_request).await?;
553        Ok(from_wire_list_objects_response(response.into_inner()))
554    }
555
556    /// Calls `gestalt.provider.v1.S3.CopyObject`.
557    pub async fn copy_object(
558        &mut self,
559        if_match: String,
560        if_none_match: String,
561        source: Option<S3ObjectRef>,
562        destination: Option<S3ObjectRef>,
563    ) -> Result<CopyObjectResponse, GestaltError> {
564        let request = CopyObjectRequest {
565            if_match,
566            if_none_match,
567            source,
568            destination,
569        };
570        let mut tonic_request = tonic::Request::new(to_wire_copy_object_request(request));
571        if let Some(timeout) = self.timeout {
572            tonic_request.set_timeout(timeout);
573        }
574        let response = self.inner.copy_object(tonic_request).await?;
575        Ok(from_wire_copy_object_response(response.into_inner()))
576    }
577
578    /// Calls `gestalt.provider.v1.S3.CopyObject` with the full request and response messages.
579    pub async fn copy_object_raw(
580        &mut self,
581        request: CopyObjectRequest,
582    ) -> Result<CopyObjectResponse, GestaltError> {
583        let mut tonic_request = tonic::Request::new(to_wire_copy_object_request(request));
584        if let Some(timeout) = self.timeout {
585            tonic_request.set_timeout(timeout);
586        }
587        let response = self.inner.copy_object(tonic_request).await?;
588        Ok(from_wire_copy_object_response(response.into_inner()))
589    }
590
591    /// Calls `gestalt.provider.v1.S3.PresignObject`.
592    pub async fn presign_object(
593        &mut self,
594        method: PresignMethod,
595        expires_seconds: i64,
596        r#ref: Option<S3ObjectRef>,
597        options: S3PresignObjectOptions,
598    ) -> Result<PresignObjectResponse, GestaltError> {
599        let request = PresignObjectRequest {
600            method,
601            expires_seconds,
602            r#ref,
603            content_type: options.content_type,
604            content_disposition: options.content_disposition,
605            ..Default::default()
606        };
607        let mut tonic_request = tonic::Request::new(to_wire_presign_object_request(request));
608        if let Some(timeout) = self.timeout {
609            tonic_request.set_timeout(timeout);
610        }
611        let response = self.inner.presign_object(tonic_request).await?;
612        Ok(from_wire_presign_object_response(response.into_inner()))
613    }
614
615    /// Calls `gestalt.provider.v1.S3.PresignObject` with the full request and response messages.
616    pub async fn presign_object_raw(
617        &mut self,
618        request: PresignObjectRequest,
619    ) -> Result<PresignObjectResponse, GestaltError> {
620        let mut tonic_request = tonic::Request::new(to_wire_presign_object_request(request));
621        if let Some(timeout) = self.timeout {
622            tonic_request.set_timeout(timeout);
623        }
624        let response = self.inner.presign_object(tonic_request).await?;
625        Ok(from_wire_presign_object_response(response.into_inner()))
626    }
627}
628
629/// Optional parameters of [`S3::presign_object`]; the default value leaves every
630/// option unset.
631#[derive(Clone, Debug, Default)]
632pub struct S3PresignObjectOptions {
633    /// The `content_type` field.
634    pub content_type: String,
635    /// The `content_disposition` field.
636    pub content_disposition: String,
637}
638
639/// Payload stream returned by `S3::read_object`; the header frame has already been consumed.
640pub struct S3ReadObjectData {
641    inner: tonic::Streaming<v1::ReadObjectChunk>,
642}
643
644impl S3ReadObjectData {
645    /// Receives the next payload chunk, or None when the stream ends.
646    pub async fn recv(&mut self) -> Result<Option<Vec<u8>>, GestaltError> {
647        match self.inner.message().await?.map(from_wire_read_object_chunk) {
648            None => Ok(None),
649            Some(frame) => match frame.result {
650                Some(ReadObjectChunkResult::Data(value)) => Ok(Some(value)),
651                _ => Err(GestaltError::new(
652                    gestalt_error_code::INTERNAL,
653                    "unexpected frame in payload stream",
654                )),
655            },
656        }
657    }
658
659    /// Buffers the remaining payload chunks into one byte vector, like the
660    /// AWS SDK's `ByteStream::collect`.
661    pub async fn collect(&mut self) -> Result<Vec<u8>, GestaltError> {
662        let mut out = Vec::new();
663        while let Some(chunk) = self.recv().await? {
664            out.extend_from_slice(&chunk);
665        }
666        Ok(out)
667    }
668}
669
670/// The first response frame carries object metadata. All subsequent frames
671/// carry byte chunks. Zero-byte objects therefore emit exactly one frame.
672///
673/// Stream of converted `ReadObjectChunk` frames; transport errors convert to GestaltError.
674pub struct S3ReadObjectStream {
675    inner: tonic::Streaming<v1::ReadObjectChunk>,
676}
677
678impl S3ReadObjectStream {
679    /// Receives the next frame, or None when the stream ends.
680    pub async fn recv(&mut self) -> Result<Option<ReadObjectChunk>, GestaltError> {
681        Ok(self.inner.message().await?.map(from_wire_read_object_chunk))
682    }
683}
684
685/// S3ObjectAccess models host-mediated object access for plugin-scoped S3
686/// bindings. It is registered by gestaltd for apps and is not implemented by
687/// S3 providers.
688///
689/// Client for the `gestalt.provider.v1.S3ObjectAccess` service.
690pub struct S3ObjectAccess {
691    inner: v1::s3_object_access_client::S3ObjectAccessClient<tonic::transport::Channel>,
692    timeout: Option<std::time::Duration>,
693}
694
695impl S3ObjectAccess {
696    /// Creates a client over an established channel.
697    pub fn new(channel: tonic::transport::Channel) -> Self {
698        Self {
699            inner: v1::s3_object_access_client::S3ObjectAccessClient::new(channel),
700            timeout: None,
701        }
702    }
703
704    /// Sets a deadline applied to every unary call; calls that run past it
705    /// fail with DEADLINE_EXCEEDED. Streaming calls are unaffected.
706    pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
707        self.timeout = Some(timeout);
708        self
709    }
710
711    /// Calls `gestalt.provider.v1.S3ObjectAccess.CreateObjectAccessURL`.
712    pub async fn create_object_access_url(
713        &mut self,
714        method: PresignMethod,
715        expires_seconds: i64,
716        r#ref: Option<S3ObjectRef>,
717        options: S3ObjectAccessCreateObjectAccessURLOptions,
718    ) -> Result<CreateObjectAccessURLResponse, GestaltError> {
719        let request = CreateObjectAccessURLRequest {
720            method,
721            expires_seconds,
722            r#ref,
723            content_type: options.content_type,
724            content_disposition: options.content_disposition,
725            ..Default::default()
726        };
727        let mut tonic_request =
728            tonic::Request::new(to_wire_create_object_access_url_request(request));
729        if let Some(timeout) = self.timeout {
730            tonic_request.set_timeout(timeout);
731        }
732        let response = self.inner.create_object_access_url(tonic_request).await?;
733        Ok(from_wire_create_object_access_url_response(
734            response.into_inner(),
735        ))
736    }
737
738    /// Calls `gestalt.provider.v1.S3ObjectAccess.CreateObjectAccessURL` with the full request and response messages.
739    pub async fn create_object_access_url_raw(
740        &mut self,
741        request: CreateObjectAccessURLRequest,
742    ) -> Result<CreateObjectAccessURLResponse, GestaltError> {
743        let mut tonic_request =
744            tonic::Request::new(to_wire_create_object_access_url_request(request));
745        if let Some(timeout) = self.timeout {
746            tonic_request.set_timeout(timeout);
747        }
748        let response = self.inner.create_object_access_url(tonic_request).await?;
749        Ok(from_wire_create_object_access_url_response(
750            response.into_inner(),
751        ))
752    }
753}
754
755/// Optional parameters of [`S3ObjectAccess::create_object_access_url`]; the default value leaves every
756/// option unset.
757#[derive(Clone, Debug, Default)]
758pub struct S3ObjectAccessCreateObjectAccessURLOptions {
759    /// The `content_type` field.
760    pub content_type: String,
761    /// The `content_disposition` field.
762    pub content_disposition: String,
763}