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