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    /// Delete a basin.
132    pub async fn delete_basin(&self, input: DeleteBasinInput) -> Result<(), S2Error> {
133        Ok(self
134            .client
135            .delete_basin(input.name, input.ignore_not_found)
136            .await?)
137    }
138
139    /// Reconfigure a basin.
140    pub async fn reconfigure_basin(
141        &self,
142        input: ReconfigureBasinInput,
143    ) -> Result<BasinConfig, S2Error> {
144        let config = self
145            .client
146            .reconfigure_basin(input.name, input.config.into())
147            .await?;
148        Ok(config.into())
149    }
150
151    /// List a page of access tokens.
152    ///
153    /// See [`list_all_access_tokens`](crate::S2::list_all_access_tokens) for automatic pagination.
154    pub async fn list_access_tokens(
155        &self,
156        input: ListAccessTokensInput,
157    ) -> Result<Page<AccessTokenInfo>, S2Error> {
158        let response = self.client.list_access_tokens(input.into()).await?;
159        Ok(Page::new(
160            response
161                .access_tokens
162                .into_iter()
163                .map(TryInto::try_into)
164                .collect::<Result<Vec<_>, _>>()?,
165            response.has_more,
166        ))
167    }
168
169    /// List all access tokens, paginating automatically.
170    pub fn list_all_access_tokens(
171        &self,
172        input: ListAllAccessTokensInput,
173    ) -> Streaming<AccessTokenInfo> {
174        let s2 = self.clone();
175        let prefix = input.prefix;
176        let start_after = input.start_after;
177        let mut input = ListAccessTokensInput::new()
178            .with_prefix(prefix)
179            .with_start_after(start_after);
180        Box::pin(async_stream::try_stream! {
181            loop {
182                let page = s2.list_access_tokens(input.clone()).await?;
183
184                let start_after = page.values.last().map(|info| info.id.clone().into());
185                for info in page.values {
186                    yield info;
187                }
188
189                if page.has_more && let Some(start_after) = start_after {
190                    input = input.with_start_after(start_after);
191                } else {
192                    break;
193                }
194            }
195        })
196    }
197
198    /// Issue an access token.
199    pub async fn issue_access_token(
200        &self,
201        input: IssueAccessTokenInput,
202    ) -> Result<String, S2Error> {
203        let response = self.client.issue_access_token(input.into()).await?;
204        Ok(response.access_token)
205    }
206
207    /// Revoke an access token.
208    pub async fn revoke_access_token(&self, id: AccessTokenId) -> Result<(), S2Error> {
209        Ok(self.client.revoke_access_token(id).await?)
210    }
211
212    /// List locations.
213    pub async fn list_locations(&self) -> Result<Vec<LocationInfo>, S2Error> {
214        let response = self.client.list_locations().await?;
215        Ok(response.into_iter().map(Into::into).collect())
216    }
217
218    /// Get the default location.
219    pub async fn get_default_location(&self) -> Result<LocationInfo, S2Error> {
220        Ok(self.client.get_default_location().await?.into())
221    }
222
223    /// Set the default location.
224    pub async fn set_default_location(
225        &self,
226        location: LocationName,
227    ) -> Result<LocationInfo, S2Error> {
228        Ok(self.client.set_default_location(location).await?.into())
229    }
230
231    /// Get account metrics.
232    pub async fn get_account_metrics(
233        &self,
234        input: GetAccountMetricsInput,
235    ) -> Result<Vec<Metric>, S2Error> {
236        let response = self.client.get_account_metrics(input.into()).await?;
237        Ok(response.values.into_iter().map(Into::into).collect())
238    }
239
240    /// Get basin metrics.
241    pub async fn get_basin_metrics(
242        &self,
243        input: GetBasinMetricsInput,
244    ) -> Result<Vec<Metric>, S2Error> {
245        let (name, request) = input.into();
246        let response = self.client.get_basin_metrics(name, request).await?;
247        Ok(response.values.into_iter().map(Into::into).collect())
248    }
249
250    /// Get stream metrics.
251    pub async fn get_stream_metrics(
252        &self,
253        input: GetStreamMetricsInput,
254    ) -> Result<Vec<Metric>, S2Error> {
255        let (basin_name, stream_name, request) = input.into();
256        let response = self
257            .client
258            .get_stream_metrics(basin_name, stream_name, request)
259            .await?;
260        Ok(response.values.into_iter().map(Into::into).collect())
261    }
262}
263
264#[derive(Debug, Clone)]
265/// A basin in an S2 account.
266///
267/// See [`S2::basin`].
268pub struct S2Basin {
269    client: BasinClient,
270}
271
272impl S2Basin {
273    /// Get an [`S2Stream`].
274    pub fn stream(&self, name: StreamName) -> S2Stream {
275        S2Stream {
276            client: self.client.clone(),
277            name,
278            encryption: None,
279        }
280    }
281
282    /// List a page of streams.
283    ///
284    /// See [`list_all_streams`](crate::S2Basin::list_all_streams) for automatic pagination.
285    pub async fn list_streams(&self, input: ListStreamsInput) -> Result<Page<StreamInfo>, S2Error> {
286        let response = self.client.list_streams(input.into()).await?;
287        Ok(Page::new(
288            response
289                .streams
290                .into_iter()
291                .map(TryInto::try_into)
292                .collect::<Result<Vec<_>, _>>()?,
293            response.has_more,
294        ))
295    }
296
297    /// List all streams, paginating automatically.
298    pub fn list_all_streams(&self, input: ListAllStreamsInput) -> Streaming<StreamInfo> {
299        let basin = self.clone();
300        let prefix = input.prefix;
301        let start_after = input.start_after;
302        let include_deleted = input.include_deleted;
303        let mut input = ListStreamsInput::new()
304            .with_prefix(prefix)
305            .with_start_after(start_after);
306        Box::pin(async_stream::try_stream! {
307            loop {
308                let page = basin.list_streams(input.clone()).await?;
309                let start_after = page.values.last().map(|info| info.name.clone().into());
310
311                for info in page.values {
312                    if !include_deleted && info.deleted_at.is_some() {
313                        continue;
314                    }
315                    yield info;
316                }
317
318                if page.has_more && let Some(start_after) = start_after {
319                    input = input.with_start_after(start_after);
320                } else {
321                    break;
322                }
323            }
324        })
325    }
326
327    /// Create a stream.
328    pub async fn create_stream(&self, input: CreateStreamInput) -> Result<StreamInfo, S2Error> {
329        let (request, idempotency_token) = input.into();
330        let info = self
331            .client
332            .create_stream(request, idempotency_token)
333            .await?;
334        Ok(info.try_into()?)
335    }
336
337    /// Ensure a stream.
338    ///
339    /// If the stream doesn't exist, creates the stream with specified configuration.
340    ///
341    /// If the stream already exists:
342    /// - Its configuration is updated to the specified configuration, if different.
343    /// - Its configuration is unchanged, if the specified configuration is same.
344    pub async fn ensure_stream(
345        &self,
346        input: EnsureStreamInput,
347    ) -> Result<EnsureOutput<StreamInfo>, S2Error> {
348        let (name, config) = input.into();
349        Ok(self
350            .client
351            .ensure_stream(name, config)
352            .await?
353            .try_map(StreamInfo::try_from)?
354            .into())
355    }
356
357    /// Get stream configuration.
358    pub async fn get_stream_config(&self, name: StreamName) -> Result<StreamConfig, S2Error> {
359        let config = self.client.get_stream_config(name).await?;
360        Ok(config.into())
361    }
362
363    /// Delete a stream.
364    pub async fn delete_stream(&self, input: DeleteStreamInput) -> Result<(), S2Error> {
365        Ok(self
366            .client
367            .delete_stream(input.name, input.ignore_not_found)
368            .await?)
369    }
370
371    /// Reconfigure a stream.
372    pub async fn reconfigure_stream(
373        &self,
374        input: ReconfigureStreamInput,
375    ) -> Result<StreamConfig, S2Error> {
376        let config = self
377            .client
378            .reconfigure_stream(input.name, input.config.into())
379            .await?;
380        Ok(config.into())
381    }
382}
383
384#[derive(Debug, Clone)]
385/// A stream in an S2 basin.
386///
387/// See [`S2Basin::stream`].
388pub struct S2Stream {
389    client: BasinClient,
390    name: StreamName,
391    encryption: Option<EncryptionKey>,
392}
393
394impl S2Stream {
395    /// Set the encryption key for this stream handle.
396    pub fn with_encryption_key(self, encryption: EncryptionKey) -> Self {
397        Self {
398            encryption: Some(encryption),
399            ..self
400        }
401    }
402
403    /// Check tail position.
404    pub async fn check_tail(&self) -> Result<StreamPosition, S2Error> {
405        let response = self.client.check_tail(&self.name).await?;
406        Ok(response.tail.into())
407    }
408
409    /// Append records.
410    pub async fn append(&self, input: AppendInput) -> Result<AppendAck, S2Error> {
411        let ack = self
412            .client
413            .append(
414                &self.name,
415                input.into(),
416                self.encryption.as_ref(),
417                self.client.config.retry.append_retry_policy,
418            )
419            .await?;
420        Ok(ack.into())
421    }
422
423    /// Read records.
424    pub async fn read(&self, input: ReadInput) -> Result<ReadBatch, S2Error> {
425        let batch = self
426            .client
427            .read(
428                &self.name,
429                input.start.into(),
430                input.stop.into(),
431                self.encryption.as_ref(),
432            )
433            .await?;
434        let mut batch = ReadBatch::from_api(batch);
435        if input.ignore_command_records {
436            batch.records.retain(|r| !r.is_command_record());
437        }
438        Ok(batch)
439    }
440
441    /// Create an append session for submitting [`AppendInput`]s.
442    pub fn append_session(&self, config: AppendSessionConfig) -> AppendSession {
443        AppendSession::new(
444            self.client.clone(),
445            self.name.clone(),
446            self.encryption.clone(),
447            config,
448        )
449    }
450
451    /// Create a producer for submitting individual [`AppendRecord`](crate::types::AppendRecord)s.
452    pub fn producer(&self, config: ProducerConfig) -> Producer {
453        Producer::new(
454            self.client.clone(),
455            self.name.clone(),
456            self.encryption.clone(),
457            config,
458        )
459    }
460
461    /// Create a read session.
462    pub async fn read_session(&self, input: ReadInput) -> Result<ReadSession, S2Error> {
463        Ok(session::read_session(
464            self.client.clone(),
465            self.name.clone(),
466            self.encryption.clone(),
467            input.start.into(),
468            input.stop.into(),
469            input.ignore_command_records,
470        )
471        .await?)
472    }
473}