1use 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
19pub type PresignMethod = i32;
21
22pub mod presign_method {
26 pub const PRESIGN_METHOD_UNSPECIFIED: i32 = 0;
28 pub const PRESIGN_METHOD_GET: i32 = 1;
30 pub const PRESIGN_METHOD_PUT: i32 = 2;
32 pub const PRESIGN_METHOD_DELETE: i32 = 3;
34 pub const PRESIGN_METHOD_HEAD: i32 = 4;
36}
37
38#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct ByteRange {
44 pub start: Option<i64>,
46 pub end: Option<i64>,
48}
49
50#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct CopyObjectRequest {
56 pub source: Option<S3ObjectRef>,
58 pub destination: Option<S3ObjectRef>,
60 pub if_match: String,
62 pub if_none_match: String,
64}
65
66#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub struct CopyObjectResponse {
72 pub meta: Option<S3ObjectMeta>,
74}
75
76#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct CreateObjectAccessURLRequest {
84 pub r#ref: Option<S3ObjectRef>,
86 pub method: PresignMethod,
88 pub expires_seconds: i64,
90 pub content_type: String,
92 pub content_disposition: String,
94 pub headers: std::collections::BTreeMap<String, String>,
96}
97
98#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct CreateObjectAccessURLResponse {
105 pub url: String,
107 pub method: PresignMethod,
109 #[serde(with = "crate::serde_time")]
110 pub expires_at: Option<std::time::SystemTime>,
112 pub headers: std::collections::BTreeMap<String, String>,
114}
115
116#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
120#[serde(rename_all = "camelCase")]
121pub struct DeleteObjectRequest {
122 pub r#ref: Option<S3ObjectRef>,
124}
125
126#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct HeadObjectRequest {
132 pub r#ref: Option<S3ObjectRef>,
134}
135
136#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct HeadObjectResponse {
142 pub meta: Option<S3ObjectMeta>,
144}
145
146#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
150#[serde(rename_all = "camelCase")]
151pub struct ListObjectsRequest {
152 pub prefix: String,
154 pub delimiter: String,
156 pub continuation_token: String,
158 pub start_after: String,
160 pub max_keys: i32,
162}
163
164#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct ListObjectsResponse {
170 pub objects: Vec<S3ObjectMeta>,
172 pub common_prefixes: Vec<String>,
174 pub next_continuation_token: String,
176 pub has_more: bool,
178}
179
180#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
184#[serde(rename_all = "camelCase")]
185pub struct PresignObjectRequest {
186 pub r#ref: Option<S3ObjectRef>,
188 pub method: PresignMethod,
190 pub expires_seconds: i64,
192 pub content_type: String,
194 pub content_disposition: String,
196 pub headers: std::collections::BTreeMap<String, String>,
198}
199
200#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
204#[serde(rename_all = "camelCase")]
205pub struct PresignObjectResponse {
206 pub url: String,
208 pub method: PresignMethod,
210 #[serde(with = "crate::serde_time")]
211 pub expires_at: Option<std::time::SystemTime>,
213 pub headers: std::collections::BTreeMap<String, String>,
215}
216
217#[allow(clippy::enum_variant_names, clippy::large_enum_variant)]
219#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
220pub enum ReadObjectChunkResult {
221 Meta(S3ObjectMeta),
223 Data(Vec<u8>),
225}
226
227#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
231#[serde(rename_all = "camelCase")]
232pub struct ReadObjectChunk {
233 pub result: Option<ReadObjectChunkResult>,
235}
236
237#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
241#[serde(rename_all = "camelCase")]
242pub struct ReadObjectRequest {
243 pub r#ref: Option<S3ObjectRef>,
245 pub range: Option<ByteRange>,
247 pub if_match: String,
249 pub if_none_match: String,
251 #[serde(with = "crate::serde_time")]
252 pub if_modified_since: Option<std::time::SystemTime>,
254 #[serde(with = "crate::serde_time")]
255 pub if_unmodified_since: Option<std::time::SystemTime>,
257}
258
259#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
263#[serde(rename_all = "camelCase")]
264pub struct S3ObjectMeta {
265 pub r#ref: Option<S3ObjectRef>,
267 pub etag: String,
269 pub size: i64,
271 pub content_type: String,
273 #[serde(with = "crate::serde_time")]
274 pub last_modified: Option<std::time::SystemTime>,
276 pub metadata: std::collections::BTreeMap<String, String>,
278 pub storage_class: String,
280}
281
282#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
286#[serde(rename_all = "camelCase")]
287pub struct S3ObjectRef {
288 pub key: String,
290 pub version_id: String,
292}
293
294#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
299#[serde(rename_all = "camelCase")]
300pub struct WriteObjectOpen {
301 pub r#ref: Option<S3ObjectRef>,
303 pub content_type: String,
305 pub cache_control: String,
307 pub content_disposition: String,
309 pub content_encoding: String,
311 pub content_language: String,
313 pub metadata: std::collections::BTreeMap<String, String>,
315 pub if_match: String,
317 pub if_none_match: String,
319}
320
321#[allow(clippy::enum_variant_names, clippy::large_enum_variant)]
323#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
324pub enum WriteObjectRequestMsg {
325 Open(WriteObjectOpen),
327 Data(Vec<u8>),
329}
330
331#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct WriteObjectRequest {
337 pub msg: Option<WriteObjectRequestMsg>,
339}
340
341#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
345#[serde(rename_all = "camelCase")]
346pub struct WriteObjectResponse {
347 pub meta: Option<S3ObjectMeta>,
349}
350
351pub struct S3 {
355 inner: v1::s3_client::S3Client<HostServiceChannel>,
356 timeout: Option<std::time::Duration>,
357}
358
359impl S3 {
360 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 pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
371 self.timeout = Some(timeout);
372 self
373 }
374
375 pub async fn connect() -> Result<Self, GestaltError> {
377 Self::connect_named("").await
378 }
379
380 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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#[derive(Clone, Debug, Default)]
632pub struct S3PresignObjectOptions {
633 pub content_type: String,
635 pub content_disposition: String,
637}
638
639pub struct S3ReadObjectData {
641 inner: tonic::Streaming<v1::ReadObjectChunk>,
642}
643
644impl S3ReadObjectData {
645 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 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
670pub struct S3ReadObjectStream {
675 inner: tonic::Streaming<v1::ReadObjectChunk>,
676}
677
678impl S3ReadObjectStream {
679 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
685pub struct S3ObjectAccess {
691 inner: v1::s3_object_access_client::S3ObjectAccessClient<tonic::transport::Channel>,
692 timeout: Option<std::time::Duration>,
693}
694
695impl S3ObjectAccess {
696 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 pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
707 self.timeout = Some(timeout);
708 self
709 }
710
711 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 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#[derive(Clone, Debug, Default)]
758pub struct S3ObjectAccessCreateObjectAccessURLOptions {
759 pub content_type: String,
761 pub content_disposition: String,
763}