Skip to main content

s2_sdk/
ops.rs

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