1use std::{
4 collections::HashSet,
5 env::VarError,
6 fmt,
7 num::NonZeroU32,
8 ops::{Deref, RangeTo},
9 pin::Pin,
10 str::FromStr,
11 sync::Arc,
12 time::Duration,
13};
14
15#[cfg(feature = "_hidden")]
16use async_trait::async_trait;
17use bytes::Bytes;
18use http::{
19 HeaderMap,
20 header::HeaderValue,
21 uri::{Authority, Scheme},
22};
23use rand::RngExt;
24use s2_api::{v1 as api, v1::stream::s2s::CompressionAlgorithm};
25pub use s2_common::ValidationError;
27pub use s2_common::access::AccessTokenId;
32pub use s2_common::access::AccessTokenIdPrefix;
34pub use s2_common::access::AccessTokenIdStartAfter;
36pub use s2_common::basin::BasinName;
41pub use s2_common::basin::BasinNamePrefix;
43pub use s2_common::basin::BasinNameStartAfter;
45pub use s2_common::location::LocationName;
50pub use s2_common::stream::StreamName;
55pub use s2_common::stream::StreamNamePrefix;
57pub use s2_common::stream::StreamNameStartAfter;
59pub use s2_common::{
60 caps::RECORD_BATCH_MAX,
61 encryption::{EncryptionAlgorithm, EncryptionKey},
62};
63
64pub(crate) const ONE_MIB: u32 = 1024 * 1024;
65
66use s2_common::{
67 maybe::Maybe,
68 record::{MAX_FENCING_TOKEN_LENGTH, Metered, MeteredSize},
69 resources::ProvisionResult,
70};
71use secrecy::SecretString;
72
73use crate::error::RequestError;
74
75#[cfg(feature = "_hidden")]
76#[derive(Debug, Clone, thiserror::Error)]
77#[error("{message}")]
78#[doc(hidden)]
79pub struct AccessTokenProviderError {
80 message: String,
81 retryable: bool,
82}
83
84#[cfg(feature = "_hidden")]
85impl AccessTokenProviderError {
86 pub fn transient(message: impl Into<String>) -> Self {
88 Self {
89 message: message.into(),
90 retryable: true,
91 }
92 }
93
94 pub fn permanent(message: impl Into<String>) -> Self {
96 Self {
97 message: message.into(),
98 retryable: false,
99 }
100 }
101
102 pub(crate) fn is_retryable(&self) -> bool {
103 self.retryable
104 }
105}
106
107#[cfg(feature = "_hidden")]
108#[async_trait]
109#[doc(hidden)]
110pub trait AccessTokenProvider: fmt::Debug + Send + Sync {
111 async fn access_token(&self) -> Result<String, AccessTokenProviderError>;
113
114 fn invalidate_access_token(&self, _rejected_access_token: &str) {}
116}
117
118#[derive(Clone)]
119pub(crate) enum AccessToken {
120 Static(SecretString),
121 #[cfg(feature = "_hidden")]
122 Provider(Arc<dyn AccessTokenProvider>),
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub(crate) enum AccessTokenMode {
127 Static,
128 #[cfg(feature = "_hidden")]
129 Refreshable,
130}
131
132impl AccessTokenMode {
133 pub(crate) fn is_refreshable(self) -> bool {
134 match self {
135 Self::Static => false,
136 #[cfg(feature = "_hidden")]
137 Self::Refreshable => true,
138 }
139 }
140}
141
142impl AccessToken {
143 pub(crate) fn mode(&self) -> AccessTokenMode {
144 match self {
145 Self::Static(_) => AccessTokenMode::Static,
146 #[cfg(feature = "_hidden")]
147 Self::Provider(_) => AccessTokenMode::Refreshable,
148 }
149 }
150}
151
152impl fmt::Debug for AccessToken {
153 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
154 match self {
155 Self::Static(_) => formatter.write_str("Static(<redacted>)"),
156 #[cfg(feature = "_hidden")]
157 Self::Provider(_) => formatter.write_str("Provider(<redacted>)"),
158 }
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub struct S2DateTime(time::OffsetDateTime);
169
170impl TryFrom<time::OffsetDateTime> for S2DateTime {
171 type Error = ValidationError;
172
173 fn try_from(dt: time::OffsetDateTime) -> Result<Self, Self::Error> {
174 dt.format(&time::format_description::well_known::Rfc3339)
175 .map_err(|e| ValidationError(format!("not a valid RFC 3339 datetime: {e}")))?;
176 Ok(Self(dt))
177 }
178}
179
180impl From<S2DateTime> for time::OffsetDateTime {
181 fn from(dt: S2DateTime) -> Self {
182 dt.0
183 }
184}
185
186impl FromStr for S2DateTime {
187 type Err = ValidationError;
188
189 fn from_str(s: &str) -> Result<Self, Self::Err> {
190 time::OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339)
191 .map(Self)
192 .map_err(|e| ValidationError(format!("not a valid RFC 3339 datetime: {e}")))
193 }
194}
195
196impl fmt::Display for S2DateTime {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 write!(
199 f,
200 "{}",
201 self.0
202 .format(&time::format_description::well_known::Rfc3339)
203 .expect("RFC3339 formatting should not fail for S2DateTime")
204 )
205 }
206}
207
208#[derive(Debug, Clone, PartialEq)]
210pub(crate) enum BasinAuthority {
211 ParentZone(Authority),
213 Direct(Authority),
215}
216
217#[derive(Debug, Clone)]
219pub struct AccountEndpoint {
220 scheme: Scheme,
221 authority: Authority,
222}
223
224impl AccountEndpoint {
225 pub fn new(endpoint: &str) -> Result<Self, ValidationError> {
227 endpoint.parse()
228 }
229}
230
231impl FromStr for AccountEndpoint {
232 type Err = ValidationError;
233
234 fn from_str(s: &str) -> Result<Self, Self::Err> {
235 let (scheme, authority) = match s.find("://") {
236 Some(idx) => {
237 let scheme: Scheme = s[..idx]
238 .parse()
239 .map_err(|_| "invalid account endpoint scheme".to_string())?;
240 (scheme, &s[idx + 3..])
241 }
242 None => (Scheme::HTTPS, s),
243 };
244 Ok(Self {
245 scheme,
246 authority: authority
247 .parse()
248 .map_err(|e| format!("invalid account endpoint authority: {e}"))?,
249 })
250 }
251}
252
253#[derive(Debug, Clone)]
255pub struct BasinEndpoint {
256 scheme: Scheme,
257 authority: BasinAuthority,
258}
259
260impl BasinEndpoint {
261 pub fn new(endpoint: &str) -> Result<Self, ValidationError> {
263 endpoint.parse()
264 }
265}
266
267impl FromStr for BasinEndpoint {
268 type Err = ValidationError;
269
270 fn from_str(s: &str) -> Result<Self, Self::Err> {
271 let (scheme, authority) = match s.find("://") {
272 Some(idx) => {
273 let scheme: Scheme = s[..idx]
274 .parse()
275 .map_err(|_| "invalid basin endpoint scheme".to_string())?;
276 (scheme, &s[idx + 3..])
277 }
278 None => (Scheme::HTTPS, s),
279 };
280 let authority = if let Some(authority) = authority.strip_prefix("{basin}.") {
281 BasinAuthority::ParentZone(
282 authority
283 .parse()
284 .map_err(|e| format!("invalid basin endpoint authority: {e}"))?,
285 )
286 } else {
287 BasinAuthority::Direct(
288 authority
289 .parse()
290 .map_err(|e| format!("invalid basin endpoint authority: {e}"))?,
291 )
292 };
293 Ok(Self { scheme, authority })
294 }
295}
296
297#[derive(Debug, Clone)]
298#[non_exhaustive]
299pub struct S2Endpoints {
301 pub(crate) scheme: Scheme,
302 pub(crate) account_authority: Authority,
303 pub(crate) basin_authority: BasinAuthority,
304}
305
306impl S2Endpoints {
307 pub fn new(
309 account_endpoint: AccountEndpoint,
310 basin_endpoint: BasinEndpoint,
311 ) -> Result<Self, ValidationError> {
312 if account_endpoint.scheme != basin_endpoint.scheme {
313 return Err("account and basin endpoints must have the same scheme".into());
314 }
315 Ok(Self {
316 scheme: account_endpoint.scheme,
317 account_authority: account_endpoint.authority,
318 basin_authority: basin_endpoint.authority,
319 })
320 }
321
322 pub fn for_endpoint(endpoint: &str) -> Result<Self, ValidationError> {
326 Self::new(
327 AccountEndpoint::new(endpoint)?,
328 BasinEndpoint::new(endpoint)?,
329 )
330 }
331
332 pub fn from_env() -> Result<Self, ValidationError> {
338 let account_endpoint: AccountEndpoint = match std::env::var("S2_ACCOUNT_ENDPOINT") {
339 Ok(endpoint) => endpoint.parse()?,
340 Err(VarError::NotPresent) => return Err("S2_ACCOUNT_ENDPOINT env var not set".into()),
341 Err(VarError::NotUnicode(_)) => {
342 return Err("S2_ACCOUNT_ENDPOINT is not valid unicode".into());
343 }
344 };
345
346 let basin_endpoint: BasinEndpoint = match std::env::var("S2_BASIN_ENDPOINT") {
347 Ok(endpoint) => endpoint.parse()?,
348 Err(VarError::NotPresent) => return Err("S2_BASIN_ENDPOINT env var not set".into()),
349 Err(VarError::NotUnicode(_)) => {
350 return Err("S2_BASIN_ENDPOINT is not valid unicode".into());
351 }
352 };
353
354 if account_endpoint.scheme != basin_endpoint.scheme {
355 return Err(
356 "S2_ACCOUNT_ENDPOINT and S2_BASIN_ENDPOINT must have the same scheme".into(),
357 );
358 }
359
360 Ok(Self {
361 scheme: account_endpoint.scheme,
362 account_authority: account_endpoint.authority,
363 basin_authority: basin_endpoint.authority,
364 })
365 }
366
367 pub fn for_cloud() -> Self {
369 Self {
370 scheme: Scheme::HTTPS,
371 account_authority: "a.s2.dev".try_into().expect("valid authority"),
372 basin_authority: BasinAuthority::ParentZone(
373 "b.s2.dev".try_into().expect("valid authority"),
374 ),
375 }
376 }
377}
378
379#[derive(Debug, Clone, Copy)]
380pub enum Compression {
382 None,
384 Gzip,
386 Zstd,
388}
389
390impl From<Compression> for CompressionAlgorithm {
391 fn from(value: Compression) -> Self {
392 match value {
393 Compression::None => CompressionAlgorithm::None,
394 Compression::Gzip => CompressionAlgorithm::Gzip,
395 Compression::Zstd => CompressionAlgorithm::Zstd,
396 }
397 }
398}
399
400#[derive(Debug, Clone, Copy, PartialEq)]
401#[non_exhaustive]
402pub enum AppendRetryPolicy {
405 All,
407 NoSideEffects,
417}
418
419#[derive(Debug, Clone)]
420#[non_exhaustive]
421pub struct RetryConfig {
430 pub max_attempts: NonZeroU32,
434 pub min_base_delay: Duration,
438 pub max_base_delay: Duration,
442 pub append_retry_policy: AppendRetryPolicy,
447}
448
449impl Default for RetryConfig {
450 fn default() -> Self {
451 Self {
452 max_attempts: NonZeroU32::new(3).expect("valid non-zero u32"),
453 min_base_delay: Duration::from_millis(100),
454 max_base_delay: Duration::from_secs(1),
455 append_retry_policy: AppendRetryPolicy::All,
456 }
457 }
458}
459
460impl RetryConfig {
461 pub fn new() -> Self {
463 Self::default()
464 }
465
466 pub(crate) fn max_retries(&self) -> u32 {
467 self.max_attempts.get() - 1
468 }
469
470 pub fn with_max_attempts(self, max_attempts: NonZeroU32) -> Self {
472 Self {
473 max_attempts,
474 ..self
475 }
476 }
477
478 pub fn with_min_base_delay(self, min_base_delay: Duration) -> Self {
480 Self {
481 min_base_delay,
482 ..self
483 }
484 }
485
486 pub fn with_max_base_delay(self, max_base_delay: Duration) -> Self {
488 Self {
489 max_base_delay,
490 ..self
491 }
492 }
493
494 pub fn with_append_retry_policy(self, append_retry_policy: AppendRetryPolicy) -> Self {
497 Self {
498 append_retry_policy,
499 ..self
500 }
501 }
502}
503
504#[derive(Debug, Clone)]
505#[non_exhaustive]
506pub struct S2Config {
508 pub(crate) access_token: AccessToken,
509 pub(crate) endpoints: S2Endpoints,
510 pub(crate) connection_timeout: Duration,
511 pub(crate) request_timeout: Duration,
512 pub(crate) retry: RetryConfig,
513 pub(crate) compression: Compression,
514 pub(crate) user_agent: HeaderValue,
515 pub(crate) default_headers: HeaderMap,
516 pub(crate) insecure_skip_cert_verification: bool,
517 pub(crate) rustls_crypto_provider: Option<Arc<rustls::crypto::CryptoProvider>>,
518}
519
520impl S2Config {
521 pub fn new(access_token: impl Into<String>) -> Self {
523 Self {
524 access_token: AccessToken::Static(access_token.into().into()),
525 endpoints: S2Endpoints::for_cloud(),
526 connection_timeout: Duration::from_secs(3),
527 request_timeout: Duration::from_secs(5),
528 retry: RetryConfig::new(),
529 compression: Compression::None,
530 user_agent: concat!("s2-sdk-rust/", env!("CARGO_PKG_VERSION"))
531 .parse()
532 .expect("valid user agent"),
533 default_headers: HeaderMap::new(),
534 insecure_skip_cert_verification: false,
535 rustls_crypto_provider: default_rustls_crypto_provider(),
536 }
537 }
538
539 #[cfg(feature = "_hidden")]
540 #[doc(hidden)]
541 pub fn with_access_token_provider(self, provider: impl AccessTokenProvider + 'static) -> Self {
542 Self {
543 access_token: AccessToken::Provider(Arc::new(provider)),
544 ..self
545 }
546 }
547
548 pub fn with_endpoints(self, endpoints: S2Endpoints) -> Self {
550 Self { endpoints, ..self }
551 }
552
553 #[cfg(feature = "_hidden")]
576 #[doc(hidden)]
577 pub fn with_default_headers(self, default_headers: HeaderMap) -> Result<Self, ValidationError> {
578 if default_headers.contains_key(http::header::CONTENT_ENCODING) {
579 return Err(ValidationError(
580 "Content-Encoding cannot be set in default headers; use S2Config::with_compression instead"
581 .into(),
582 ));
583 }
584 for name in [
585 http::header::CONTENT_TYPE,
586 http::header::CONTENT_LENGTH,
587 http::header::TRANSFER_ENCODING,
588 ] {
589 if default_headers.contains_key(&name) {
590 return Err(ValidationError(format!(
591 "{name} cannot be set in default headers; the SDK controls request format and body framing"
592 )));
593 }
594 }
595 Ok(Self {
596 default_headers,
597 ..self
598 })
599 }
600
601 pub fn with_connection_timeout(self, connection_timeout: Duration) -> Self {
605 Self {
606 connection_timeout,
607 ..self
608 }
609 }
610
611 pub fn with_request_timeout(self, request_timeout: Duration) -> Self {
615 Self {
616 request_timeout,
617 ..self
618 }
619 }
620
621 pub fn with_retry(self, retry: RetryConfig) -> Self {
625 Self { retry, ..self }
626 }
627
628 pub fn with_compression(self, compression: Compression) -> Self {
632 Self {
633 compression,
634 ..self
635 }
636 }
637
638 pub fn with_insecure_skip_cert_verification(self, skip: bool) -> Self {
650 Self {
651 insecure_skip_cert_verification: skip,
652 ..self
653 }
654 }
655
656 pub fn with_rustls_crypto_provider(
666 self,
667 provider: impl Into<Arc<rustls::crypto::CryptoProvider>>,
668 ) -> Self {
669 Self {
670 rustls_crypto_provider: Some(provider.into()),
671 ..self
672 }
673 }
674
675 #[cfg(feature = "rustls-aws-lc-rs")]
679 pub fn with_rustls_aws_lc_rs_crypto_provider(self) -> Self {
680 self.with_rustls_crypto_provider(rustls::crypto::aws_lc_rs::default_provider())
681 }
682
683 #[cfg(feature = "rustls-ring")]
687 pub fn with_rustls_ring_crypto_provider(self) -> Self {
688 self.with_rustls_crypto_provider(rustls::crypto::ring::default_provider())
689 }
690
691 #[doc(hidden)]
692 #[cfg(feature = "_hidden")]
693 pub fn with_user_agent(self, user_agent: impl Into<String>) -> Result<Self, ValidationError> {
694 let user_agent = user_agent
695 .into()
696 .parse()
697 .map_err(|e| ValidationError(format!("invalid user agent: {e}")))?;
698 Ok(Self { user_agent, ..self })
699 }
700}
701
702#[cfg(feature = "rustls-aws-lc-rs")]
703fn default_rustls_crypto_provider() -> Option<Arc<rustls::crypto::CryptoProvider>> {
704 Some(Arc::new(rustls::crypto::aws_lc_rs::default_provider()))
705}
706
707#[cfg(all(not(feature = "rustls-aws-lc-rs"), feature = "rustls-ring"))]
708fn default_rustls_crypto_provider() -> Option<Arc<rustls::crypto::CryptoProvider>> {
709 Some(Arc::new(rustls::crypto::ring::default_provider()))
710}
711
712#[cfg(all(not(feature = "rustls-aws-lc-rs"), not(feature = "rustls-ring")))]
713fn default_rustls_crypto_provider() -> Option<Arc<rustls::crypto::CryptoProvider>> {
714 None
715}
716
717#[derive(Debug, Default, Clone, PartialEq, Eq)]
718#[non_exhaustive]
719pub struct Page<T> {
721 pub values: Vec<T>,
723 pub has_more: bool,
725}
726
727impl<T> Page<T> {
728 pub(crate) fn new(values: impl Into<Vec<T>>, has_more: bool) -> Self {
729 Self {
730 values: values.into(),
731 has_more,
732 }
733 }
734}
735
736#[derive(Debug, Clone, Copy, PartialEq, Eq)]
737pub enum StorageClass {
739 Standard,
741 Express,
743}
744
745impl From<api::config::StorageClass> for StorageClass {
746 fn from(value: api::config::StorageClass) -> Self {
747 match value {
748 api::config::StorageClass::Standard => StorageClass::Standard,
749 api::config::StorageClass::Express => StorageClass::Express,
750 }
751 }
752}
753
754impl From<StorageClass> for api::config::StorageClass {
755 fn from(value: StorageClass) -> Self {
756 match value {
757 StorageClass::Standard => api::config::StorageClass::Standard,
758 StorageClass::Express => api::config::StorageClass::Express,
759 }
760 }
761}
762
763#[derive(Debug, Clone, Copy, PartialEq, Eq)]
764pub enum RetentionPolicy {
766 Age(u64),
768 Infinite,
770}
771
772impl From<api::config::RetentionPolicy> for RetentionPolicy {
773 fn from(value: api::config::RetentionPolicy) -> Self {
774 match value {
775 api::config::RetentionPolicy::Age(secs) => RetentionPolicy::Age(secs),
776 api::config::RetentionPolicy::Infinite(_) => RetentionPolicy::Infinite,
777 }
778 }
779}
780
781impl From<RetentionPolicy> for api::config::RetentionPolicy {
782 fn from(value: RetentionPolicy) -> Self {
783 match value {
784 RetentionPolicy::Age(secs) => api::config::RetentionPolicy::Age(secs),
785 RetentionPolicy::Infinite => {
786 api::config::RetentionPolicy::Infinite(api::config::InfiniteRetention {})
787 }
788 }
789 }
790}
791
792#[derive(Debug, Clone, Copy, PartialEq, Eq)]
793pub enum TimestampingMode {
795 ClientPrefer,
797 ClientRequire,
799 Arrival,
801}
802
803impl From<api::config::TimestampingMode> for TimestampingMode {
804 fn from(value: api::config::TimestampingMode) -> Self {
805 match value {
806 api::config::TimestampingMode::ClientPrefer => TimestampingMode::ClientPrefer,
807 api::config::TimestampingMode::ClientRequire => TimestampingMode::ClientRequire,
808 api::config::TimestampingMode::Arrival => TimestampingMode::Arrival,
809 }
810 }
811}
812
813impl From<TimestampingMode> for api::config::TimestampingMode {
814 fn from(value: TimestampingMode) -> Self {
815 match value {
816 TimestampingMode::ClientPrefer => api::config::TimestampingMode::ClientPrefer,
817 TimestampingMode::ClientRequire => api::config::TimestampingMode::ClientRequire,
818 TimestampingMode::Arrival => api::config::TimestampingMode::Arrival,
819 }
820 }
821}
822
823#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
824#[non_exhaustive]
825pub struct TimestampingConfig {
827 pub mode: Option<TimestampingMode>,
831 pub uncapped: Option<bool>,
835}
836
837impl TimestampingConfig {
838 pub fn new() -> Self {
840 Self::default()
841 }
842
843 pub fn with_mode(self, mode: TimestampingMode) -> Self {
845 Self {
846 mode: Some(mode),
847 ..self
848 }
849 }
850
851 pub fn with_uncapped(self, uncapped: bool) -> Self {
853 Self {
854 uncapped: Some(uncapped),
855 ..self
856 }
857 }
858}
859
860impl From<api::config::TimestampingConfig> for TimestampingConfig {
861 fn from(value: api::config::TimestampingConfig) -> Self {
862 Self {
863 mode: value.mode.map(Into::into),
864 uncapped: value.uncapped,
865 }
866 }
867}
868
869impl From<TimestampingConfig> for api::config::TimestampingConfig {
870 fn from(value: TimestampingConfig) -> Self {
871 Self {
872 mode: value.mode.map(Into::into),
873 uncapped: value.uncapped,
874 }
875 }
876}
877
878#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
879#[non_exhaustive]
880pub struct DeleteOnEmptyConfig {
882 pub min_age_secs: u64,
886}
887
888impl DeleteOnEmptyConfig {
889 pub fn new() -> Self {
891 Self::default()
892 }
893
894 pub fn with_min_age(self, min_age: Duration) -> Self {
896 Self {
897 min_age_secs: min_age.as_secs(),
898 }
899 }
900}
901
902impl From<api::config::DeleteOnEmptyConfig> for DeleteOnEmptyConfig {
903 fn from(value: api::config::DeleteOnEmptyConfig) -> Self {
904 Self {
905 min_age_secs: value.min_age_secs,
906 }
907 }
908}
909
910impl From<DeleteOnEmptyConfig> for api::config::DeleteOnEmptyConfig {
911 fn from(value: DeleteOnEmptyConfig) -> Self {
912 Self {
913 min_age_secs: value.min_age_secs,
914 }
915 }
916}
917
918#[derive(Debug, Clone, Default, PartialEq, Eq)]
919#[non_exhaustive]
920pub struct StreamConfig {
922 pub storage_class: Option<StorageClass>,
926 pub retention_policy: Option<RetentionPolicy>,
930 pub timestamping: Option<TimestampingConfig>,
934 pub delete_on_empty: Option<DeleteOnEmptyConfig>,
938}
939
940impl StreamConfig {
941 pub fn new() -> Self {
943 Self::default()
944 }
945
946 pub fn with_storage_class(self, storage_class: StorageClass) -> Self {
948 Self {
949 storage_class: Some(storage_class),
950 ..self
951 }
952 }
953
954 pub fn with_retention_policy(self, retention_policy: RetentionPolicy) -> Self {
956 Self {
957 retention_policy: Some(retention_policy),
958 ..self
959 }
960 }
961
962 pub fn with_timestamping(self, timestamping: TimestampingConfig) -> Self {
964 Self {
965 timestamping: Some(timestamping),
966 ..self
967 }
968 }
969
970 pub fn with_delete_on_empty(self, delete_on_empty: DeleteOnEmptyConfig) -> Self {
972 Self {
973 delete_on_empty: Some(delete_on_empty),
974 ..self
975 }
976 }
977}
978
979impl From<api::config::StreamConfig> for StreamConfig {
980 fn from(value: api::config::StreamConfig) -> Self {
981 Self {
982 storage_class: value.storage_class.map(Into::into),
983 retention_policy: value.retention_policy.map(Into::into),
984 timestamping: value.timestamping.map(Into::into),
985 delete_on_empty: value.delete_on_empty.map(Into::into),
986 }
987 }
988}
989
990impl From<StreamConfig> for api::config::StreamConfig {
991 fn from(value: StreamConfig) -> Self {
992 Self {
993 storage_class: value.storage_class.map(Into::into),
994 retention_policy: value.retention_policy.map(Into::into),
995 timestamping: value.timestamping.map(Into::into),
996 delete_on_empty: value.delete_on_empty.map(Into::into),
997 }
998 }
999}
1000
1001#[derive(Debug, Clone, Default, PartialEq, Eq)]
1002#[non_exhaustive]
1003pub struct BasinConfig {
1005 pub default_stream_config: Option<StreamConfig>,
1009 pub stream_cipher: Option<EncryptionAlgorithm>,
1011 pub create_stream_on_append: bool,
1015 pub create_stream_on_read: bool,
1019}
1020
1021impl BasinConfig {
1022 pub fn new() -> Self {
1024 Self::default()
1025 }
1026
1027 pub fn with_default_stream_config(self, config: StreamConfig) -> Self {
1029 Self {
1030 default_stream_config: Some(config),
1031 ..self
1032 }
1033 }
1034
1035 pub fn with_stream_cipher(self, stream_cipher: EncryptionAlgorithm) -> Self {
1037 Self {
1038 stream_cipher: Some(stream_cipher),
1039 ..self
1040 }
1041 }
1042
1043 pub fn with_create_stream_on_append(self, create_stream_on_append: bool) -> Self {
1046 Self {
1047 create_stream_on_append,
1048 ..self
1049 }
1050 }
1051
1052 pub fn with_create_stream_on_read(self, create_stream_on_read: bool) -> Self {
1054 Self {
1055 create_stream_on_read,
1056 ..self
1057 }
1058 }
1059}
1060
1061impl From<api::config::BasinConfig> for BasinConfig {
1062 fn from(value: api::config::BasinConfig) -> Self {
1063 Self {
1064 default_stream_config: value.default_stream_config.map(Into::into),
1065 stream_cipher: value.stream_cipher.map(Into::into),
1066 create_stream_on_append: value.create_stream_on_append,
1067 create_stream_on_read: value.create_stream_on_read,
1068 }
1069 }
1070}
1071
1072impl From<BasinConfig> for api::config::BasinConfig {
1073 fn from(value: BasinConfig) -> Self {
1074 Self {
1075 default_stream_config: value.default_stream_config.map(Into::into),
1076 stream_cipher: value.stream_cipher.map(Into::into),
1077 create_stream_on_append: value.create_stream_on_append,
1078 create_stream_on_read: value.create_stream_on_read,
1079 }
1080 }
1081}
1082
1083#[derive(Debug, Clone)]
1084#[non_exhaustive]
1085pub struct CreateBasinInput {
1087 pub name: BasinName,
1089 pub config: Option<BasinConfig>,
1093 pub location: Option<LocationName>,
1097 idempotency_token: String,
1098}
1099
1100impl CreateBasinInput {
1101 pub fn new(name: BasinName) -> Self {
1103 Self {
1104 name,
1105 config: None,
1106 location: None,
1107 idempotency_token: idempotency_token(),
1108 }
1109 }
1110
1111 pub fn with_config(self, config: BasinConfig) -> Self {
1113 Self {
1114 config: Some(config),
1115 ..self
1116 }
1117 }
1118
1119 pub fn with_location<S>(self, location: S) -> Result<Self, ValidationError>
1121 where
1122 S: TryInto<LocationName>,
1123 S::Error: fmt::Display,
1124 {
1125 let location = location
1126 .try_into()
1127 .map_err(|e| ValidationError(e.to_string()))?;
1128 Ok(Self {
1129 location: Some(location),
1130 ..self
1131 })
1132 }
1133}
1134
1135impl From<CreateBasinInput> for (api::basin::CreateBasinRequest, String) {
1136 fn from(value: CreateBasinInput) -> Self {
1137 (
1138 api::basin::CreateBasinRequest {
1139 basin: value.name,
1140 config: value.config.map(Into::into),
1141 location: value.location,
1142 },
1143 value.idempotency_token,
1144 )
1145 }
1146}
1147
1148#[derive(Debug, Clone)]
1149#[non_exhaustive]
1150pub struct EnsureBasinInput {
1152 pub name: BasinName,
1154 pub config: Option<BasinConfig>,
1158 pub location: Option<LocationName>,
1163}
1164
1165impl EnsureBasinInput {
1166 pub fn new(name: BasinName) -> Self {
1168 Self {
1169 name,
1170 config: None,
1171 location: None,
1172 }
1173 }
1174
1175 pub fn with_config(self, config: BasinConfig) -> Self {
1177 Self {
1178 config: Some(config),
1179 ..self
1180 }
1181 }
1182
1183 pub fn with_location<S>(self, location: S) -> Result<Self, ValidationError>
1185 where
1186 S: TryInto<LocationName>,
1187 S::Error: fmt::Display,
1188 {
1189 let location = location
1190 .try_into()
1191 .map_err(|e| ValidationError(e.to_string()))?;
1192 Ok(Self {
1193 location: Some(location),
1194 ..self
1195 })
1196 }
1197}
1198
1199impl From<EnsureBasinInput> for (BasinName, Option<api::basin::EnsureBasinRequest>) {
1200 fn from(value: EnsureBasinInput) -> Self {
1201 let config = value.config;
1202 let request = if config.is_some() || value.location.is_some() {
1203 Some(api::basin::EnsureBasinRequest {
1204 config: config.map(Into::into),
1205 location: value.location,
1206 })
1207 } else {
1208 None
1209 };
1210 (value.name, request)
1211 }
1212}
1213
1214#[derive(Debug, Clone)]
1215pub enum EnsureOutput<T> {
1218 Created(T),
1220 ConfigUpdated(T),
1222 ConfigUnchanged(T),
1224}
1225
1226impl<T> From<ProvisionResult<T>> for EnsureOutput<T> {
1227 fn from(result: ProvisionResult<T>) -> Self {
1228 match result {
1229 ProvisionResult::Created(info) => EnsureOutput::Created(info),
1230 ProvisionResult::Updated(info) => EnsureOutput::ConfigUpdated(info),
1231 ProvisionResult::Noop(info) => EnsureOutput::ConfigUnchanged(info),
1232 }
1233 }
1234}
1235
1236#[derive(Debug, Clone, Default)]
1237#[non_exhaustive]
1238pub struct ListBasinsInput {
1240 pub prefix: BasinNamePrefix,
1244 pub start_after: BasinNameStartAfter,
1248 pub limit: Option<usize>,
1252}
1253
1254impl ListBasinsInput {
1255 pub fn new() -> Self {
1257 Self::default()
1258 }
1259
1260 pub fn with_prefix(self, prefix: BasinNamePrefix) -> Self {
1262 Self { prefix, ..self }
1263 }
1264
1265 pub fn with_start_after(self, start_after: BasinNameStartAfter) -> Self {
1268 Self {
1269 start_after,
1270 ..self
1271 }
1272 }
1273
1274 pub fn with_limit(self, limit: usize) -> Self {
1276 Self {
1277 limit: Some(limit),
1278 ..self
1279 }
1280 }
1281}
1282
1283impl From<ListBasinsInput> for api::basin::ListBasinsRequest {
1284 fn from(value: ListBasinsInput) -> Self {
1285 Self {
1286 prefix: Some(value.prefix),
1287 start_after: Some(value.start_after),
1288 limit: value.limit,
1289 }
1290 }
1291}
1292
1293#[derive(Debug, Clone, Default)]
1294pub struct ListAllBasinsInput {
1296 pub prefix: BasinNamePrefix,
1300 pub start_after: BasinNameStartAfter,
1304 pub include_deleted: bool,
1308}
1309
1310impl ListAllBasinsInput {
1311 pub fn new() -> Self {
1313 Self::default()
1314 }
1315
1316 pub fn with_prefix(self, prefix: BasinNamePrefix) -> Self {
1318 Self { prefix, ..self }
1319 }
1320
1321 pub fn with_start_after(self, start_after: BasinNameStartAfter) -> Self {
1324 Self {
1325 start_after,
1326 ..self
1327 }
1328 }
1329
1330 pub fn with_include_deleted(self, include_deleted: bool) -> Self {
1332 Self {
1333 include_deleted,
1334 ..self
1335 }
1336 }
1337}
1338
1339#[derive(Debug, Clone, PartialEq, Eq)]
1340#[non_exhaustive]
1341pub struct BasinInfo {
1343 pub name: BasinName,
1345 pub location: Option<LocationName>,
1347 pub created_at: S2DateTime,
1349 pub deleted_at: Option<S2DateTime>,
1351}
1352
1353impl TryFrom<api::basin::BasinInfo> for BasinInfo {
1354 type Error = ValidationError;
1355
1356 fn try_from(value: api::basin::BasinInfo) -> Result<Self, Self::Error> {
1357 Ok(Self {
1358 name: value.name,
1359 location: value.location,
1360 created_at: value.created_at.try_into()?,
1361 deleted_at: value.deleted_at.map(S2DateTime::try_from).transpose()?,
1362 })
1363 }
1364}
1365
1366#[derive(Debug, Clone)]
1367#[non_exhaustive]
1368pub struct DeleteBasinInput {
1370 pub name: BasinName,
1372 pub ignore_not_found: bool,
1374}
1375
1376impl DeleteBasinInput {
1377 pub fn new(name: BasinName) -> Self {
1379 Self {
1380 name,
1381 ignore_not_found: false,
1382 }
1383 }
1384
1385 pub fn with_ignore_not_found(self, ignore_not_found: bool) -> Self {
1387 Self {
1388 ignore_not_found,
1389 ..self
1390 }
1391 }
1392}
1393
1394#[derive(Debug, Clone, Default)]
1395#[non_exhaustive]
1396pub struct TimestampingReconfiguration {
1398 pub mode: Maybe<Option<TimestampingMode>>,
1400 pub uncapped: Maybe<Option<bool>>,
1402}
1403
1404impl TimestampingReconfiguration {
1405 pub fn new() -> Self {
1407 Self::default()
1408 }
1409
1410 pub fn with_mode(self, mode: TimestampingMode) -> Self {
1412 Self {
1413 mode: Maybe::Specified(Some(mode)),
1414 ..self
1415 }
1416 }
1417
1418 pub fn with_uncapped(self, uncapped: bool) -> Self {
1420 Self {
1421 uncapped: Maybe::Specified(Some(uncapped)),
1422 ..self
1423 }
1424 }
1425}
1426
1427impl From<TimestampingReconfiguration> for api::config::TimestampingReconfiguration {
1428 fn from(value: TimestampingReconfiguration) -> Self {
1429 Self {
1430 mode: value.mode.map(|m| m.map(Into::into)),
1431 uncapped: value.uncapped,
1432 }
1433 }
1434}
1435
1436#[derive(Debug, Clone, Default)]
1437#[non_exhaustive]
1438pub struct DeleteOnEmptyReconfiguration {
1440 pub min_age_secs: Maybe<Option<u64>>,
1442}
1443
1444impl DeleteOnEmptyReconfiguration {
1445 pub fn new() -> Self {
1447 Self::default()
1448 }
1449
1450 pub fn with_min_age(self, min_age: Duration) -> Self {
1452 Self {
1453 min_age_secs: Maybe::Specified(Some(min_age.as_secs())),
1454 }
1455 }
1456}
1457
1458impl From<DeleteOnEmptyReconfiguration> for api::config::DeleteOnEmptyReconfiguration {
1459 fn from(value: DeleteOnEmptyReconfiguration) -> Self {
1460 Self {
1461 min_age_secs: value.min_age_secs,
1462 }
1463 }
1464}
1465
1466#[derive(Debug, Clone, Default)]
1467#[non_exhaustive]
1468pub struct StreamReconfiguration {
1470 pub storage_class: Maybe<Option<StorageClass>>,
1472 pub retention_policy: Maybe<Option<RetentionPolicy>>,
1474 pub timestamping: Maybe<Option<TimestampingReconfiguration>>,
1476 pub delete_on_empty: Maybe<Option<DeleteOnEmptyReconfiguration>>,
1478}
1479
1480impl StreamReconfiguration {
1481 pub fn new() -> Self {
1483 Self::default()
1484 }
1485
1486 pub fn with_storage_class(self, storage_class: StorageClass) -> Self {
1488 Self {
1489 storage_class: Maybe::Specified(Some(storage_class)),
1490 ..self
1491 }
1492 }
1493
1494 pub fn with_retention_policy(self, retention_policy: RetentionPolicy) -> Self {
1496 Self {
1497 retention_policy: Maybe::Specified(Some(retention_policy)),
1498 ..self
1499 }
1500 }
1501
1502 pub fn with_timestamping(self, timestamping: TimestampingReconfiguration) -> Self {
1504 Self {
1505 timestamping: Maybe::Specified(Some(timestamping)),
1506 ..self
1507 }
1508 }
1509
1510 pub fn with_delete_on_empty(self, delete_on_empty: DeleteOnEmptyReconfiguration) -> Self {
1512 Self {
1513 delete_on_empty: Maybe::Specified(Some(delete_on_empty)),
1514 ..self
1515 }
1516 }
1517}
1518
1519impl From<StreamReconfiguration> for api::config::StreamReconfiguration {
1520 fn from(value: StreamReconfiguration) -> Self {
1521 Self {
1522 storage_class: value.storage_class.map(|m| m.map(Into::into)),
1523 retention_policy: value.retention_policy.map(|m| m.map(Into::into)),
1524 timestamping: value.timestamping.map(|m| m.map(Into::into)),
1525 delete_on_empty: value.delete_on_empty.map(|m| m.map(Into::into)),
1526 }
1527 }
1528}
1529
1530#[derive(Debug, Clone, Default)]
1531#[non_exhaustive]
1532pub struct BasinReconfiguration {
1534 pub default_stream_config: Maybe<Option<StreamReconfiguration>>,
1536 pub stream_cipher: Maybe<Option<EncryptionAlgorithm>>,
1538 pub create_stream_on_append: Maybe<bool>,
1541 pub create_stream_on_read: Maybe<bool>,
1543}
1544
1545impl BasinReconfiguration {
1546 pub fn new() -> Self {
1548 Self::default()
1549 }
1550
1551 pub fn with_default_stream_config(self, config: StreamReconfiguration) -> Self {
1554 Self {
1555 default_stream_config: Maybe::Specified(Some(config)),
1556 ..self
1557 }
1558 }
1559
1560 pub fn with_stream_cipher(self, stream_cipher: EncryptionAlgorithm) -> Self {
1562 Self {
1563 stream_cipher: Maybe::Specified(Some(stream_cipher)),
1564 ..self
1565 }
1566 }
1567
1568 pub fn with_create_stream_on_append(self, create_stream_on_append: bool) -> Self {
1571 Self {
1572 create_stream_on_append: Maybe::Specified(create_stream_on_append),
1573 ..self
1574 }
1575 }
1576
1577 pub fn with_create_stream_on_read(self, create_stream_on_read: bool) -> Self {
1580 Self {
1581 create_stream_on_read: Maybe::Specified(create_stream_on_read),
1582 ..self
1583 }
1584 }
1585}
1586
1587impl From<BasinReconfiguration> for api::config::BasinReconfiguration {
1588 fn from(value: BasinReconfiguration) -> Self {
1589 Self {
1590 default_stream_config: value.default_stream_config.map(|m| m.map(Into::into)),
1591 stream_cipher: value.stream_cipher.map(|m| m.map(Into::into)),
1592 create_stream_on_append: value.create_stream_on_append,
1593 create_stream_on_read: value.create_stream_on_read,
1594 }
1595 }
1596}
1597
1598#[derive(Debug, Clone)]
1599#[non_exhaustive]
1600pub struct ReconfigureBasinInput {
1602 pub name: BasinName,
1604 pub config: BasinReconfiguration,
1606}
1607
1608impl ReconfigureBasinInput {
1609 pub fn new(name: BasinName, config: BasinReconfiguration) -> Self {
1611 Self { name, config }
1612 }
1613}
1614
1615#[derive(Debug, Clone, Default)]
1616#[non_exhaustive]
1617pub struct ListAccessTokensInput {
1619 pub prefix: AccessTokenIdPrefix,
1623 pub start_after: AccessTokenIdStartAfter,
1627 pub limit: Option<usize>,
1631}
1632
1633impl ListAccessTokensInput {
1634 pub fn new() -> Self {
1636 Self::default()
1637 }
1638
1639 pub fn with_prefix(self, prefix: AccessTokenIdPrefix) -> Self {
1641 Self { prefix, ..self }
1642 }
1643
1644 pub fn with_start_after(self, start_after: AccessTokenIdStartAfter) -> Self {
1647 Self {
1648 start_after,
1649 ..self
1650 }
1651 }
1652
1653 pub fn with_limit(self, limit: usize) -> Self {
1655 Self {
1656 limit: Some(limit),
1657 ..self
1658 }
1659 }
1660}
1661
1662impl From<ListAccessTokensInput> for api::access::ListAccessTokensRequest {
1663 fn from(value: ListAccessTokensInput) -> Self {
1664 Self {
1665 prefix: Some(value.prefix),
1666 start_after: Some(value.start_after),
1667 limit: value.limit,
1668 }
1669 }
1670}
1671
1672#[derive(Debug, Clone, Default)]
1673pub struct ListAllAccessTokensInput {
1675 pub prefix: AccessTokenIdPrefix,
1679 pub start_after: AccessTokenIdStartAfter,
1683}
1684
1685impl ListAllAccessTokensInput {
1686 pub fn new() -> Self {
1688 Self::default()
1689 }
1690
1691 pub fn with_prefix(self, prefix: AccessTokenIdPrefix) -> Self {
1693 Self { prefix, ..self }
1694 }
1695
1696 pub fn with_start_after(self, start_after: AccessTokenIdStartAfter) -> Self {
1699 Self {
1700 start_after,
1701 ..self
1702 }
1703 }
1704}
1705
1706#[derive(Debug, Clone, PartialEq, Eq)]
1707#[non_exhaustive]
1708pub struct LocationInfo {
1710 pub name: LocationName,
1712 pub is_private: bool,
1714}
1715
1716impl From<api::location::LocationInfo> for LocationInfo {
1717 fn from(value: api::location::LocationInfo) -> Self {
1718 Self {
1719 name: value.name,
1720 is_private: value.is_private,
1721 }
1722 }
1723}
1724
1725#[derive(Debug, Clone)]
1726#[non_exhaustive]
1727pub struct AccessTokenInfo {
1729 pub id: AccessTokenId,
1731 pub expires_at: Option<S2DateTime>,
1733 pub auto_prefix_streams: bool,
1736 pub scope: AccessTokenScope,
1738}
1739
1740impl TryFrom<api::access::AccessTokenInfo> for AccessTokenInfo {
1741 type Error = ValidationError;
1742
1743 fn try_from(value: api::access::AccessTokenInfo) -> Result<Self, Self::Error> {
1744 let expires_at = value.expires_at.map(S2DateTime::try_from).transpose()?;
1745 Ok(Self {
1746 id: value.id,
1747 expires_at,
1748 auto_prefix_streams: value.auto_prefix_streams,
1749 scope: value.scope.into(),
1750 })
1751 }
1752}
1753
1754#[derive(Debug, Clone)]
1755pub enum BasinMatcher {
1759 None,
1761 Exact(BasinName),
1763 Prefix(BasinNamePrefix),
1765}
1766
1767#[derive(Debug, Clone)]
1768pub enum StreamMatcher {
1772 None,
1774 Exact(StreamName),
1776 Prefix(StreamNamePrefix),
1778}
1779
1780#[derive(Debug, Clone)]
1781pub enum AccessTokenMatcher {
1785 None,
1787 Exact(AccessTokenId),
1789 Prefix(AccessTokenIdPrefix),
1791}
1792
1793#[derive(Debug, Clone, Default)]
1794#[non_exhaustive]
1795pub struct ReadWritePermissions {
1797 pub read: bool,
1801 pub write: bool,
1805}
1806
1807impl ReadWritePermissions {
1808 pub fn new() -> Self {
1810 Self::default()
1811 }
1812
1813 pub fn read_only() -> Self {
1815 Self {
1816 read: true,
1817 write: false,
1818 }
1819 }
1820
1821 pub fn write_only() -> Self {
1823 Self {
1824 read: false,
1825 write: true,
1826 }
1827 }
1828
1829 pub fn read_write() -> Self {
1831 Self {
1832 read: true,
1833 write: true,
1834 }
1835 }
1836}
1837
1838impl From<ReadWritePermissions> for api::access::ReadWritePermissions {
1839 fn from(value: ReadWritePermissions) -> Self {
1840 Self {
1841 read: Some(value.read),
1842 write: Some(value.write),
1843 }
1844 }
1845}
1846
1847impl From<api::access::ReadWritePermissions> for ReadWritePermissions {
1848 fn from(value: api::access::ReadWritePermissions) -> Self {
1849 Self {
1850 read: value.read.unwrap_or_default(),
1851 write: value.write.unwrap_or_default(),
1852 }
1853 }
1854}
1855
1856#[derive(Debug, Clone, Default)]
1857#[non_exhaustive]
1858pub struct OperationGroupPermissions {
1862 pub account: Option<ReadWritePermissions>,
1866 pub basin: Option<ReadWritePermissions>,
1870 pub stream: Option<ReadWritePermissions>,
1874}
1875
1876impl OperationGroupPermissions {
1877 pub fn new() -> Self {
1879 Self::default()
1880 }
1881
1882 pub fn read_only_all() -> Self {
1884 Self {
1885 account: Some(ReadWritePermissions::read_only()),
1886 basin: Some(ReadWritePermissions::read_only()),
1887 stream: Some(ReadWritePermissions::read_only()),
1888 }
1889 }
1890
1891 pub fn write_only_all() -> Self {
1893 Self {
1894 account: Some(ReadWritePermissions::write_only()),
1895 basin: Some(ReadWritePermissions::write_only()),
1896 stream: Some(ReadWritePermissions::write_only()),
1897 }
1898 }
1899
1900 pub fn read_write_all() -> Self {
1902 Self {
1903 account: Some(ReadWritePermissions::read_write()),
1904 basin: Some(ReadWritePermissions::read_write()),
1905 stream: Some(ReadWritePermissions::read_write()),
1906 }
1907 }
1908
1909 pub fn with_account(self, account: ReadWritePermissions) -> Self {
1911 Self {
1912 account: Some(account),
1913 ..self
1914 }
1915 }
1916
1917 pub fn with_basin(self, basin: ReadWritePermissions) -> Self {
1919 Self {
1920 basin: Some(basin),
1921 ..self
1922 }
1923 }
1924
1925 pub fn with_stream(self, stream: ReadWritePermissions) -> Self {
1927 Self {
1928 stream: Some(stream),
1929 ..self
1930 }
1931 }
1932}
1933
1934impl From<OperationGroupPermissions> for api::access::PermittedOperationGroups {
1935 fn from(value: OperationGroupPermissions) -> Self {
1936 Self {
1937 account: value.account.map(Into::into),
1938 basin: value.basin.map(Into::into),
1939 stream: value.stream.map(Into::into),
1940 }
1941 }
1942}
1943
1944impl From<api::access::PermittedOperationGroups> for OperationGroupPermissions {
1945 fn from(value: api::access::PermittedOperationGroups) -> Self {
1946 Self {
1947 account: value.account.map(Into::into),
1948 basin: value.basin.map(Into::into),
1949 stream: value.stream.map(Into::into),
1950 }
1951 }
1952}
1953
1954#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1955pub enum Operation {
1959 ListBasins,
1961 CreateBasin,
1963 GetBasinConfig,
1965 DeleteBasin,
1967 ReconfigureBasin,
1969 ListAccessTokens,
1971 IssueAccessToken,
1973 RevokeAccessToken,
1975 GetAccountMetrics,
1977 GetBasinMetrics,
1979 GetStreamMetrics,
1981 ListStreams,
1983 CreateStream,
1985 GetStreamConfig,
1987 DeleteStream,
1989 ReconfigureStream,
1991 CheckTail,
1993 Append,
1995 Read,
1997 Trim,
1999 Fence,
2001 ListLocations,
2003 GetDefaultLocation,
2005 SetDefaultLocation,
2007}
2008
2009impl From<Operation> for api::access::Operation {
2010 fn from(value: Operation) -> Self {
2011 match value {
2012 Operation::ListBasins => api::access::Operation::ListBasins,
2013 Operation::CreateBasin => api::access::Operation::CreateBasin,
2014 Operation::DeleteBasin => api::access::Operation::DeleteBasin,
2015 Operation::ReconfigureBasin => api::access::Operation::ReconfigureBasin,
2016 Operation::GetBasinConfig => api::access::Operation::GetBasinConfig,
2017 Operation::IssueAccessToken => api::access::Operation::IssueAccessToken,
2018 Operation::RevokeAccessToken => api::access::Operation::RevokeAccessToken,
2019 Operation::ListAccessTokens => api::access::Operation::ListAccessTokens,
2020 Operation::ListStreams => api::access::Operation::ListStreams,
2021 Operation::CreateStream => api::access::Operation::CreateStream,
2022 Operation::DeleteStream => api::access::Operation::DeleteStream,
2023 Operation::GetStreamConfig => api::access::Operation::GetStreamConfig,
2024 Operation::ReconfigureStream => api::access::Operation::ReconfigureStream,
2025 Operation::CheckTail => api::access::Operation::CheckTail,
2026 Operation::Append => api::access::Operation::Append,
2027 Operation::Read => api::access::Operation::Read,
2028 Operation::Trim => api::access::Operation::Trim,
2029 Operation::Fence => api::access::Operation::Fence,
2030 Operation::GetAccountMetrics => api::access::Operation::AccountMetrics,
2031 Operation::GetBasinMetrics => api::access::Operation::BasinMetrics,
2032 Operation::GetStreamMetrics => api::access::Operation::StreamMetrics,
2033 Operation::ListLocations => api::access::Operation::ListLocations,
2034 Operation::GetDefaultLocation => api::access::Operation::GetDefaultLocation,
2035 Operation::SetDefaultLocation => api::access::Operation::SetDefaultLocation,
2036 }
2037 }
2038}
2039
2040impl From<api::access::Operation> for Operation {
2041 fn from(value: api::access::Operation) -> Self {
2042 match value {
2043 api::access::Operation::ListBasins => Operation::ListBasins,
2044 api::access::Operation::CreateBasin => Operation::CreateBasin,
2045 api::access::Operation::DeleteBasin => Operation::DeleteBasin,
2046 api::access::Operation::ReconfigureBasin => Operation::ReconfigureBasin,
2047 api::access::Operation::GetBasinConfig => Operation::GetBasinConfig,
2048 api::access::Operation::IssueAccessToken => Operation::IssueAccessToken,
2049 api::access::Operation::RevokeAccessToken => Operation::RevokeAccessToken,
2050 api::access::Operation::ListAccessTokens => Operation::ListAccessTokens,
2051 api::access::Operation::ListStreams => Operation::ListStreams,
2052 api::access::Operation::CreateStream => Operation::CreateStream,
2053 api::access::Operation::DeleteStream => Operation::DeleteStream,
2054 api::access::Operation::GetStreamConfig => Operation::GetStreamConfig,
2055 api::access::Operation::ReconfigureStream => Operation::ReconfigureStream,
2056 api::access::Operation::CheckTail => Operation::CheckTail,
2057 api::access::Operation::Append => Operation::Append,
2058 api::access::Operation::Read => Operation::Read,
2059 api::access::Operation::Trim => Operation::Trim,
2060 api::access::Operation::Fence => Operation::Fence,
2061 api::access::Operation::AccountMetrics => Operation::GetAccountMetrics,
2062 api::access::Operation::BasinMetrics => Operation::GetBasinMetrics,
2063 api::access::Operation::StreamMetrics => Operation::GetStreamMetrics,
2064 api::access::Operation::ListLocations => Operation::ListLocations,
2065 api::access::Operation::GetDefaultLocation => Operation::GetDefaultLocation,
2066 api::access::Operation::SetDefaultLocation => Operation::SetDefaultLocation,
2067 }
2068 }
2069}
2070
2071#[derive(Debug, Clone)]
2072#[non_exhaustive]
2073pub struct AccessTokenScopeInput {
2081 basins: Option<BasinMatcher>,
2082 streams: Option<StreamMatcher>,
2083 access_tokens: Option<AccessTokenMatcher>,
2084 op_group_perms: Option<OperationGroupPermissions>,
2085 ops: HashSet<Operation>,
2086}
2087
2088impl AccessTokenScopeInput {
2089 pub fn from_ops(ops: impl IntoIterator<Item = Operation>) -> Self {
2091 Self {
2092 basins: None,
2093 streams: None,
2094 access_tokens: None,
2095 op_group_perms: None,
2096 ops: ops.into_iter().collect(),
2097 }
2098 }
2099
2100 pub fn from_op_group_perms(op_group_perms: OperationGroupPermissions) -> Self {
2102 Self {
2103 basins: None,
2104 streams: None,
2105 access_tokens: None,
2106 op_group_perms: Some(op_group_perms),
2107 ops: HashSet::default(),
2108 }
2109 }
2110
2111 pub fn with_ops(self, ops: impl IntoIterator<Item = Operation>) -> Self {
2113 Self {
2114 ops: ops.into_iter().collect(),
2115 ..self
2116 }
2117 }
2118
2119 pub fn with_op_group_perms(self, op_group_perms: OperationGroupPermissions) -> Self {
2121 Self {
2122 op_group_perms: Some(op_group_perms),
2123 ..self
2124 }
2125 }
2126
2127 pub fn with_basins(self, basins: BasinMatcher) -> Self {
2131 Self {
2132 basins: Some(basins),
2133 ..self
2134 }
2135 }
2136
2137 pub fn with_streams(self, streams: StreamMatcher) -> Self {
2141 Self {
2142 streams: Some(streams),
2143 ..self
2144 }
2145 }
2146
2147 pub fn with_access_tokens(self, access_tokens: AccessTokenMatcher) -> Self {
2151 Self {
2152 access_tokens: Some(access_tokens),
2153 ..self
2154 }
2155 }
2156}
2157
2158#[derive(Debug, Clone)]
2159#[non_exhaustive]
2160pub struct AccessTokenScope {
2162 pub basins: Option<BasinMatcher>,
2164 pub streams: Option<StreamMatcher>,
2166 pub access_tokens: Option<AccessTokenMatcher>,
2168 pub op_group_perms: Option<OperationGroupPermissions>,
2170 pub ops: HashSet<Operation>,
2172}
2173
2174impl From<api::access::AccessTokenScope> for AccessTokenScope {
2175 fn from(value: api::access::AccessTokenScope) -> Self {
2176 Self {
2177 basins: value.basins.map(|rs| match rs {
2178 api::access::ResourceSet::Exact(api::access::MaybeEmpty::NonEmpty(e)) => {
2179 BasinMatcher::Exact(e)
2180 }
2181 api::access::ResourceSet::Exact(api::access::MaybeEmpty::Empty) => {
2182 BasinMatcher::None
2183 }
2184 api::access::ResourceSet::Prefix(p) => BasinMatcher::Prefix(p),
2185 }),
2186 streams: value.streams.map(|rs| match rs {
2187 api::access::ResourceSet::Exact(api::access::MaybeEmpty::NonEmpty(e)) => {
2188 StreamMatcher::Exact(e)
2189 }
2190 api::access::ResourceSet::Exact(api::access::MaybeEmpty::Empty) => {
2191 StreamMatcher::None
2192 }
2193 api::access::ResourceSet::Prefix(p) => StreamMatcher::Prefix(p),
2194 }),
2195 access_tokens: value.access_tokens.map(|rs| match rs {
2196 api::access::ResourceSet::Exact(api::access::MaybeEmpty::NonEmpty(e)) => {
2197 AccessTokenMatcher::Exact(e)
2198 }
2199 api::access::ResourceSet::Exact(api::access::MaybeEmpty::Empty) => {
2200 AccessTokenMatcher::None
2201 }
2202 api::access::ResourceSet::Prefix(p) => AccessTokenMatcher::Prefix(p),
2203 }),
2204 op_group_perms: value.op_groups.map(Into::into),
2205 ops: value
2206 .ops
2207 .map(|ops| ops.into_iter().map(Into::into).collect())
2208 .unwrap_or_default(),
2209 }
2210 }
2211}
2212
2213impl From<AccessTokenScopeInput> for api::access::AccessTokenScope {
2214 fn from(value: AccessTokenScopeInput) -> Self {
2215 Self {
2216 basins: value.basins.map(|rs| match rs {
2217 BasinMatcher::None => {
2218 api::access::ResourceSet::Exact(api::access::MaybeEmpty::Empty)
2219 }
2220 BasinMatcher::Exact(e) => {
2221 api::access::ResourceSet::Exact(api::access::MaybeEmpty::NonEmpty(e))
2222 }
2223 BasinMatcher::Prefix(p) => api::access::ResourceSet::Prefix(p),
2224 }),
2225 streams: value.streams.map(|rs| match rs {
2226 StreamMatcher::None => {
2227 api::access::ResourceSet::Exact(api::access::MaybeEmpty::Empty)
2228 }
2229 StreamMatcher::Exact(e) => {
2230 api::access::ResourceSet::Exact(api::access::MaybeEmpty::NonEmpty(e))
2231 }
2232 StreamMatcher::Prefix(p) => api::access::ResourceSet::Prefix(p),
2233 }),
2234 access_tokens: value.access_tokens.map(|rs| match rs {
2235 AccessTokenMatcher::None => {
2236 api::access::ResourceSet::Exact(api::access::MaybeEmpty::Empty)
2237 }
2238 AccessTokenMatcher::Exact(e) => {
2239 api::access::ResourceSet::Exact(api::access::MaybeEmpty::NonEmpty(e))
2240 }
2241 AccessTokenMatcher::Prefix(p) => api::access::ResourceSet::Prefix(p),
2242 }),
2243 op_groups: value.op_group_perms.map(Into::into),
2244 ops: if value.ops.is_empty() {
2245 None
2246 } else {
2247 Some(value.ops.into_iter().map(Into::into).collect())
2248 },
2249 }
2250 }
2251}
2252
2253#[derive(Debug, Clone)]
2254#[non_exhaustive]
2255pub struct IssueAccessTokenInput {
2257 pub id: AccessTokenId,
2259 pub expires_at: Option<S2DateTime>,
2264 pub auto_prefix_streams: bool,
2272 pub scope: AccessTokenScopeInput,
2274}
2275
2276impl IssueAccessTokenInput {
2277 pub fn new(id: AccessTokenId, scope: AccessTokenScopeInput) -> Self {
2279 Self {
2280 id,
2281 expires_at: None,
2282 auto_prefix_streams: false,
2283 scope,
2284 }
2285 }
2286
2287 pub fn with_expires_at(self, expires_at: S2DateTime) -> Self {
2289 Self {
2290 expires_at: Some(expires_at),
2291 ..self
2292 }
2293 }
2294
2295 pub fn with_auto_prefix_streams(self, auto_prefix_streams: bool) -> Self {
2298 Self {
2299 auto_prefix_streams,
2300 ..self
2301 }
2302 }
2303}
2304
2305impl From<IssueAccessTokenInput> for api::access::IssueAccessTokenRequest {
2306 fn from(value: IssueAccessTokenInput) -> Self {
2307 Self {
2308 id: value.id,
2309 expires_at: value.expires_at.map(Into::into),
2310 auto_prefix_streams: value.auto_prefix_streams.then_some(true),
2311 scope: value.scope.into(),
2312 }
2313 }
2314}
2315
2316#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2317pub enum TimeseriesInterval {
2319 Minute,
2321 Hour,
2323 Day,
2325}
2326
2327impl From<TimeseriesInterval> for api::metrics::TimeseriesInterval {
2328 fn from(value: TimeseriesInterval) -> Self {
2329 match value {
2330 TimeseriesInterval::Minute => api::metrics::TimeseriesInterval::Minute,
2331 TimeseriesInterval::Hour => api::metrics::TimeseriesInterval::Hour,
2332 TimeseriesInterval::Day => api::metrics::TimeseriesInterval::Day,
2333 }
2334 }
2335}
2336
2337impl From<api::metrics::TimeseriesInterval> for TimeseriesInterval {
2338 fn from(value: api::metrics::TimeseriesInterval) -> Self {
2339 match value {
2340 api::metrics::TimeseriesInterval::Minute => TimeseriesInterval::Minute,
2341 api::metrics::TimeseriesInterval::Hour => TimeseriesInterval::Hour,
2342 api::metrics::TimeseriesInterval::Day => TimeseriesInterval::Day,
2343 }
2344 }
2345}
2346
2347#[derive(Debug, Clone, Copy)]
2348#[non_exhaustive]
2349pub struct TimeRange {
2351 pub start: u32,
2353 pub end: u32,
2355}
2356
2357impl TimeRange {
2358 pub fn new(start: u32, end: u32) -> Self {
2360 Self { start, end }
2361 }
2362}
2363
2364#[derive(Debug, Clone, Copy)]
2365#[non_exhaustive]
2366pub struct TimeRangeAndInterval {
2368 pub start: u32,
2370 pub end: u32,
2372 pub interval: Option<TimeseriesInterval>,
2376}
2377
2378impl TimeRangeAndInterval {
2379 pub fn new(start: u32, end: u32) -> Self {
2381 Self {
2382 start,
2383 end,
2384 interval: None,
2385 }
2386 }
2387
2388 pub fn with_interval(self, interval: TimeseriesInterval) -> Self {
2390 Self {
2391 interval: Some(interval),
2392 ..self
2393 }
2394 }
2395}
2396
2397#[derive(Debug, Clone, Copy)]
2398pub enum AccountMetricSet {
2400 ActiveBasins(TimeRange),
2403 AccountOps(TimeRangeAndInterval),
2410}
2411
2412#[derive(Debug, Clone)]
2413#[non_exhaustive]
2414pub struct GetAccountMetricsInput {
2416 pub set: AccountMetricSet,
2418}
2419
2420impl GetAccountMetricsInput {
2421 pub fn new(set: AccountMetricSet) -> Self {
2423 Self { set }
2424 }
2425}
2426
2427impl From<GetAccountMetricsInput> for api::metrics::AccountMetricSetRequest {
2428 fn from(value: GetAccountMetricsInput) -> Self {
2429 let (set, start, end, interval) = match value.set {
2430 AccountMetricSet::ActiveBasins(args) => (
2431 api::metrics::AccountMetricSet::ActiveBasins,
2432 args.start,
2433 args.end,
2434 None,
2435 ),
2436 AccountMetricSet::AccountOps(args) => (
2437 api::metrics::AccountMetricSet::AccountOps,
2438 args.start,
2439 args.end,
2440 args.interval,
2441 ),
2442 };
2443 Self {
2444 set,
2445 start: Some(start),
2446 end: Some(end),
2447 interval: interval.map(Into::into),
2448 }
2449 }
2450}
2451
2452#[derive(Debug, Clone, Copy)]
2453pub enum BasinMetricSet {
2455 Storage(TimeRange),
2458 AppendOps(TimeRangeAndInterval),
2466 ReadOps(TimeRangeAndInterval),
2474 ReadThroughput(TimeRangeAndInterval),
2481 AppendThroughput(TimeRangeAndInterval),
2488 BasinOps(TimeRangeAndInterval),
2495}
2496
2497#[derive(Debug, Clone)]
2498#[non_exhaustive]
2499pub struct GetBasinMetricsInput {
2501 pub name: BasinName,
2503 pub set: BasinMetricSet,
2505}
2506
2507impl GetBasinMetricsInput {
2508 pub fn new(name: BasinName, set: BasinMetricSet) -> Self {
2510 Self { name, set }
2511 }
2512}
2513
2514impl From<GetBasinMetricsInput> for (BasinName, api::metrics::BasinMetricSetRequest) {
2515 fn from(value: GetBasinMetricsInput) -> Self {
2516 let (set, start, end, interval) = match value.set {
2517 BasinMetricSet::Storage(args) => (
2518 api::metrics::BasinMetricSet::Storage,
2519 args.start,
2520 args.end,
2521 None,
2522 ),
2523 BasinMetricSet::AppendOps(args) => (
2524 api::metrics::BasinMetricSet::AppendOps,
2525 args.start,
2526 args.end,
2527 args.interval,
2528 ),
2529 BasinMetricSet::ReadOps(args) => (
2530 api::metrics::BasinMetricSet::ReadOps,
2531 args.start,
2532 args.end,
2533 args.interval,
2534 ),
2535 BasinMetricSet::ReadThroughput(args) => (
2536 api::metrics::BasinMetricSet::ReadThroughput,
2537 args.start,
2538 args.end,
2539 args.interval,
2540 ),
2541 BasinMetricSet::AppendThroughput(args) => (
2542 api::metrics::BasinMetricSet::AppendThroughput,
2543 args.start,
2544 args.end,
2545 args.interval,
2546 ),
2547 BasinMetricSet::BasinOps(args) => (
2548 api::metrics::BasinMetricSet::BasinOps,
2549 args.start,
2550 args.end,
2551 args.interval,
2552 ),
2553 };
2554 (
2555 value.name,
2556 api::metrics::BasinMetricSetRequest {
2557 set,
2558 start: Some(start),
2559 end: Some(end),
2560 interval: interval.map(Into::into),
2561 },
2562 )
2563 }
2564}
2565
2566#[derive(Debug, Clone, Copy)]
2567pub enum StreamMetricSet {
2569 Storage(TimeRange),
2572}
2573
2574#[derive(Debug, Clone)]
2575#[non_exhaustive]
2576pub struct GetStreamMetricsInput {
2578 pub basin_name: BasinName,
2580 pub stream_name: StreamName,
2582 pub set: StreamMetricSet,
2584}
2585
2586impl GetStreamMetricsInput {
2587 pub fn new(basin_name: BasinName, stream_name: StreamName, set: StreamMetricSet) -> Self {
2590 Self {
2591 basin_name,
2592 stream_name,
2593 set,
2594 }
2595 }
2596}
2597
2598impl From<GetStreamMetricsInput> for (BasinName, StreamName, api::metrics::StreamMetricSetRequest) {
2599 fn from(value: GetStreamMetricsInput) -> Self {
2600 let (set, start, end, interval) = match value.set {
2601 StreamMetricSet::Storage(args) => (
2602 api::metrics::StreamMetricSet::Storage,
2603 args.start,
2604 args.end,
2605 None,
2606 ),
2607 };
2608 (
2609 value.basin_name,
2610 value.stream_name,
2611 api::metrics::StreamMetricSetRequest {
2612 set,
2613 start: Some(start),
2614 end: Some(end),
2615 interval,
2616 },
2617 )
2618 }
2619}
2620
2621#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2622pub enum MetricUnit {
2624 Bytes,
2626 Operations,
2628}
2629
2630impl From<api::metrics::MetricUnit> for MetricUnit {
2631 fn from(value: api::metrics::MetricUnit) -> Self {
2632 match value {
2633 api::metrics::MetricUnit::Bytes => MetricUnit::Bytes,
2634 api::metrics::MetricUnit::Operations => MetricUnit::Operations,
2635 }
2636 }
2637}
2638
2639#[derive(Debug, Clone)]
2640#[non_exhaustive]
2641pub struct ScalarMetric {
2643 pub name: String,
2645 pub unit: MetricUnit,
2647 pub value: f64,
2649}
2650
2651#[derive(Debug, Clone)]
2652#[non_exhaustive]
2653pub struct AccumulationMetric {
2656 pub name: String,
2658 pub unit: MetricUnit,
2660 pub interval: TimeseriesInterval,
2662 pub values: Vec<(u32, f64)>,
2666}
2667
2668#[derive(Debug, Clone)]
2669#[non_exhaustive]
2670pub struct GaugeMetric {
2672 pub name: String,
2674 pub unit: MetricUnit,
2676 pub values: Vec<(u32, f64)>,
2679}
2680
2681#[derive(Debug, Clone)]
2682#[non_exhaustive]
2683pub struct LabelMetric {
2685 pub name: String,
2687 pub values: Vec<String>,
2689}
2690
2691#[derive(Debug, Clone)]
2692pub enum Metric {
2694 Scalar(ScalarMetric),
2696 Accumulation(AccumulationMetric),
2699 Gauge(GaugeMetric),
2701 Label(LabelMetric),
2703}
2704
2705impl From<api::metrics::Metric> for Metric {
2706 fn from(value: api::metrics::Metric) -> Self {
2707 match value {
2708 api::metrics::Metric::Scalar(sm) => Metric::Scalar(ScalarMetric {
2709 name: sm.name.into(),
2710 unit: sm.unit.into(),
2711 value: sm.value,
2712 }),
2713 api::metrics::Metric::Accumulation(am) => Metric::Accumulation(AccumulationMetric {
2714 name: am.name.into(),
2715 unit: am.unit.into(),
2716 interval: am.interval.into(),
2717 values: am.values,
2718 }),
2719 api::metrics::Metric::Gauge(gm) => Metric::Gauge(GaugeMetric {
2720 name: gm.name.into(),
2721 unit: gm.unit.into(),
2722 values: gm.values,
2723 }),
2724 api::metrics::Metric::Label(lm) => Metric::Label(LabelMetric {
2725 name: lm.name.into(),
2726 values: lm.values,
2727 }),
2728 }
2729 }
2730}
2731
2732#[derive(Debug, Clone, Default)]
2733#[non_exhaustive]
2734pub struct ListStreamsInput {
2736 pub prefix: StreamNamePrefix,
2740 pub start_after: StreamNameStartAfter,
2744 pub limit: Option<usize>,
2748}
2749
2750impl ListStreamsInput {
2751 pub fn new() -> Self {
2753 Self::default()
2754 }
2755
2756 pub fn with_prefix(self, prefix: StreamNamePrefix) -> Self {
2758 Self { prefix, ..self }
2759 }
2760
2761 pub fn with_start_after(self, start_after: StreamNameStartAfter) -> Self {
2764 Self {
2765 start_after,
2766 ..self
2767 }
2768 }
2769
2770 pub fn with_limit(self, limit: usize) -> Self {
2772 Self {
2773 limit: Some(limit),
2774 ..self
2775 }
2776 }
2777}
2778
2779impl From<ListStreamsInput> for api::stream::ListStreamsRequest {
2780 fn from(value: ListStreamsInput) -> Self {
2781 Self {
2782 prefix: Some(value.prefix),
2783 start_after: Some(value.start_after),
2784 limit: value.limit,
2785 }
2786 }
2787}
2788
2789#[derive(Debug, Clone, Default)]
2790pub struct ListAllStreamsInput {
2792 pub prefix: StreamNamePrefix,
2796 pub start_after: StreamNameStartAfter,
2800 pub include_deleted: bool,
2804}
2805
2806impl ListAllStreamsInput {
2807 pub fn new() -> Self {
2809 Self::default()
2810 }
2811
2812 pub fn with_prefix(self, prefix: StreamNamePrefix) -> Self {
2814 Self { prefix, ..self }
2815 }
2816
2817 pub fn with_start_after(self, start_after: StreamNameStartAfter) -> Self {
2820 Self {
2821 start_after,
2822 ..self
2823 }
2824 }
2825
2826 pub fn with_include_deleted(self, include_deleted: bool) -> Self {
2828 Self {
2829 include_deleted,
2830 ..self
2831 }
2832 }
2833}
2834
2835#[derive(Debug, Clone, PartialEq, Eq)]
2836#[non_exhaustive]
2837pub struct StreamInfo {
2839 pub name: StreamName,
2841 pub created_at: S2DateTime,
2843 pub deleted_at: Option<S2DateTime>,
2845 pub cipher: Option<EncryptionAlgorithm>,
2847}
2848
2849impl TryFrom<api::stream::StreamInfo> for StreamInfo {
2850 type Error = ValidationError;
2851
2852 fn try_from(value: api::stream::StreamInfo) -> Result<Self, Self::Error> {
2853 Ok(Self {
2854 name: value.name,
2855 created_at: value.created_at.try_into()?,
2856 deleted_at: value.deleted_at.map(S2DateTime::try_from).transpose()?,
2857 cipher: value.cipher.map(Into::into),
2858 })
2859 }
2860}
2861
2862#[derive(Debug, Clone)]
2863#[non_exhaustive]
2864pub struct CreateStreamInput {
2866 pub name: StreamName,
2868 pub config: Option<StreamConfig>,
2872 idempotency_token: String,
2873}
2874
2875impl CreateStreamInput {
2876 pub fn new(name: StreamName) -> Self {
2878 Self {
2879 name,
2880 config: None,
2881 idempotency_token: idempotency_token(),
2882 }
2883 }
2884
2885 pub fn with_config(self, config: StreamConfig) -> Self {
2887 Self {
2888 config: Some(config),
2889 ..self
2890 }
2891 }
2892}
2893
2894impl From<CreateStreamInput> for (api::stream::CreateStreamRequest, String) {
2895 fn from(value: CreateStreamInput) -> Self {
2896 (
2897 api::stream::CreateStreamRequest {
2898 stream: value.name,
2899 config: value.config.map(Into::into),
2900 },
2901 value.idempotency_token,
2902 )
2903 }
2904}
2905
2906#[derive(Debug, Clone)]
2907#[non_exhaustive]
2908pub struct EnsureStreamInput {
2911 pub name: StreamName,
2913 pub config: Option<StreamConfig>,
2917}
2918
2919impl EnsureStreamInput {
2920 pub fn new(name: StreamName) -> Self {
2922 Self { name, config: None }
2923 }
2924
2925 pub fn with_config(self, config: StreamConfig) -> Self {
2927 Self {
2928 config: Some(config),
2929 ..self
2930 }
2931 }
2932}
2933
2934impl From<EnsureStreamInput> for (StreamName, Option<api::config::StreamConfig>) {
2935 fn from(value: EnsureStreamInput) -> Self {
2936 (value.name, value.config.map(Into::into))
2937 }
2938}
2939
2940#[derive(Debug, Clone)]
2941#[non_exhaustive]
2942pub struct DeleteStreamInput {
2944 pub name: StreamName,
2946 pub ignore_not_found: bool,
2948}
2949
2950impl DeleteStreamInput {
2951 pub fn new(name: StreamName) -> Self {
2953 Self {
2954 name,
2955 ignore_not_found: false,
2956 }
2957 }
2958
2959 pub fn with_ignore_not_found(self, ignore_not_found: bool) -> Self {
2961 Self {
2962 ignore_not_found,
2963 ..self
2964 }
2965 }
2966}
2967
2968#[derive(Debug, Clone)]
2969#[non_exhaustive]
2970pub struct ReconfigureStreamInput {
2972 pub name: StreamName,
2974 pub config: StreamReconfiguration,
2976}
2977
2978impl ReconfigureStreamInput {
2979 pub fn new(name: StreamName, config: StreamReconfiguration) -> Self {
2981 Self { name, config }
2982 }
2983}
2984
2985#[derive(Debug, Clone, PartialEq, Eq)]
2986pub struct FencingToken(String);
2992
2993impl FencingToken {
2994 pub(crate) fn from_server(value: String) -> Self {
2995 Self(value)
2996 }
2997
2998 pub fn generate(n: usize) -> Result<Self, ValidationError> {
3000 rand::rng()
3001 .sample_iter(&rand::distr::Alphanumeric)
3002 .take(n)
3003 .map(char::from)
3004 .collect::<String>()
3005 .parse()
3006 }
3007}
3008
3009impl FromStr for FencingToken {
3010 type Err = ValidationError;
3011
3012 fn from_str(s: &str) -> Result<Self, Self::Err> {
3013 if s.len() > MAX_FENCING_TOKEN_LENGTH {
3014 return Err(ValidationError(format!(
3015 "fencing token exceeds {MAX_FENCING_TOKEN_LENGTH} bytes in length",
3016 )));
3017 }
3018 Ok(FencingToken(s.to_string()))
3019 }
3020}
3021
3022impl std::fmt::Display for FencingToken {
3023 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3024 write!(f, "{}", self.0)
3025 }
3026}
3027
3028impl Deref for FencingToken {
3029 type Target = str;
3030
3031 fn deref(&self) -> &Self::Target {
3032 &self.0
3033 }
3034}
3035
3036#[derive(Debug, Clone, Copy, PartialEq)]
3037#[non_exhaustive]
3038pub struct StreamPosition {
3040 pub seq_num: u64,
3042 pub timestamp: u64,
3045}
3046
3047impl StreamPosition {
3048 pub fn new(seq_num: u64, timestamp: u64) -> Self {
3052 Self { seq_num, timestamp }
3053 }
3054}
3055
3056impl std::fmt::Display for StreamPosition {
3057 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3058 write!(f, "seq_num={}, timestamp={}", self.seq_num, self.timestamp)
3059 }
3060}
3061
3062impl From<api::stream::proto::StreamPosition> for StreamPosition {
3063 fn from(value: api::stream::proto::StreamPosition) -> Self {
3064 Self {
3065 seq_num: value.seq_num,
3066 timestamp: value.timestamp,
3067 }
3068 }
3069}
3070
3071impl From<api::stream::StreamPosition> for StreamPosition {
3072 fn from(value: api::stream::StreamPosition) -> Self {
3073 Self {
3074 seq_num: value.seq_num,
3075 timestamp: value.timestamp,
3076 }
3077 }
3078}
3079
3080#[derive(Debug, Clone, PartialEq)]
3081#[non_exhaustive]
3082pub struct Header {
3084 pub name: Bytes,
3086 pub value: Bytes,
3088}
3089
3090impl Header {
3091 pub fn new(name: impl Into<Bytes>, value: impl Into<Bytes>) -> Self {
3093 Self {
3094 name: name.into(),
3095 value: value.into(),
3096 }
3097 }
3098}
3099
3100impl From<Header> for api::stream::proto::Header {
3101 fn from(value: Header) -> Self {
3102 Self {
3103 name: value.name,
3104 value: value.value,
3105 }
3106 }
3107}
3108
3109impl From<api::stream::proto::Header> for Header {
3110 fn from(value: api::stream::proto::Header) -> Self {
3111 Self {
3112 name: value.name,
3113 value: value.value,
3114 }
3115 }
3116}
3117
3118#[derive(Debug, Clone, PartialEq)]
3119pub struct AppendRecord {
3121 body: Bytes,
3122 headers: Vec<Header>,
3123 timestamp: Option<u64>,
3124}
3125
3126impl AppendRecord {
3127 fn validate(self) -> Result<Self, ValidationError> {
3128 if self.metered_bytes() > RECORD_BATCH_MAX.bytes {
3129 Err(ValidationError(format!(
3130 "metered_bytes: {} exceeds {}",
3131 self.metered_bytes(),
3132 RECORD_BATCH_MAX.bytes
3133 )))
3134 } else {
3135 Ok(self)
3136 }
3137 }
3138
3139 pub fn new(body: impl Into<Bytes>) -> Result<Self, ValidationError> {
3141 let record = Self {
3142 body: body.into(),
3143 headers: Vec::default(),
3144 timestamp: None,
3145 };
3146 record.validate()
3147 }
3148
3149 pub fn with_headers(
3151 self,
3152 headers: impl IntoIterator<Item = Header>,
3153 ) -> Result<Self, ValidationError> {
3154 let record = Self {
3155 headers: headers.into_iter().collect(),
3156 ..self
3157 };
3158 record.validate()
3159 }
3160
3161 pub fn with_timestamp(self, timestamp: u64) -> Self {
3165 Self {
3166 timestamp: Some(timestamp),
3167 ..self
3168 }
3169 }
3170
3171 pub fn body(&self) -> &[u8] {
3173 &self.body
3174 }
3175
3176 pub fn headers(&self) -> &[Header] {
3178 &self.headers
3179 }
3180
3181 pub fn timestamp(&self) -> Option<u64> {
3183 self.timestamp
3184 }
3185}
3186
3187impl From<AppendRecord> for api::stream::proto::AppendRecord {
3188 fn from(value: AppendRecord) -> Self {
3189 Self {
3190 timestamp: value.timestamp,
3191 headers: value.headers.into_iter().map(Into::into).collect(),
3192 body: value.body,
3193 }
3194 }
3195}
3196
3197pub trait MeteredBytes {
3204 fn metered_bytes(&self) -> usize;
3206}
3207
3208macro_rules! metered_bytes_impl {
3209 ($ty:ty) => {
3210 impl MeteredBytes for $ty {
3211 fn metered_bytes(&self) -> usize {
3212 8 + (2 * self.headers.len())
3213 + self
3214 .headers
3215 .iter()
3216 .map(|h| h.name.len() + h.value.len())
3217 .sum::<usize>()
3218 + self.body.len()
3219 }
3220 }
3221 };
3222}
3223
3224metered_bytes_impl!(AppendRecord);
3225
3226impl MeteredSize for AppendRecord {
3227 fn metered_size(&self) -> usize {
3228 self.metered_bytes()
3229 }
3230}
3231
3232#[derive(Debug, Clone)]
3233pub struct AppendRecordBatch(Metered<Vec<AppendRecord>>);
3242
3243impl From<Metered<Vec<AppendRecord>>> for AppendRecordBatch {
3244 fn from(records: Metered<Vec<AppendRecord>>) -> Self {
3245 Self(records)
3246 }
3247}
3248
3249impl AppendRecordBatch {
3250 pub fn try_from_iter<I>(iter: I) -> Result<Self, ValidationError>
3252 where
3253 I: IntoIterator<Item = AppendRecord>,
3254 {
3255 let mut records = Metered::with_capacity(RECORD_BATCH_MAX.count);
3256
3257 for record in iter {
3258 records.push(Metered::from(record));
3259
3260 if records.metered_size() > RECORD_BATCH_MAX.bytes {
3261 return Err(ValidationError(format!(
3262 "batch size in metered bytes ({}) exceeds {}",
3263 records.metered_size(),
3264 RECORD_BATCH_MAX.bytes
3265 )));
3266 }
3267
3268 if records.len() > RECORD_BATCH_MAX.count {
3269 return Err(ValidationError(format!(
3270 "number of records in the batch exceeds {}",
3271 RECORD_BATCH_MAX.count
3272 )));
3273 }
3274 }
3275
3276 if records.is_empty() {
3277 return Err(ValidationError("batch is empty".into()));
3278 }
3279
3280 Ok(records.into())
3281 }
3282}
3283
3284impl Deref for AppendRecordBatch {
3285 type Target = [AppendRecord];
3286
3287 fn deref(&self) -> &Self::Target {
3288 &self.0[..]
3289 }
3290}
3291
3292impl MeteredBytes for AppendRecordBatch {
3293 fn metered_bytes(&self) -> usize {
3294 self.0.metered_size()
3295 }
3296}
3297
3298impl IntoIterator for AppendRecordBatch {
3299 type Item = AppendRecord;
3300 type IntoIter = std::vec::IntoIter<AppendRecord>;
3301
3302 fn into_iter(self) -> Self::IntoIter {
3303 self.0.into_iter()
3304 }
3305}
3306
3307impl<'a> IntoIterator for &'a AppendRecordBatch {
3308 type Item = &'a AppendRecord;
3309 type IntoIter = std::slice::Iter<'a, AppendRecord>;
3310
3311 fn into_iter(self) -> Self::IntoIter {
3312 self.0.iter()
3313 }
3314}
3315
3316#[derive(Debug, Clone)]
3317pub enum Command {
3319 Fence {
3321 fencing_token: FencingToken,
3323 },
3324 Trim {
3326 trim_point: u64,
3328 },
3329}
3330
3331#[derive(Debug, Clone)]
3332#[non_exhaustive]
3333pub struct CommandRecord {
3337 pub command: Command,
3339 pub timestamp: Option<u64>,
3341}
3342
3343impl CommandRecord {
3344 const FENCE: &[u8] = b"fence";
3345 const TRIM: &[u8] = b"trim";
3346
3347 pub fn fence(fencing_token: FencingToken) -> Self {
3352 Self {
3353 command: Command::Fence { fencing_token },
3354 timestamp: None,
3355 }
3356 }
3357
3358 pub fn trim(trim_point: u64) -> Self {
3365 Self {
3366 command: Command::Trim { trim_point },
3367 timestamp: None,
3368 }
3369 }
3370
3371 pub fn with_timestamp(self, timestamp: u64) -> Self {
3373 Self {
3374 timestamp: Some(timestamp),
3375 ..self
3376 }
3377 }
3378}
3379
3380impl From<CommandRecord> for AppendRecord {
3381 fn from(value: CommandRecord) -> Self {
3382 let (header_value, body) = match value.command {
3383 Command::Fence { fencing_token } => (
3384 CommandRecord::FENCE,
3385 Bytes::copy_from_slice(fencing_token.as_bytes()),
3386 ),
3387 Command::Trim { trim_point } => (
3388 CommandRecord::TRIM,
3389 Bytes::copy_from_slice(&trim_point.to_be_bytes()),
3390 ),
3391 };
3392 Self {
3393 body,
3394 headers: vec![Header::new("", header_value)],
3395 timestamp: value.timestamp,
3396 }
3397 }
3398}
3399
3400#[derive(Debug, Clone)]
3401#[non_exhaustive]
3402pub struct AppendInput {
3405 pub records: AppendRecordBatch,
3407 pub match_seq_num: Option<u64>,
3411 pub fencing_token: Option<FencingToken>,
3416 pub stream_config: Option<StreamConfig>,
3426}
3427
3428impl AppendInput {
3429 pub fn new(records: AppendRecordBatch) -> Self {
3431 Self {
3432 records,
3433 match_seq_num: None,
3434 fencing_token: None,
3435 stream_config: None,
3436 }
3437 }
3438
3439 pub fn with_stream_config(self, stream_config: StreamConfig) -> Self {
3441 Self {
3442 stream_config: Some(stream_config),
3443 ..self
3444 }
3445 }
3446
3447 pub fn with_match_seq_num(self, match_seq_num: u64) -> Self {
3449 Self {
3450 match_seq_num: Some(match_seq_num),
3451 ..self
3452 }
3453 }
3454
3455 pub fn with_fencing_token(self, fencing_token: FencingToken) -> Self {
3457 Self {
3458 fencing_token: Some(fencing_token),
3459 ..self
3460 }
3461 }
3462}
3463
3464impl From<AppendInput> for api::stream::proto::AppendInput {
3465 fn from(value: AppendInput) -> Self {
3466 Self {
3467 records: value.records.iter().cloned().map(Into::into).collect(),
3468 match_seq_num: value.match_seq_num,
3469 fencing_token: value.fencing_token.map(|t| t.to_string()),
3470 }
3471 }
3472}
3473
3474#[derive(Debug, Clone, PartialEq)]
3475#[non_exhaustive]
3476pub struct AppendAck {
3478 pub start: StreamPosition,
3480 pub end: StreamPosition,
3486 pub tail: StreamPosition,
3491}
3492
3493impl AppendAck {
3494 pub fn new(start: StreamPosition, end: StreamPosition, tail: StreamPosition) -> Self {
3498 Self { start, end, tail }
3499 }
3500}
3501
3502impl From<api::stream::proto::AppendAck> for AppendAck {
3503 fn from(value: api::stream::proto::AppendAck) -> Self {
3504 Self {
3505 start: value.start.unwrap_or_default().into(),
3506 end: value.end.unwrap_or_default().into(),
3507 tail: value.tail.unwrap_or_default().into(),
3508 }
3509 }
3510}
3511
3512#[derive(Debug, Clone, Copy)]
3513pub enum ReadFrom {
3515 SeqNum(u64),
3517 Timestamp(u64),
3519 TailOffset(u64),
3521}
3522
3523impl Default for ReadFrom {
3524 fn default() -> Self {
3525 Self::SeqNum(0)
3526 }
3527}
3528
3529#[derive(Debug, Default, Clone)]
3530#[non_exhaustive]
3531pub struct ReadStart {
3533 pub from: ReadFrom,
3537 pub clamp_to_tail: bool,
3541}
3542
3543impl ReadStart {
3544 pub fn new() -> Self {
3546 Self::default()
3547 }
3548
3549 pub fn with_from(self, from: ReadFrom) -> Self {
3551 Self { from, ..self }
3552 }
3553
3554 pub fn with_clamp_to_tail(self, clamp_to_tail: bool) -> Self {
3556 Self {
3557 clamp_to_tail,
3558 ..self
3559 }
3560 }
3561}
3562
3563impl From<ReadStart> for api::stream::ReadStart {
3564 fn from(value: ReadStart) -> Self {
3565 let (seq_num, timestamp, tail_offset) = match value.from {
3566 ReadFrom::SeqNum(n) => (Some(n), None, None),
3567 ReadFrom::Timestamp(t) => (None, Some(t), None),
3568 ReadFrom::TailOffset(o) => (None, None, Some(o)),
3569 };
3570 Self {
3571 seq_num,
3572 timestamp,
3573 tail_offset,
3574 clamp: if value.clamp_to_tail {
3575 Some(true)
3576 } else {
3577 None
3578 },
3579 }
3580 }
3581}
3582
3583#[derive(Debug, Clone, Default)]
3584#[non_exhaustive]
3585pub struct ReadLimits {
3587 pub count: Option<usize>,
3591 pub bytes: Option<usize>,
3595}
3596
3597impl ReadLimits {
3598 pub fn new() -> Self {
3600 Self::default()
3601 }
3602
3603 pub fn with_count(self, count: usize) -> Self {
3605 Self {
3606 count: Some(count),
3607 ..self
3608 }
3609 }
3610
3611 pub fn with_bytes(self, bytes: usize) -> Self {
3613 Self {
3614 bytes: Some(bytes),
3615 ..self
3616 }
3617 }
3618}
3619
3620#[derive(Debug, Clone, Default)]
3621#[non_exhaustive]
3622pub struct ReadStop {
3624 pub limits: ReadLimits,
3628 pub until: Option<RangeTo<u64>>,
3632 pub wait: Option<u32>,
3642}
3643
3644impl ReadStop {
3645 pub fn new() -> Self {
3647 Self::default()
3648 }
3649
3650 pub fn with_limits(self, limits: ReadLimits) -> Self {
3652 Self { limits, ..self }
3653 }
3654
3655 pub fn with_until(self, until: RangeTo<u64>) -> Self {
3657 Self {
3658 until: Some(until),
3659 ..self
3660 }
3661 }
3662
3663 pub fn with_wait(self, wait: u32) -> Self {
3665 Self {
3666 wait: Some(wait),
3667 ..self
3668 }
3669 }
3670}
3671
3672impl From<ReadStop> for api::stream::ReadEnd {
3673 fn from(value: ReadStop) -> Self {
3674 Self {
3675 count: value.limits.count,
3676 bytes: value.limits.bytes,
3677 until: value.until.map(|r| r.end),
3678 wait: value.wait,
3679 }
3680 }
3681}
3682
3683#[derive(Debug, Clone, Default)]
3684#[non_exhaustive]
3685pub struct ReadInput {
3688 pub start: ReadStart,
3692 pub stop: ReadStop,
3696 pub ignore_command_records: bool,
3700 pub stream_config: Option<StreamConfig>,
3705}
3706
3707#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
3708#[non_exhaustive]
3709pub enum ReadSessionRetryPolicy {
3711 #[default]
3713 Budgeted,
3714 Indefinite,
3720}
3721
3722#[derive(Debug, Clone, Default)]
3723#[non_exhaustive]
3724pub struct ReadSessionConfig {
3726 pub retry_policy: ReadSessionRetryPolicy,
3732}
3733
3734impl ReadSessionConfig {
3735 pub fn new() -> Self {
3737 Self::default()
3738 }
3739
3740 pub fn with_retry_policy(self, retry_policy: ReadSessionRetryPolicy) -> Self {
3742 Self {
3743 retry_policy,
3744 ..self
3745 }
3746 }
3747}
3748
3749impl ReadInput {
3750 pub fn new() -> Self {
3752 Self::default()
3753 }
3754
3755 pub fn with_start(self, start: ReadStart) -> Self {
3757 Self { start, ..self }
3758 }
3759
3760 pub fn with_stop(self, stop: ReadStop) -> Self {
3762 Self { stop, ..self }
3763 }
3764
3765 pub fn with_ignore_command_records(self, ignore_command_records: bool) -> Self {
3767 Self {
3768 ignore_command_records,
3769 ..self
3770 }
3771 }
3772
3773 pub fn with_stream_config(self, stream_config: StreamConfig) -> Self {
3775 Self {
3776 stream_config: Some(stream_config),
3777 ..self
3778 }
3779 }
3780}
3781
3782#[derive(Debug, Clone)]
3783#[non_exhaustive]
3784pub struct SequencedRecord {
3786 pub seq_num: u64,
3788 pub body: Bytes,
3790 pub headers: Vec<Header>,
3792 pub timestamp: u64,
3794}
3795
3796impl SequencedRecord {
3797 pub fn from_parts(
3801 seq_num: u64,
3802 timestamp: u64,
3803 headers: Vec<Header>,
3804 body: impl Into<Bytes>,
3805 ) -> Self {
3806 Self {
3807 seq_num,
3808 timestamp,
3809 body: body.into(),
3810 headers,
3811 }
3812 }
3813
3814 pub fn is_command_record(&self) -> bool {
3816 self.headers.len() == 1 && *self.headers[0].name == *b""
3817 }
3818}
3819
3820impl From<api::stream::proto::SequencedRecord> for SequencedRecord {
3821 fn from(value: api::stream::proto::SequencedRecord) -> Self {
3822 Self {
3823 seq_num: value.seq_num,
3824 body: value.body,
3825 headers: value.headers.into_iter().map(Into::into).collect(),
3826 timestamp: value.timestamp,
3827 }
3828 }
3829}
3830
3831metered_bytes_impl!(SequencedRecord);
3832
3833#[derive(Debug, Clone)]
3834#[non_exhaustive]
3835pub struct ReadBatch {
3838 pub records: Vec<SequencedRecord>,
3845 pub tail: Option<StreamPosition>,
3850}
3851
3852impl ReadBatch {
3853 pub fn new(records: Vec<SequencedRecord>, tail: Option<StreamPosition>) -> Self {
3857 Self { records, tail }
3858 }
3859
3860 pub(crate) fn from_api(batch: api::stream::proto::ReadBatch) -> Self {
3861 Self {
3862 records: batch.records.into_iter().map(Into::into).collect(),
3863 tail: batch.tail.map(Into::into),
3864 }
3865 }
3866}
3867
3868pub type Streaming<T> = Pin<Box<dyn Send + futures_core::Stream<Item = Result<T, RequestError>>>>;
3870
3871fn idempotency_token() -> String {
3872 uuid::Uuid::new_v4().simple().to_string()
3873}
3874
3875#[cfg(test)]
3876mod tests {
3877 use proptest::prelude::*;
3878 use rstest::rstest;
3879
3880 use super::*;
3881
3882 type HeaderParts = (Vec<u8>, Vec<u8>);
3883 type AppendRecordParts = (Vec<u8>, Vec<HeaderParts>);
3884
3885 fn byte_vec_strategy(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
3886 prop::collection::vec(any::<u8>(), 0..=max_len)
3887 }
3888
3889 fn header_parts_strategy() -> impl Strategy<Value = HeaderParts> {
3890 (byte_vec_strategy(32), byte_vec_strategy(64))
3891 }
3892
3893 fn string_strategy(max_chars: usize) -> impl Strategy<Value = String> {
3894 prop::collection::vec(any::<char>(), 0..=max_chars)
3895 .prop_map(|chars| chars.into_iter().collect())
3896 }
3897
3898 fn read_from_strategy() -> impl Strategy<Value = ReadFrom> {
3899 prop_oneof![
3900 any::<u64>().prop_map(ReadFrom::SeqNum),
3901 any::<u64>().prop_map(ReadFrom::Timestamp),
3902 any::<u64>().prop_map(ReadFrom::TailOffset),
3903 ]
3904 }
3905
3906 fn append_record_parts_strategy() -> impl Strategy<Value = AppendRecordParts> {
3907 (
3908 byte_vec_strategy(256),
3909 prop::collection::vec(header_parts_strategy(), 0..=16),
3910 )
3911 }
3912
3913 fn proto_stream_position_strategy() -> impl Strategy<Value = api::stream::proto::StreamPosition>
3914 {
3915 (any::<u64>(), any::<u64>()).prop_map(|(seq_num, timestamp)| {
3916 api::stream::proto::StreamPosition { seq_num, timestamp }
3917 })
3918 }
3919
3920 fn headers_from_parts(headers: &[HeaderParts]) -> Vec<Header> {
3921 headers
3922 .iter()
3923 .map(|(name, value)| Header::new(Bytes::from(name.clone()), Bytes::from(value.clone())))
3924 .collect()
3925 }
3926
3927 fn expected_metered_bytes(body: &[u8], headers: &[HeaderParts]) -> usize {
3928 8 + (2 * headers.len())
3929 + headers
3930 .iter()
3931 .map(|(name, value)| name.len() + value.len())
3932 .sum::<usize>()
3933 + body.len()
3934 }
3935
3936 #[test]
3939 fn s2_datetime_parse_valid_rfc3339() {
3940 let dt: S2DateTime = "2024-01-15T12:30:00Z".parse().unwrap();
3941 assert_eq!(dt.to_string(), "2024-01-15T12:30:00Z");
3942 }
3943
3944 #[test]
3945 fn s2_datetime_parse_with_offset() {
3946 let dt: S2DateTime = "2024-06-01T08:00:00+05:30".parse().unwrap();
3947 assert_eq!(dt.to_string(), "2024-06-01T08:00:00+05:30");
3948
3949 let offset_dt: time::OffsetDateTime = dt.into();
3950 assert_eq!(
3951 offset_dt.offset(),
3952 time::UtcOffset::from_hms(5, 30, 0).unwrap()
3953 );
3954 }
3955
3956 #[test]
3957 fn s2_datetime_parse_invalid() {
3958 let err = "not-a-date".parse::<S2DateTime>();
3959 assert!(err.is_err());
3960 }
3961
3962 #[test]
3963 fn s2_datetime_roundtrip_via_offset_datetime() {
3964 let odt = time::OffsetDateTime::from_unix_timestamp(1_700_000_000).unwrap();
3965 let dt = S2DateTime::try_from(odt).unwrap();
3966 let back: time::OffsetDateTime = dt.into();
3967 assert_eq!(odt, back);
3968 }
3969
3970 #[rstest]
3973 #[case::https_with_scheme("https://aws.s2.dev", Scheme::HTTPS)]
3974 #[case::http_with_scheme("http://localhost:8080", Scheme::HTTP)]
3975 #[case::default_https("aws.s2.dev", Scheme::HTTPS)]
3976 fn account_endpoint_parse(#[case] input: &str, #[case] expected_scheme: Scheme) {
3977 let ep: AccountEndpoint = input.parse().unwrap();
3978 assert_eq!(ep.scheme, expected_scheme);
3979 }
3980
3981 #[rstest]
3984 #[case::https_parent_zone("https://{basin}.b.s2.dev", Scheme::HTTPS, true)]
3985 #[case::http_direct("http://localhost:8080", Scheme::HTTP, false)]
3986 #[case::default_https_parent_zone("{basin}.b.s2.dev", Scheme::HTTPS, true)]
3987 fn basin_endpoint_parse(
3988 #[case] input: &str,
3989 #[case] expected_scheme: Scheme,
3990 #[case] expected_parent_zone: bool,
3991 ) {
3992 let ep: BasinEndpoint = input.parse().unwrap();
3993 assert_eq!(ep.scheme, expected_scheme);
3994 assert_eq!(
3995 matches!(ep.authority, BasinAuthority::ParentZone(_)),
3996 expected_parent_zone
3997 );
3998 }
3999
4000 #[test]
4003 fn s2_endpoints_new_requires_same_scheme() {
4004 let account: AccountEndpoint = "https://aws.s2.dev".parse().unwrap();
4005 let basin: BasinEndpoint = "http://localhost:8080".parse().unwrap();
4006 let err = S2Endpoints::new(account, basin);
4007 assert!(err.is_err());
4008 }
4009
4010 #[test]
4011 fn s2_endpoints_new_same_scheme_succeeds() {
4012 let account: AccountEndpoint = "https://aws.s2.dev".parse().unwrap();
4013 let basin: BasinEndpoint = "https://{basin}.b.s2.dev".parse().unwrap();
4014 let ep = S2Endpoints::new(account, basin).unwrap();
4015 assert_eq!(ep.scheme, Scheme::HTTPS);
4016 }
4017
4018 #[test]
4019 fn s2_endpoints_for_endpoint_defaults_to_https() {
4020 let ep = S2Endpoints::for_endpoint("localhost:8080").unwrap();
4021 let authority: Authority = "localhost:8080".parse().unwrap();
4022 assert_eq!(ep.scheme, Scheme::HTTPS);
4023 assert_eq!(ep.account_authority, authority);
4024 assert_eq!(ep.basin_authority, BasinAuthority::Direct(authority));
4025 }
4026
4027 #[test]
4028 fn s2_endpoints_for_endpoint_accepts_explicit_scheme() {
4029 let ep = S2Endpoints::for_endpoint("http://localhost:8080").unwrap();
4030 assert_eq!(ep.scheme, Scheme::HTTP);
4031 }
4032
4033 #[test]
4034 fn s2_endpoints_for_endpoint_rejects_invalid_endpoint() {
4035 assert!(S2Endpoints::for_endpoint("not a valid endpoint").is_err());
4036 }
4037
4038 #[rstest]
4041 #[case::none(Compression::None, CompressionAlgorithm::None)]
4042 #[case::gzip(Compression::Gzip, CompressionAlgorithm::Gzip)]
4043 #[case::zstd(Compression::Zstd, CompressionAlgorithm::Zstd)]
4044 fn compression_conversion(#[case] sdk: Compression, #[case] api: CompressionAlgorithm) {
4045 assert_eq!(CompressionAlgorithm::from(sdk), api);
4046 }
4047
4048 #[test]
4051 fn retry_config_defaults() {
4052 let rc = RetryConfig::default();
4053 assert_eq!(rc.max_attempts.get(), 3);
4054 assert_eq!(rc.min_base_delay, Duration::from_millis(100));
4055 assert_eq!(rc.max_base_delay, Duration::from_secs(1));
4056 assert!(matches!(rc.append_retry_policy, AppendRetryPolicy::All));
4057 }
4058
4059 #[test]
4060 fn retry_config_max_retries() {
4061 let rc = RetryConfig::default();
4062 assert_eq!(rc.max_retries(), 2);
4063 }
4064
4065 #[test]
4068 fn s2_config_defaults() {
4069 let cfg = S2Config::new("test-token");
4070 assert_eq!(cfg.connection_timeout, Duration::from_secs(3));
4071 assert_eq!(cfg.request_timeout, Duration::from_secs(5));
4072 assert!(!cfg.insecure_skip_cert_verification);
4073 }
4074
4075 #[cfg(feature = "_hidden")]
4076 #[rstest]
4077 #[case::matching_compression("content-encoding", "gzip", Compression::Gzip)]
4078 #[case::mixed_case("Content-Encoding", "identity", Compression::None)]
4079 #[case::empty_value("content-encoding", "", Compression::None)]
4080 fn default_headers_reject_content_encoding(
4081 #[case] name: &str,
4082 #[case] value: &str,
4083 #[case] compression: Compression,
4084 ) {
4085 let headers = HeaderMap::from_iter([(
4086 name.parse::<http::header::HeaderName>().unwrap(),
4087 HeaderValue::from_str(value).unwrap(),
4088 )]);
4089 let error = S2Config::new("token")
4090 .with_compression(compression)
4091 .with_default_headers(headers)
4092 .unwrap_err();
4093 assert!(error.0.contains("Content-Encoding"));
4094 assert!(error.0.contains("with_compression"));
4095 }
4096
4097 #[cfg(feature = "_hidden")]
4098 #[rstest]
4099 #[case::content_type_s2s("content-type", "s2s/proto")]
4100 #[case::content_type_protobuf("content-type", "application/protobuf")]
4101 #[case::content_type_json("content-type", "application/json")]
4102 #[case::content_type_mixed_case("Content-Type", "s2s/proto")]
4103 #[case::content_type_empty("content-type", "")]
4104 #[case::content_length("content-length", "123")]
4105 #[case::content_length_mixed_case("Content-Length", "0")]
4106 #[case::content_length_empty("content-length", "")]
4107 #[case::transfer_encoding("transfer-encoding", "chunked")]
4108 #[case::transfer_encoding_mixed_case("Transfer-Encoding", "chunked")]
4109 #[case::transfer_encoding_empty("transfer-encoding", "")]
4110 fn default_headers_reject_framing_headers(#[case] name: &str, #[case] value: &str) {
4111 let headers = HeaderMap::from_iter([(
4112 name.parse::<http::header::HeaderName>().unwrap(),
4113 HeaderValue::from_str(value).unwrap(),
4114 )]);
4115 let error = S2Config::new("token")
4116 .with_default_headers(headers)
4117 .unwrap_err();
4118 assert!(error.0.contains(&name.to_ascii_lowercase()));
4119 assert!(error.0.contains("framing"));
4120 }
4121
4122 #[rstest]
4125 #[case::standard(StorageClass::Standard)]
4126 #[case::express(StorageClass::Express)]
4127 fn storage_class_roundtrip(#[case] sdk: StorageClass) {
4128 let api: api::config::StorageClass = sdk.into();
4129 let back: StorageClass = api.into();
4130 assert_eq!(back, sdk);
4131 }
4132
4133 #[rstest]
4136 #[case::age(RetentionPolicy::Age(3600))]
4137 #[case::infinite(RetentionPolicy::Infinite)]
4138 fn retention_policy_roundtrip(#[case] sdk: RetentionPolicy) {
4139 let api: api::config::RetentionPolicy = sdk.into();
4140 let back: RetentionPolicy = api.into();
4141 assert_eq!(back, sdk);
4142 }
4143
4144 #[rstest]
4147 #[case::client_prefer(
4148 TimestampingMode::ClientPrefer,
4149 api::config::TimestampingMode::ClientPrefer
4150 )]
4151 #[case::client_require(
4152 TimestampingMode::ClientRequire,
4153 api::config::TimestampingMode::ClientRequire
4154 )]
4155 #[case::arrival(TimestampingMode::Arrival, api::config::TimestampingMode::Arrival)]
4156 fn timestamping_mode_roundtrip(
4157 #[case] sdk: TimestampingMode,
4158 #[case] expected_api: api::config::TimestampingMode,
4159 ) {
4160 let converted: api::config::TimestampingMode = sdk.into();
4161 assert_eq!(converted, expected_api);
4162 let back: TimestampingMode = converted.into();
4163 assert_eq!(back, sdk);
4164 }
4165
4166 #[test]
4169 fn timestamping_config_roundtrip() {
4170 let sdk = TimestampingConfig {
4171 mode: Some(TimestampingMode::Arrival),
4172 uncapped: Some(true),
4173 };
4174 let api: api::config::TimestampingConfig = sdk.into();
4175 let back: TimestampingConfig = api.into();
4176 assert_eq!(back, sdk);
4177 }
4178
4179 #[test]
4182 fn delete_on_empty_config_roundtrip() {
4183 let sdk = DeleteOnEmptyConfig::new().with_min_age(Duration::from_secs(300));
4184 let api: api::config::DeleteOnEmptyConfig = sdk.into();
4185 let back: DeleteOnEmptyConfig = api.into();
4186 assert_eq!(back, sdk);
4187 }
4188
4189 #[test]
4192 fn stream_config_builder_and_roundtrip() {
4193 let sdk = StreamConfig::new()
4194 .with_storage_class(StorageClass::Express)
4195 .with_retention_policy(RetentionPolicy::Age(86400))
4196 .with_timestamping(TimestampingConfig {
4197 mode: Some(TimestampingMode::ClientPrefer),
4198 uncapped: None,
4199 })
4200 .with_delete_on_empty(DeleteOnEmptyConfig { min_age_secs: 60 });
4201 let api: api::config::StreamConfig = sdk.clone().into();
4202 let back: StreamConfig = api.into();
4203 assert_eq!(back, sdk);
4204 }
4205
4206 #[test]
4209 fn basin_config_builder_and_roundtrip() {
4210 let sdk = BasinConfig::new()
4211 .with_default_stream_config(
4212 StreamConfig::new().with_storage_class(StorageClass::Standard),
4213 )
4214 .with_create_stream_on_append(true)
4215 .with_create_stream_on_read(false);
4216 let api: api::config::BasinConfig = sdk.clone().into();
4217 let back: BasinConfig = api.into();
4218 assert_eq!(back, sdk);
4219 }
4220
4221 proptest! {
4224 #[test]
4225 fn fencing_token_parse_accepts_only_within_byte_limit(
4226 token in string_strategy(MAX_FENCING_TOKEN_LENGTH + 8),
4227 ) {
4228 let parsed = token.parse::<FencingToken>();
4229
4230 if token.len() <= MAX_FENCING_TOKEN_LENGTH {
4231 prop_assert_eq!(parsed.unwrap().to_string(), token);
4232 } else {
4233 prop_assert!(parsed.is_err());
4234 }
4235 }
4236 }
4237
4238 #[test]
4241 fn stream_position_display() {
4242 let pos = StreamPosition {
4243 seq_num: 42,
4244 timestamp: 1700000000,
4245 };
4246 assert_eq!(pos.to_string(), "seq_num=42, timestamp=1700000000");
4247 }
4248
4249 proptest! {
4250 #[test]
4251 fn stream_position_conversions_preserve_values(seq_num in any::<u64>(), timestamp in any::<u64>()) {
4252 let proto: StreamPosition = api::stream::proto::StreamPosition {
4253 seq_num,
4254 timestamp,
4255 }
4256 .into();
4257 prop_assert_eq!(proto.seq_num, seq_num);
4258 prop_assert_eq!(proto.timestamp, timestamp);
4259
4260 let api: StreamPosition = api::stream::StreamPosition {
4261 seq_num,
4262 timestamp,
4263 }
4264 .into();
4265 prop_assert_eq!(api.seq_num, seq_num);
4266 prop_assert_eq!(api.timestamp, timestamp);
4267 }
4268 }
4269
4270 proptest! {
4273 #[test]
4274 fn header_proto_roundtrip_preserves_binary_parts(
4275 name in byte_vec_strategy(64),
4276 value in byte_vec_strategy(128),
4277 ) {
4278 let header = Header::new(Bytes::from(name.clone()), Bytes::from(value.clone()));
4279 let proto: api::stream::proto::Header = header.into();
4280 let back: Header = proto.into();
4281
4282 prop_assert_eq!(back.name.as_ref(), name.as_slice());
4283 prop_assert_eq!(back.value.as_ref(), value.as_slice());
4284 }
4285 }
4286
4287 #[test]
4290 fn append_record_too_large() {
4291 let big_body = vec![0u8; RECORD_BATCH_MAX.bytes + 1];
4292 assert!(AppendRecord::new(big_body).is_err());
4293 }
4294
4295 proptest! {
4298 #[test]
4299 fn append_record_preserves_fields_and_metered_byte_formula(
4300 (body, headers) in append_record_parts_strategy(),
4301 timestamp in proptest::option::of(any::<u64>()),
4302 ) {
4303 let mut record = AppendRecord::new(body.clone())
4304 .unwrap()
4305 .with_headers(headers_from_parts(&headers))
4306 .unwrap();
4307 if let Some(timestamp) = timestamp {
4308 record = record.with_timestamp(timestamp);
4309 }
4310
4311 prop_assert_eq!(record.body(), body.as_slice());
4312 prop_assert_eq!(record.headers().len(), headers.len());
4313 prop_assert_eq!(record.timestamp(), timestamp);
4314 prop_assert_eq!(record.metered_bytes(), expected_metered_bytes(&body, &headers));
4315
4316 for (actual, (expected_name, expected_value)) in record.headers().iter().zip(headers.iter()) {
4317 prop_assert_eq!(actual.name.as_ref(), expected_name.as_slice());
4318 prop_assert_eq!(actual.value.as_ref(), expected_value.as_slice());
4319 }
4320 }
4321 }
4322
4323 #[test]
4326 fn append_record_batch_empty_is_err() {
4327 let result = AppendRecordBatch::try_from_iter(vec![]);
4328 assert!(result.is_err());
4329 }
4330
4331 #[test]
4332 fn append_record_batch_too_many_records() {
4333 let records: Vec<_> = (0..1001).map(|_| AppendRecord::new("x").unwrap()).collect();
4334 let result = AppendRecordBatch::try_from_iter(records);
4335 assert!(result.is_err());
4336 }
4337
4338 proptest! {
4339 #[test]
4340 fn append_record_batch_metered_bytes_is_sum_of_records(
4341 records in prop::collection::vec(append_record_parts_strategy(), 1..=32),
4342 ) {
4343 let expected = records
4344 .iter()
4345 .map(|(body, headers)| expected_metered_bytes(body, headers))
4346 .sum::<usize>();
4347 let records = records
4348 .into_iter()
4349 .map(|(body, headers)| {
4350 AppendRecord::new(body)
4351 .unwrap()
4352 .with_headers(headers_from_parts(&headers))
4353 .unwrap()
4354 })
4355 .collect::<Vec<_>>();
4356
4357 let batch = AppendRecordBatch::try_from_iter(records).unwrap();
4358 prop_assert_eq!(batch.metered_bytes(), expected);
4359 prop_assert_eq!(batch.iter().map(MeteredBytes::metered_bytes).sum::<usize>(), expected);
4360 }
4361 }
4362
4363 #[test]
4366 fn command_record_fence() {
4367 let token: FencingToken = "tok".parse().unwrap();
4368 let cmd = CommandRecord::fence(token);
4369 let record: AppendRecord = cmd.into();
4370 assert_eq!(record.headers().len(), 1);
4371 assert_eq!(record.headers()[0].name.as_ref(), b"");
4372 assert_eq!(record.headers()[0].value.as_ref(), b"fence");
4373 assert_eq!(record.body(), b"tok");
4374 }
4375
4376 #[test]
4377 fn command_record_trim() {
4378 let cmd = CommandRecord::trim(42);
4379 let record: AppendRecord = cmd.into();
4380 assert_eq!(record.headers().len(), 1);
4381 assert_eq!(record.headers()[0].value.as_ref(), b"trim");
4382 assert_eq!(record.body(), &42u64.to_be_bytes());
4383 }
4384
4385 #[rstest]
4388 #[case::command(vec![Header::new("", "fence")], true)]
4389 #[case::regular(vec![Header::new("key", "value")], false)]
4390 #[case::no_headers(vec![], false)]
4391 fn sequenced_record_command_detection(#[case] headers: Vec<Header>, #[case] expected: bool) {
4392 let record = SequencedRecord {
4393 seq_num: 0,
4394 body: Bytes::from("data"),
4395 headers,
4396 timestamp: 0,
4397 };
4398 assert_eq!(record.is_command_record(), expected);
4399 }
4400
4401 proptest! {
4404 #[test]
4405 fn read_start_to_api_sets_only_selected_position_field(
4406 from in read_from_strategy(),
4407 clamp_to_tail in any::<bool>(),
4408 ) {
4409 let (seq_num, timestamp, tail_offset) = match from {
4410 ReadFrom::SeqNum(value) => (Some(value), None, None),
4411 ReadFrom::Timestamp(value) => (None, Some(value), None),
4412 ReadFrom::TailOffset(value) => (None, None, Some(value)),
4413 };
4414 let api: api::stream::ReadStart = ReadStart::new()
4415 .with_from(from)
4416 .with_clamp_to_tail(clamp_to_tail)
4417 .into();
4418
4419 prop_assert_eq!(api.seq_num, seq_num);
4420 prop_assert_eq!(api.timestamp, timestamp);
4421 prop_assert_eq!(api.tail_offset, tail_offset);
4422 prop_assert_eq!(api.clamp, clamp_to_tail.then_some(true));
4423 }
4424 }
4425
4426 #[test]
4429 fn read_stop_to_api() {
4430 let stop = ReadStop::new()
4431 .with_limits(ReadLimits::new().with_count(50))
4432 .with_until(..1000)
4433 .with_wait(30);
4434 let api: api::stream::ReadEnd = stop.into();
4435 assert_eq!(api.count, Some(50));
4436 assert_eq!(api.until, Some(1000));
4437 assert_eq!(api.wait, Some(30));
4438 }
4439
4440 #[test]
4443 fn operation_roundtrip_all_variants() {
4444 let variants = [
4445 Operation::ListBasins,
4446 Operation::CreateBasin,
4447 Operation::GetBasinConfig,
4448 Operation::DeleteBasin,
4449 Operation::ReconfigureBasin,
4450 Operation::ListAccessTokens,
4451 Operation::IssueAccessToken,
4452 Operation::RevokeAccessToken,
4453 Operation::GetAccountMetrics,
4454 Operation::GetBasinMetrics,
4455 Operation::GetStreamMetrics,
4456 Operation::ListStreams,
4457 Operation::CreateStream,
4458 Operation::GetStreamConfig,
4459 Operation::DeleteStream,
4460 Operation::ReconfigureStream,
4461 Operation::CheckTail,
4462 Operation::Append,
4463 Operation::Read,
4464 Operation::Trim,
4465 Operation::Fence,
4466 Operation::ListLocations,
4467 Operation::GetDefaultLocation,
4468 Operation::SetDefaultLocation,
4469 ];
4470 for op in variants {
4471 let api_op: api::access::Operation = op.into();
4472 let back: Operation = api_op.into();
4473 assert_eq!(back, op);
4474 }
4475 }
4476
4477 #[test]
4480 fn metric_unit_conversion() {
4481 assert_eq!(
4482 MetricUnit::from(api::metrics::MetricUnit::Bytes),
4483 MetricUnit::Bytes
4484 );
4485 assert_eq!(
4486 MetricUnit::from(api::metrics::MetricUnit::Operations),
4487 MetricUnit::Operations
4488 );
4489 }
4490
4491 proptest! {
4494 #[test]
4495 fn append_ack_from_proto_preserves_present_positions_and_defaults_missing(
4496 start in proptest::option::of(proto_stream_position_strategy()),
4497 end in proptest::option::of(proto_stream_position_strategy()),
4498 tail in proptest::option::of(proto_stream_position_strategy()),
4499 ) {
4500 let expected_start = start.unwrap_or_default();
4501 let expected_end = end.unwrap_or_default();
4502 let expected_tail = tail.unwrap_or_default();
4503 let ack: AppendAck = api::stream::proto::AppendAck { start, end, tail }.into();
4504
4505 prop_assert_eq!(ack.start.seq_num, expected_start.seq_num);
4506 prop_assert_eq!(ack.start.timestamp, expected_start.timestamp);
4507 prop_assert_eq!(ack.end.seq_num, expected_end.seq_num);
4508 prop_assert_eq!(ack.end.timestamp, expected_end.timestamp);
4509 prop_assert_eq!(ack.tail.seq_num, expected_tail.seq_num);
4510 prop_assert_eq!(ack.tail.timestamp, expected_tail.timestamp);
4511 }
4512 }
4513
4514 #[test]
4517 fn read_batch_from_api() {
4518 let proto_batch = api::stream::proto::ReadBatch {
4519 records: vec![api::stream::proto::SequencedRecord {
4520 seq_num: 0,
4521 body: Bytes::from("hi"),
4522 headers: vec![api::stream::proto::Header {
4523 name: Bytes::from("k"),
4524 value: Bytes::from("v"),
4525 }],
4526 timestamp: 42,
4527 }],
4528 tail: Some(api::stream::proto::StreamPosition {
4529 seq_num: 1,
4530 timestamp: 42,
4531 }),
4532 };
4533 let batch = ReadBatch::from_api(proto_batch);
4534 assert_eq!(batch.records.len(), 1);
4535 assert_eq!(batch.records[0].seq_num, 0);
4536 assert_eq!(batch.records[0].timestamp, 42);
4537 assert_eq!(batch.records[0].body.as_ref(), b"hi");
4538 assert_eq!(batch.records[0].headers.len(), 1);
4539 assert_eq!(batch.records[0].headers[0].name.as_ref(), b"k");
4540 assert_eq!(batch.records[0].headers[0].value.as_ref(), b"v");
4541 assert_eq!(
4542 batch.tail,
4543 Some(StreamPosition {
4544 seq_num: 1,
4545 timestamp: 42,
4546 })
4547 );
4548 }
4549
4550 #[test]
4553 fn create_basin_input_to_api() {
4554 let name: BasinName = "test-basin-name".parse().unwrap();
4555 let input = CreateBasinInput::new(name.clone()).with_config(BasinConfig::new());
4556 let (req, token): (api::basin::CreateBasinRequest, String) = input.into();
4557 assert_eq!(req.basin, name);
4558 assert!(req.config.is_some());
4559 assert!(!token.is_empty());
4560 }
4561
4562 #[test]
4565 fn create_stream_input_to_api() {
4566 let name: StreamName = "my-stream".parse().unwrap();
4567 let input = CreateStreamInput::new(name.clone()).with_config(StreamConfig::new());
4568 let (req, token): (api::stream::CreateStreamRequest, String) = input.into();
4569 assert_eq!(req.stream, name);
4570 assert!(req.config.is_some());
4571 assert!(!token.is_empty());
4572 }
4573
4574 #[test]
4577 fn sequenced_record_from_proto() {
4578 let proto = api::stream::proto::SequencedRecord {
4579 seq_num: 99,
4580 body: Bytes::from("data"),
4581 headers: vec![api::stream::proto::Header {
4582 name: Bytes::from("k"),
4583 value: Bytes::from("v"),
4584 }],
4585 timestamp: 1234,
4586 };
4587 let record: SequencedRecord = proto.into();
4588 assert_eq!(record.seq_num, 99);
4589 assert_eq!(record.body.as_ref(), b"data");
4590 assert_eq!(record.headers.len(), 1);
4591 assert_eq!(record.headers[0].name.as_ref(), b"k");
4592 assert_eq!(record.headers[0].value.as_ref(), b"v");
4593 assert_eq!(record.timestamp, 1234);
4594 }
4595}