Skip to main content

s2_sdk/
ops.rs

1#[cfg(feature = "_hidden")]
2use crate::client::Connect;
3use crate::{
4    api::{AccountClient, BaseClient, BasinClient},
5    error::{AppendError, ReadError, RequestError},
6    producer::{Producer, ProducerConfig},
7    session::{self, AppendSession, AppendSessionConfig, ReadSession, ReadSessionError},
8    types::{
9        AccessTokenId, AccessTokenInfo, AppendAck, AppendInput, BasinConfig, BasinInfo, BasinName,
10        CreateBasinInput, CreateStreamInput, DeleteBasinInput, DeleteStreamInput, EncryptionKey,
11        EnsureBasinInput, EnsureOutput, EnsureStreamInput, GetAccountMetricsInput,
12        GetBasinMetricsInput, GetStreamMetricsInput, IssueAccessTokenInput, ListAccessTokensInput,
13        ListAllAccessTokensInput, ListAllBasinsInput, ListAllStreamsInput, ListBasinsInput,
14        ListStreamsInput, LocationInfo, LocationName, Metric, Page, ReadBatch, ReadInput,
15        ReadSessionConfig, ReconfigureBasinInput, ReconfigureStreamInput, S2Config, StreamConfig,
16        StreamInfo, StreamName, StreamPosition, Streaming,
17    },
18};
19
20#[derive(Debug, Clone)]
21/// An S2 account.
22pub struct S2 {
23    client: AccountClient,
24}
25
26impl S2 {
27    /// Create a new [`S2`].
28    pub fn new(config: S2Config) -> Result<Self, RequestError> {
29        let base_client = BaseClient::init(&config)?;
30        Ok(Self {
31            client: AccountClient::init(config, base_client),
32        })
33    }
34
35    #[doc(hidden)]
36    #[cfg(feature = "_hidden")]
37    pub fn new_with_connector<C>(config: S2Config, connector: C) -> Result<Self, RequestError>
38    where
39        C: Connect + Clone + Send + Sync + 'static,
40    {
41        let base_client = BaseClient::init_with_connector(&config, connector)?;
42        Ok(Self {
43            client: AccountClient::init(config, base_client),
44        })
45    }
46
47    /// Get an [`S2Basin`].
48    pub fn basin(&self, name: BasinName) -> S2Basin {
49        S2Basin {
50            client: self.client.basin_client(name),
51        }
52    }
53
54    /// List a page of basins.
55    ///
56    /// See [`list_all_basins`](crate::S2::list_all_basins) for automatic pagination.
57    pub async fn list_basins(
58        &self,
59        input: ListBasinsInput,
60    ) -> Result<Page<BasinInfo>, RequestError> {
61        let response = self.client.list_basins(input.into()).await?;
62        Ok(Page::new(
63            response
64                .basins
65                .into_iter()
66                .map(TryInto::try_into)
67                .collect::<Result<Vec<_>, _>>()?,
68            response.has_more,
69        ))
70    }
71
72    /// List all basins, paginating automatically.
73    pub fn list_all_basins(&self, input: ListAllBasinsInput) -> Streaming<BasinInfo> {
74        let s2 = self.clone();
75        let prefix = input.prefix;
76        let start_after = input.start_after;
77        let include_deleted = input.include_deleted;
78        let mut input = ListBasinsInput::new()
79            .with_prefix(prefix)
80            .with_start_after(start_after);
81        Box::pin(async_stream::try_stream! {
82            loop {
83                let page = s2.list_basins(input.clone()).await?;
84                let start_after = page.values.last().map(|info| info.name.clone().into());
85
86                for info in page.values {
87                    if !include_deleted && info.deleted_at.is_some() {
88                        continue;
89                    }
90                    yield info;
91                }
92
93                if page.has_more && let Some(start_after) = start_after {
94                    input = input.with_start_after(start_after);
95                } else {
96                    break;
97                }
98            }
99        })
100    }
101
102    /// Create a basin.
103    pub async fn create_basin(&self, input: CreateBasinInput) -> Result<BasinInfo, RequestError> {
104        let (request, idempotency_token) = input.into();
105        let info = self.client.create_basin(request, idempotency_token).await?;
106        Ok(info.try_into()?)
107    }
108
109    /// Ensure a basin.
110    ///
111    /// If the basin doesn't exist, creates the basin with specified configuration.
112    ///
113    /// If the basin already exists:
114    /// - Its configuration is updated to the specified configuration, if different.
115    /// - Its configuration is unchanged, if the specified configuration is same.
116    pub async fn ensure_basin(
117        &self,
118        input: EnsureBasinInput,
119    ) -> Result<EnsureOutput<BasinInfo>, RequestError> {
120        let (name, request) = input.into();
121        Ok(self
122            .client
123            .ensure_basin(name, request)
124            .await?
125            .try_map(BasinInfo::try_from)?
126            .into())
127    }
128
129    /// Get basin configuration.
130    pub async fn get_basin_config(&self, name: BasinName) -> Result<BasinConfig, RequestError> {
131        let config = self.client.get_basin_config(name).await?;
132        Ok(config.into())
133    }
134
135    #[doc(hidden)]
136    #[cfg(feature = "_hidden")]
137    pub async fn get_basin_config_api(
138        &self,
139        name: BasinName,
140    ) -> Result<s2_api::v1::config::BasinConfig, RequestError> {
141        Ok(self.client.get_basin_config(name).await?)
142    }
143
144    /// Delete a basin.
145    pub async fn delete_basin(&self, input: DeleteBasinInput) -> Result<(), RequestError> {
146        Ok(self
147            .client
148            .delete_basin(input.name, input.ignore_not_found)
149            .await?)
150    }
151
152    /// Reconfigure a basin.
153    pub async fn reconfigure_basin(
154        &self,
155        input: ReconfigureBasinInput,
156    ) -> Result<BasinConfig, RequestError> {
157        let config = self
158            .client
159            .reconfigure_basin(input.name, input.config.into())
160            .await?;
161        Ok(config.into())
162    }
163
164    /// List a page of access tokens.
165    ///
166    /// See [`list_all_access_tokens`](crate::S2::list_all_access_tokens) for automatic pagination.
167    pub async fn list_access_tokens(
168        &self,
169        input: ListAccessTokensInput,
170    ) -> Result<Page<AccessTokenInfo>, RequestError> {
171        let response = self.client.list_access_tokens(input.into()).await?;
172        Ok(Page::new(
173            response
174                .access_tokens
175                .into_iter()
176                .map(TryInto::try_into)
177                .collect::<Result<Vec<_>, _>>()?,
178            response.has_more,
179        ))
180    }
181
182    #[doc(hidden)]
183    #[cfg(feature = "_hidden")]
184    pub async fn list_access_tokens_api(
185        &self,
186        input: ListAccessTokensInput,
187    ) -> Result<s2_api::v1::access::ListAccessTokensResponse, RequestError> {
188        Ok(self.client.list_access_tokens(input.into()).await?)
189    }
190
191    /// List all access tokens, paginating automatically.
192    pub fn list_all_access_tokens(
193        &self,
194        input: ListAllAccessTokensInput,
195    ) -> Streaming<AccessTokenInfo> {
196        let s2 = self.clone();
197        let prefix = input.prefix;
198        let start_after = input.start_after;
199        let mut input = ListAccessTokensInput::new()
200            .with_prefix(prefix)
201            .with_start_after(start_after);
202        Box::pin(async_stream::try_stream! {
203            loop {
204                let page = s2.list_access_tokens(input.clone()).await?;
205
206                let start_after = page.values.last().map(|info| info.id.clone().into());
207                for info in page.values {
208                    yield info;
209                }
210
211                if page.has_more && let Some(start_after) = start_after {
212                    input = input.with_start_after(start_after);
213                } else {
214                    break;
215                }
216            }
217        })
218    }
219
220    /// Issue an access token.
221    pub async fn issue_access_token(
222        &self,
223        input: IssueAccessTokenInput,
224    ) -> Result<String, RequestError> {
225        let response = self.client.issue_access_token(input.into()).await?;
226        Ok(response.access_token)
227    }
228
229    /// Revoke an access token.
230    pub async fn revoke_access_token(&self, id: AccessTokenId) -> Result<(), RequestError> {
231        Ok(self.client.revoke_access_token(id).await?)
232    }
233
234    /// List locations.
235    pub async fn list_locations(&self) -> Result<Vec<LocationInfo>, RequestError> {
236        let response = self.client.list_locations().await?;
237        Ok(response.into_iter().map(Into::into).collect())
238    }
239
240    /// Get the default location.
241    pub async fn get_default_location(&self) -> Result<LocationInfo, RequestError> {
242        Ok(self.client.get_default_location().await?.into())
243    }
244
245    /// Set the default location.
246    pub async fn set_default_location(
247        &self,
248        location: LocationName,
249    ) -> Result<LocationInfo, RequestError> {
250        Ok(self.client.set_default_location(location).await?.into())
251    }
252
253    /// Get account metrics.
254    pub async fn get_account_metrics(
255        &self,
256        input: GetAccountMetricsInput,
257    ) -> Result<Vec<Metric>, RequestError> {
258        let response = self.client.get_account_metrics(input.into()).await?;
259        Ok(response.values.into_iter().map(Into::into).collect())
260    }
261
262    /// Get basin metrics.
263    pub async fn get_basin_metrics(
264        &self,
265        input: GetBasinMetricsInput,
266    ) -> Result<Vec<Metric>, RequestError> {
267        let (name, request) = input.into();
268        let response = self.client.get_basin_metrics(name, request).await?;
269        Ok(response.values.into_iter().map(Into::into).collect())
270    }
271
272    /// Get stream metrics.
273    pub async fn get_stream_metrics(
274        &self,
275        input: GetStreamMetricsInput,
276    ) -> Result<Vec<Metric>, RequestError> {
277        let (basin_name, stream_name, request) = input.into();
278        let response = self
279            .client
280            .get_stream_metrics(basin_name, stream_name, request)
281            .await?;
282        Ok(response.values.into_iter().map(Into::into).collect())
283    }
284}
285
286#[derive(Debug, Clone)]
287/// A basin in an S2 account.
288///
289/// See [`S2::basin`].
290pub struct S2Basin {
291    client: BasinClient,
292}
293
294impl S2Basin {
295    /// Get an [`S2Stream`].
296    pub fn stream(&self, name: StreamName) -> S2Stream {
297        S2Stream {
298            client: self.client.clone(),
299            name,
300            encryption: None,
301        }
302    }
303
304    /// List a page of streams.
305    ///
306    /// See [`list_all_streams`](crate::S2Basin::list_all_streams) for automatic pagination.
307    pub async fn list_streams(
308        &self,
309        input: ListStreamsInput,
310    ) -> Result<Page<StreamInfo>, RequestError> {
311        let response = self.client.list_streams(input.into()).await?;
312        Ok(Page::new(
313            response
314                .streams
315                .into_iter()
316                .map(TryInto::try_into)
317                .collect::<Result<Vec<_>, _>>()?,
318            response.has_more,
319        ))
320    }
321
322    /// List all streams, paginating automatically.
323    pub fn list_all_streams(&self, input: ListAllStreamsInput) -> Streaming<StreamInfo> {
324        let basin = self.clone();
325        let prefix = input.prefix;
326        let start_after = input.start_after;
327        let include_deleted = input.include_deleted;
328        let mut input = ListStreamsInput::new()
329            .with_prefix(prefix)
330            .with_start_after(start_after);
331        Box::pin(async_stream::try_stream! {
332            loop {
333                let page = basin.list_streams(input.clone()).await?;
334                let start_after = page.values.last().map(|info| info.name.clone().into());
335
336                for info in page.values {
337                    if !include_deleted && info.deleted_at.is_some() {
338                        continue;
339                    }
340                    yield info;
341                }
342
343                if page.has_more && let Some(start_after) = start_after {
344                    input = input.with_start_after(start_after);
345                } else {
346                    break;
347                }
348            }
349        })
350    }
351
352    /// Create a stream.
353    pub async fn create_stream(
354        &self,
355        input: CreateStreamInput,
356    ) -> Result<StreamInfo, RequestError> {
357        let (request, idempotency_token) = input.into();
358        let info = self
359            .client
360            .create_stream(request, idempotency_token)
361            .await?;
362        Ok(info.try_into()?)
363    }
364
365    /// Ensure a stream.
366    ///
367    /// If the stream doesn't exist, creates the stream with specified configuration.
368    ///
369    /// If the stream already exists:
370    /// - Its configuration is updated to the specified configuration, if different.
371    /// - Its configuration is unchanged, if the specified configuration is same.
372    pub async fn ensure_stream(
373        &self,
374        input: EnsureStreamInput,
375    ) -> Result<EnsureOutput<StreamInfo>, RequestError> {
376        let (name, config) = input.into();
377        Ok(self
378            .client
379            .ensure_stream(name, config)
380            .await?
381            .try_map(StreamInfo::try_from)?
382            .into())
383    }
384
385    /// Get stream configuration.
386    pub async fn get_stream_config(&self, name: StreamName) -> Result<StreamConfig, RequestError> {
387        let config = self.client.get_stream_config(name).await?;
388        Ok(config.into())
389    }
390
391    #[doc(hidden)]
392    #[cfg(feature = "_hidden")]
393    pub async fn get_stream_config_api(
394        &self,
395        name: StreamName,
396    ) -> Result<s2_api::v1::config::StreamConfig, RequestError> {
397        Ok(self.client.get_stream_config(name).await?)
398    }
399
400    /// Delete a stream.
401    pub async fn delete_stream(&self, input: DeleteStreamInput) -> Result<(), RequestError> {
402        Ok(self
403            .client
404            .delete_stream(input.name, input.ignore_not_found)
405            .await?)
406    }
407
408    /// Reconfigure a stream.
409    pub async fn reconfigure_stream(
410        &self,
411        input: ReconfigureStreamInput,
412    ) -> Result<StreamConfig, RequestError> {
413        let config = self
414            .client
415            .reconfigure_stream(input.name, input.config.into())
416            .await?;
417        Ok(config.into())
418    }
419}
420
421#[derive(Debug, Clone)]
422/// A stream in an S2 basin.
423///
424/// See [`S2Basin::stream`].
425pub struct S2Stream {
426    client: BasinClient,
427    name: StreamName,
428    encryption: Option<EncryptionKey>,
429}
430
431impl S2Stream {
432    /// Set the encryption key for this stream handle.
433    pub fn with_encryption_key(self, encryption: EncryptionKey) -> Self {
434        Self {
435            encryption: Some(encryption),
436            ..self
437        }
438    }
439
440    /// Check tail position.
441    pub async fn check_tail(&self) -> Result<StreamPosition, ReadError> {
442        let response = self.client.check_tail(&self.name).await?;
443        Ok(response.tail.into())
444    }
445
446    /// Append records.
447    pub async fn append(&self, input: AppendInput) -> Result<AppendAck, AppendError> {
448        let ack = self
449            .client
450            .append(
451                &self.name,
452                input.into(),
453                self.encryption.as_ref(),
454                self.client.config.retry.append_retry_policy,
455            )
456            .await?;
457        Ok(ack.into())
458    }
459
460    /// Read records.
461    pub async fn read(&self, input: ReadInput) -> Result<ReadBatch, ReadError> {
462        let batch = self
463            .client
464            .read(
465                &self.name,
466                input.start.into(),
467                input.stop.into(),
468                self.encryption.as_ref(),
469            )
470            .await?;
471        let mut batch = ReadBatch::from_api(batch);
472        if input.ignore_command_records {
473            batch.records.retain(|r| !r.is_command_record());
474        }
475        Ok(batch)
476    }
477
478    /// Create an append session for submitting [`AppendInput`]s.
479    pub fn append_session(&self, config: AppendSessionConfig) -> AppendSession {
480        AppendSession::new(
481            self.client.clone(),
482            self.name.clone(),
483            self.encryption.clone(),
484            config,
485        )
486    }
487
488    /// Create a producer for submitting individual [`AppendRecord`](crate::types::AppendRecord)s.
489    pub fn producer(&self, config: ProducerConfig) -> Producer {
490        Producer::new(
491            self.client.clone(),
492            self.name.clone(),
493            self.encryption.clone(),
494            config,
495        )
496    }
497
498    /// Create a read session.
499    pub async fn read_session(
500        &self,
501        input: ReadInput,
502        config: ReadSessionConfig,
503    ) -> Result<ReadSession, ReadSessionError> {
504        session::read_session(
505            self.client.clone(),
506            self.name.clone(),
507            self.encryption.clone(),
508            input,
509            config,
510        )
511        .await
512    }
513}