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 header::HeaderValue,
20 uri::{Authority, Scheme},
21};
22use rand::RngExt;
23use s2_api::{v1 as api, v1::stream::s2s::CompressionAlgorithm};
24pub use s2_common::ValidationError;
26pub use s2_common::access::AccessTokenId;
30pub use s2_common::access::AccessTokenIdPrefix;
32pub use s2_common::access::AccessTokenIdStartAfter;
34pub use s2_common::basin::BasinName;
39pub use s2_common::basin::BasinNamePrefix;
41pub use s2_common::basin::BasinNameStartAfter;
43pub use s2_common::location::LocationName;
48pub use s2_common::stream::StreamName;
52pub use s2_common::stream::StreamNamePrefix;
54pub use s2_common::stream::StreamNameStartAfter;
56pub use s2_common::{
57 caps::RECORD_BATCH_MAX,
58 encryption::{EncryptionAlgorithm, EncryptionKey},
59};
60
61pub(crate) const ONE_MIB: u32 = 1024 * 1024;
62
63use s2_common::{
64 maybe::Maybe,
65 record::{MAX_FENCING_TOKEN_LENGTH, Metered, MeteredSize},
66 resources::ProvisionResult,
67};
68use secrecy::SecretString;
69
70use crate::error::RequestError;
71
72#[cfg(feature = "_hidden")]
73#[derive(Debug, Clone, thiserror::Error)]
74#[error("{message}")]
75#[doc(hidden)]
76pub struct AccessTokenProviderError {
77 message: String,
78 retryable: bool,
79}
80
81#[cfg(feature = "_hidden")]
82impl AccessTokenProviderError {
83 pub fn transient(message: impl Into<String>) -> Self {
85 Self {
86 message: message.into(),
87 retryable: true,
88 }
89 }
90
91 pub fn permanent(message: impl Into<String>) -> Self {
93 Self {
94 message: message.into(),
95 retryable: false,
96 }
97 }
98
99 pub(crate) fn is_retryable(&self) -> bool {
100 self.retryable
101 }
102}
103
104#[cfg(feature = "_hidden")]
105#[async_trait]
106#[doc(hidden)]
107pub trait AccessTokenProvider: fmt::Debug + Send + Sync {
108 async fn access_token(&self) -> Result<String, AccessTokenProviderError>;
110
111 fn invalidate_access_token(&self, _rejected_access_token: &str) {}
113}
114
115#[derive(Clone)]
116pub(crate) enum AccessToken {
117 Static(SecretString),
118 #[cfg(feature = "_hidden")]
119 Provider(Arc<dyn AccessTokenProvider>),
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub(crate) enum AccessTokenMode {
124 Static,
125 #[cfg(feature = "_hidden")]
126 Refreshable,
127}
128
129impl AccessTokenMode {
130 pub(crate) fn is_refreshable(self) -> bool {
131 match self {
132 Self::Static => false,
133 #[cfg(feature = "_hidden")]
134 Self::Refreshable => true,
135 }
136 }
137}
138
139impl AccessToken {
140 pub(crate) fn mode(&self) -> AccessTokenMode {
141 match self {
142 Self::Static(_) => AccessTokenMode::Static,
143 #[cfg(feature = "_hidden")]
144 Self::Provider(_) => AccessTokenMode::Refreshable,
145 }
146 }
147}
148
149impl fmt::Debug for AccessToken {
150 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151 match self {
152 Self::Static(_) => formatter.write_str("Static(<redacted>)"),
153 #[cfg(feature = "_hidden")]
154 Self::Provider(_) => formatter.write_str("Provider(<redacted>)"),
155 }
156 }
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub struct S2DateTime(time::OffsetDateTime);
166
167impl TryFrom<time::OffsetDateTime> for S2DateTime {
168 type Error = ValidationError;
169
170 fn try_from(dt: time::OffsetDateTime) -> Result<Self, Self::Error> {
171 dt.format(&time::format_description::well_known::Rfc3339)
172 .map_err(|e| ValidationError(format!("not a valid RFC 3339 datetime: {e}")))?;
173 Ok(Self(dt))
174 }
175}
176
177impl From<S2DateTime> for time::OffsetDateTime {
178 fn from(dt: S2DateTime) -> Self {
179 dt.0
180 }
181}
182
183impl FromStr for S2DateTime {
184 type Err = ValidationError;
185
186 fn from_str(s: &str) -> Result<Self, Self::Err> {
187 time::OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339)
188 .map(Self)
189 .map_err(|e| ValidationError(format!("not a valid RFC 3339 datetime: {e}")))
190 }
191}
192
193impl fmt::Display for S2DateTime {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 write!(
196 f,
197 "{}",
198 self.0
199 .format(&time::format_description::well_known::Rfc3339)
200 .expect("RFC3339 formatting should not fail for S2DateTime")
201 )
202 }
203}
204
205#[derive(Debug, Clone, PartialEq)]
207pub(crate) enum BasinAuthority {
208 ParentZone(Authority),
210 Direct(Authority),
212}
213
214#[derive(Debug, Clone)]
216pub struct AccountEndpoint {
217 scheme: Scheme,
218 authority: Authority,
219}
220
221impl AccountEndpoint {
222 pub fn new(endpoint: &str) -> Result<Self, ValidationError> {
224 endpoint.parse()
225 }
226}
227
228impl FromStr for AccountEndpoint {
229 type Err = ValidationError;
230
231 fn from_str(s: &str) -> Result<Self, Self::Err> {
232 let (scheme, authority) = match s.find("://") {
233 Some(idx) => {
234 let scheme: Scheme = s[..idx]
235 .parse()
236 .map_err(|_| "invalid account endpoint scheme".to_string())?;
237 (scheme, &s[idx + 3..])
238 }
239 None => (Scheme::HTTPS, s),
240 };
241 Ok(Self {
242 scheme,
243 authority: authority
244 .parse()
245 .map_err(|e| format!("invalid account endpoint authority: {e}"))?,
246 })
247 }
248}
249
250#[derive(Debug, Clone)]
252pub struct BasinEndpoint {
253 scheme: Scheme,
254 authority: BasinAuthority,
255}
256
257impl BasinEndpoint {
258 pub fn new(endpoint: &str) -> Result<Self, ValidationError> {
260 endpoint.parse()
261 }
262}
263
264impl FromStr for BasinEndpoint {
265 type Err = ValidationError;
266
267 fn from_str(s: &str) -> Result<Self, Self::Err> {
268 let (scheme, authority) = match s.find("://") {
269 Some(idx) => {
270 let scheme: Scheme = s[..idx]
271 .parse()
272 .map_err(|_| "invalid basin endpoint scheme".to_string())?;
273 (scheme, &s[idx + 3..])
274 }
275 None => (Scheme::HTTPS, s),
276 };
277 let authority = if let Some(authority) = authority.strip_prefix("{basin}.") {
278 BasinAuthority::ParentZone(
279 authority
280 .parse()
281 .map_err(|e| format!("invalid basin endpoint authority: {e}"))?,
282 )
283 } else {
284 BasinAuthority::Direct(
285 authority
286 .parse()
287 .map_err(|e| format!("invalid basin endpoint authority: {e}"))?,
288 )
289 };
290 Ok(Self { scheme, authority })
291 }
292}
293
294#[derive(Debug, Clone)]
295#[non_exhaustive]
296pub struct S2Endpoints {
298 pub(crate) scheme: Scheme,
299 pub(crate) account_authority: Authority,
300 pub(crate) basin_authority: BasinAuthority,
301}
302
303impl S2Endpoints {
304 pub fn new(
306 account_endpoint: AccountEndpoint,
307 basin_endpoint: BasinEndpoint,
308 ) -> Result<Self, ValidationError> {
309 if account_endpoint.scheme != basin_endpoint.scheme {
310 return Err("account and basin endpoints must have the same scheme".into());
311 }
312 Ok(Self {
313 scheme: account_endpoint.scheme,
314 account_authority: account_endpoint.authority,
315 basin_authority: basin_endpoint.authority,
316 })
317 }
318
319 pub fn for_endpoint(endpoint: &str) -> Result<Self, ValidationError> {
323 Self::new(
324 AccountEndpoint::new(endpoint)?,
325 BasinEndpoint::new(endpoint)?,
326 )
327 }
328
329 pub fn from_env() -> Result<Self, ValidationError> {
335 let account_endpoint: AccountEndpoint = match std::env::var("S2_ACCOUNT_ENDPOINT") {
336 Ok(endpoint) => endpoint.parse()?,
337 Err(VarError::NotPresent) => return Err("S2_ACCOUNT_ENDPOINT env var not set".into()),
338 Err(VarError::NotUnicode(_)) => {
339 return Err("S2_ACCOUNT_ENDPOINT is not valid unicode".into());
340 }
341 };
342
343 let basin_endpoint: BasinEndpoint = match std::env::var("S2_BASIN_ENDPOINT") {
344 Ok(endpoint) => endpoint.parse()?,
345 Err(VarError::NotPresent) => return Err("S2_BASIN_ENDPOINT env var not set".into()),
346 Err(VarError::NotUnicode(_)) => {
347 return Err("S2_BASIN_ENDPOINT is not valid unicode".into());
348 }
349 };
350
351 if account_endpoint.scheme != basin_endpoint.scheme {
352 return Err(
353 "S2_ACCOUNT_ENDPOINT and S2_BASIN_ENDPOINT must have the same scheme".into(),
354 );
355 }
356
357 Ok(Self {
358 scheme: account_endpoint.scheme,
359 account_authority: account_endpoint.authority,
360 basin_authority: basin_endpoint.authority,
361 })
362 }
363
364 pub fn for_cloud() -> Self {
366 Self {
367 scheme: Scheme::HTTPS,
368 account_authority: "a.s2.dev".try_into().expect("valid authority"),
369 basin_authority: BasinAuthority::ParentZone(
370 "b.s2.dev".try_into().expect("valid authority"),
371 ),
372 }
373 }
374}
375
376#[derive(Debug, Clone, Copy)]
377pub enum Compression {
379 None,
381 Gzip,
383 Zstd,
385}
386
387impl From<Compression> for CompressionAlgorithm {
388 fn from(value: Compression) -> Self {
389 match value {
390 Compression::None => CompressionAlgorithm::None,
391 Compression::Gzip => CompressionAlgorithm::Gzip,
392 Compression::Zstd => CompressionAlgorithm::Zstd,
393 }
394 }
395}
396
397#[derive(Debug, Clone, Copy, PartialEq)]
398#[non_exhaustive]
399pub enum AppendRetryPolicy {
402 All,
404 NoSideEffects,
414}
415
416#[derive(Debug, Clone)]
417#[non_exhaustive]
418pub struct RetryConfig {
427 pub max_attempts: NonZeroU32,
431 pub min_base_delay: Duration,
435 pub max_base_delay: Duration,
439 pub append_retry_policy: AppendRetryPolicy,
444}
445
446impl Default for RetryConfig {
447 fn default() -> Self {
448 Self {
449 max_attempts: NonZeroU32::new(3).expect("valid non-zero u32"),
450 min_base_delay: Duration::from_millis(100),
451 max_base_delay: Duration::from_secs(1),
452 append_retry_policy: AppendRetryPolicy::All,
453 }
454 }
455}
456
457impl RetryConfig {
458 pub fn new() -> Self {
460 Self::default()
461 }
462
463 pub(crate) fn max_retries(&self) -> u32 {
464 self.max_attempts.get() - 1
465 }
466
467 pub fn with_max_attempts(self, max_attempts: NonZeroU32) -> Self {
469 Self {
470 max_attempts,
471 ..self
472 }
473 }
474
475 pub fn with_min_base_delay(self, min_base_delay: Duration) -> Self {
477 Self {
478 min_base_delay,
479 ..self
480 }
481 }
482
483 pub fn with_max_base_delay(self, max_base_delay: Duration) -> Self {
485 Self {
486 max_base_delay,
487 ..self
488 }
489 }
490
491 pub fn with_append_retry_policy(self, append_retry_policy: AppendRetryPolicy) -> Self {
494 Self {
495 append_retry_policy,
496 ..self
497 }
498 }
499}
500
501#[derive(Debug, Clone)]
502#[non_exhaustive]
503pub struct S2Config {
505 pub(crate) access_token: AccessToken,
506 pub(crate) endpoints: S2Endpoints,
507 pub(crate) connection_timeout: Duration,
508 pub(crate) request_timeout: Duration,
509 pub(crate) retry: RetryConfig,
510 pub(crate) compression: Compression,
511 pub(crate) user_agent: HeaderValue,
512 pub(crate) insecure_skip_cert_verification: bool,
513 pub(crate) rustls_crypto_provider: Option<Arc<rustls::crypto::CryptoProvider>>,
514}
515
516impl S2Config {
517 pub fn new(access_token: impl Into<String>) -> Self {
519 Self {
520 access_token: AccessToken::Static(access_token.into().into()),
521 endpoints: S2Endpoints::for_cloud(),
522 connection_timeout: Duration::from_secs(3),
523 request_timeout: Duration::from_secs(5),
524 retry: RetryConfig::new(),
525 compression: Compression::None,
526 user_agent: concat!("s2-sdk-rust/", env!("CARGO_PKG_VERSION"))
527 .parse()
528 .expect("valid user agent"),
529 insecure_skip_cert_verification: false,
530 rustls_crypto_provider: default_rustls_crypto_provider(),
531 }
532 }
533
534 #[cfg(feature = "_hidden")]
535 #[doc(hidden)]
536 pub fn with_access_token_provider(self, provider: impl AccessTokenProvider + 'static) -> Self {
537 Self {
538 access_token: AccessToken::Provider(Arc::new(provider)),
539 ..self
540 }
541 }
542
543 pub fn with_endpoints(self, endpoints: S2Endpoints) -> Self {
545 Self { endpoints, ..self }
546 }
547
548 pub fn with_connection_timeout(self, connection_timeout: Duration) -> Self {
552 Self {
553 connection_timeout,
554 ..self
555 }
556 }
557
558 pub fn with_request_timeout(self, request_timeout: Duration) -> Self {
562 Self {
563 request_timeout,
564 ..self
565 }
566 }
567
568 pub fn with_retry(self, retry: RetryConfig) -> Self {
572 Self { retry, ..self }
573 }
574
575 pub fn with_compression(self, compression: Compression) -> Self {
579 Self {
580 compression,
581 ..self
582 }
583 }
584
585 pub fn with_insecure_skip_cert_verification(self, skip: bool) -> Self {
597 Self {
598 insecure_skip_cert_verification: skip,
599 ..self
600 }
601 }
602
603 pub fn with_rustls_crypto_provider(
613 self,
614 provider: impl Into<Arc<rustls::crypto::CryptoProvider>>,
615 ) -> Self {
616 Self {
617 rustls_crypto_provider: Some(provider.into()),
618 ..self
619 }
620 }
621
622 #[cfg(feature = "rustls-aws-lc-rs")]
626 pub fn with_rustls_aws_lc_rs_crypto_provider(self) -> Self {
627 self.with_rustls_crypto_provider(rustls::crypto::aws_lc_rs::default_provider())
628 }
629
630 #[cfg(feature = "rustls-ring")]
634 pub fn with_rustls_ring_crypto_provider(self) -> Self {
635 self.with_rustls_crypto_provider(rustls::crypto::ring::default_provider())
636 }
637
638 #[doc(hidden)]
639 #[cfg(feature = "_hidden")]
640 pub fn with_user_agent(self, user_agent: impl Into<String>) -> Result<Self, ValidationError> {
641 let user_agent = user_agent
642 .into()
643 .parse()
644 .map_err(|e| ValidationError(format!("invalid user agent: {e}")))?;
645 Ok(Self { user_agent, ..self })
646 }
647}
648
649#[cfg(feature = "rustls-aws-lc-rs")]
650fn default_rustls_crypto_provider() -> Option<Arc<rustls::crypto::CryptoProvider>> {
651 Some(Arc::new(rustls::crypto::aws_lc_rs::default_provider()))
652}
653
654#[cfg(all(not(feature = "rustls-aws-lc-rs"), feature = "rustls-ring"))]
655fn default_rustls_crypto_provider() -> Option<Arc<rustls::crypto::CryptoProvider>> {
656 Some(Arc::new(rustls::crypto::ring::default_provider()))
657}
658
659#[cfg(all(not(feature = "rustls-aws-lc-rs"), not(feature = "rustls-ring")))]
660fn default_rustls_crypto_provider() -> Option<Arc<rustls::crypto::CryptoProvider>> {
661 None
662}
663
664#[derive(Debug, Default, Clone, PartialEq, Eq)]
665#[non_exhaustive]
666pub struct Page<T> {
668 pub values: Vec<T>,
670 pub has_more: bool,
672}
673
674impl<T> Page<T> {
675 pub(crate) fn new(values: impl Into<Vec<T>>, has_more: bool) -> Self {
676 Self {
677 values: values.into(),
678 has_more,
679 }
680 }
681}
682
683#[derive(Debug, Clone, Copy, PartialEq, Eq)]
684pub enum StorageClass {
686 Standard,
688 Express,
690}
691
692impl From<api::config::StorageClass> for StorageClass {
693 fn from(value: api::config::StorageClass) -> Self {
694 match value {
695 api::config::StorageClass::Standard => StorageClass::Standard,
696 api::config::StorageClass::Express => StorageClass::Express,
697 }
698 }
699}
700
701impl From<StorageClass> for api::config::StorageClass {
702 fn from(value: StorageClass) -> Self {
703 match value {
704 StorageClass::Standard => api::config::StorageClass::Standard,
705 StorageClass::Express => api::config::StorageClass::Express,
706 }
707 }
708}
709
710#[derive(Debug, Clone, Copy, PartialEq, Eq)]
711pub enum RetentionPolicy {
713 Age(u64),
715 Infinite,
717}
718
719impl From<api::config::RetentionPolicy> for RetentionPolicy {
720 fn from(value: api::config::RetentionPolicy) -> Self {
721 match value {
722 api::config::RetentionPolicy::Age(secs) => RetentionPolicy::Age(secs),
723 api::config::RetentionPolicy::Infinite(_) => RetentionPolicy::Infinite,
724 }
725 }
726}
727
728impl From<RetentionPolicy> for api::config::RetentionPolicy {
729 fn from(value: RetentionPolicy) -> Self {
730 match value {
731 RetentionPolicy::Age(secs) => api::config::RetentionPolicy::Age(secs),
732 RetentionPolicy::Infinite => {
733 api::config::RetentionPolicy::Infinite(api::config::InfiniteRetention {})
734 }
735 }
736 }
737}
738
739#[derive(Debug, Clone, Copy, PartialEq, Eq)]
740pub enum TimestampingMode {
742 ClientPrefer,
744 ClientRequire,
746 Arrival,
748}
749
750impl From<api::config::TimestampingMode> for TimestampingMode {
751 fn from(value: api::config::TimestampingMode) -> Self {
752 match value {
753 api::config::TimestampingMode::ClientPrefer => TimestampingMode::ClientPrefer,
754 api::config::TimestampingMode::ClientRequire => TimestampingMode::ClientRequire,
755 api::config::TimestampingMode::Arrival => TimestampingMode::Arrival,
756 }
757 }
758}
759
760impl From<TimestampingMode> for api::config::TimestampingMode {
761 fn from(value: TimestampingMode) -> Self {
762 match value {
763 TimestampingMode::ClientPrefer => api::config::TimestampingMode::ClientPrefer,
764 TimestampingMode::ClientRequire => api::config::TimestampingMode::ClientRequire,
765 TimestampingMode::Arrival => api::config::TimestampingMode::Arrival,
766 }
767 }
768}
769
770#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
771#[non_exhaustive]
772pub struct TimestampingConfig {
774 pub mode: Option<TimestampingMode>,
778 pub uncapped: Option<bool>,
782}
783
784impl TimestampingConfig {
785 pub fn new() -> Self {
787 Self::default()
788 }
789
790 pub fn with_mode(self, mode: TimestampingMode) -> Self {
792 Self {
793 mode: Some(mode),
794 ..self
795 }
796 }
797
798 pub fn with_uncapped(self, uncapped: bool) -> Self {
800 Self {
801 uncapped: Some(uncapped),
802 ..self
803 }
804 }
805}
806
807impl From<api::config::TimestampingConfig> for TimestampingConfig {
808 fn from(value: api::config::TimestampingConfig) -> Self {
809 Self {
810 mode: value.mode.map(Into::into),
811 uncapped: value.uncapped,
812 }
813 }
814}
815
816impl From<TimestampingConfig> for api::config::TimestampingConfig {
817 fn from(value: TimestampingConfig) -> Self {
818 Self {
819 mode: value.mode.map(Into::into),
820 uncapped: value.uncapped,
821 }
822 }
823}
824
825#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
826#[non_exhaustive]
827pub struct DeleteOnEmptyConfig {
829 pub min_age_secs: u64,
833}
834
835impl DeleteOnEmptyConfig {
836 pub fn new() -> Self {
838 Self::default()
839 }
840
841 pub fn with_min_age(self, min_age: Duration) -> Self {
843 Self {
844 min_age_secs: min_age.as_secs(),
845 }
846 }
847}
848
849impl From<api::config::DeleteOnEmptyConfig> for DeleteOnEmptyConfig {
850 fn from(value: api::config::DeleteOnEmptyConfig) -> Self {
851 Self {
852 min_age_secs: value.min_age_secs,
853 }
854 }
855}
856
857impl From<DeleteOnEmptyConfig> for api::config::DeleteOnEmptyConfig {
858 fn from(value: DeleteOnEmptyConfig) -> Self {
859 Self {
860 min_age_secs: value.min_age_secs,
861 }
862 }
863}
864
865#[derive(Debug, Clone, Default, PartialEq, Eq)]
866#[non_exhaustive]
867pub struct StreamConfig {
869 pub storage_class: Option<StorageClass>,
873 pub retention_policy: Option<RetentionPolicy>,
877 pub timestamping: Option<TimestampingConfig>,
881 pub delete_on_empty: Option<DeleteOnEmptyConfig>,
885}
886
887impl StreamConfig {
888 pub fn new() -> Self {
890 Self::default()
891 }
892
893 pub fn with_storage_class(self, storage_class: StorageClass) -> Self {
895 Self {
896 storage_class: Some(storage_class),
897 ..self
898 }
899 }
900
901 pub fn with_retention_policy(self, retention_policy: RetentionPolicy) -> Self {
903 Self {
904 retention_policy: Some(retention_policy),
905 ..self
906 }
907 }
908
909 pub fn with_timestamping(self, timestamping: TimestampingConfig) -> Self {
911 Self {
912 timestamping: Some(timestamping),
913 ..self
914 }
915 }
916
917 pub fn with_delete_on_empty(self, delete_on_empty: DeleteOnEmptyConfig) -> Self {
919 Self {
920 delete_on_empty: Some(delete_on_empty),
921 ..self
922 }
923 }
924}
925
926impl From<api::config::StreamConfig> for StreamConfig {
927 fn from(value: api::config::StreamConfig) -> Self {
928 Self {
929 storage_class: value.storage_class.map(Into::into),
930 retention_policy: value.retention_policy.map(Into::into),
931 timestamping: value.timestamping.map(Into::into),
932 delete_on_empty: value.delete_on_empty.map(Into::into),
933 }
934 }
935}
936
937impl From<StreamConfig> for api::config::StreamConfig {
938 fn from(value: StreamConfig) -> Self {
939 Self {
940 storage_class: value.storage_class.map(Into::into),
941 retention_policy: value.retention_policy.map(Into::into),
942 timestamping: value.timestamping.map(Into::into),
943 delete_on_empty: value.delete_on_empty.map(Into::into),
944 }
945 }
946}
947
948#[derive(Debug, Clone, Default, PartialEq, Eq)]
949#[non_exhaustive]
950pub struct BasinConfig {
952 pub default_stream_config: Option<StreamConfig>,
956 pub stream_cipher: Option<EncryptionAlgorithm>,
958 pub create_stream_on_append: bool,
962 pub create_stream_on_read: bool,
966}
967
968impl BasinConfig {
969 pub fn new() -> Self {
971 Self::default()
972 }
973
974 pub fn with_default_stream_config(self, config: StreamConfig) -> Self {
976 Self {
977 default_stream_config: Some(config),
978 ..self
979 }
980 }
981
982 pub fn with_stream_cipher(self, stream_cipher: EncryptionAlgorithm) -> Self {
984 Self {
985 stream_cipher: Some(stream_cipher),
986 ..self
987 }
988 }
989
990 pub fn with_create_stream_on_append(self, create_stream_on_append: bool) -> Self {
993 Self {
994 create_stream_on_append,
995 ..self
996 }
997 }
998
999 pub fn with_create_stream_on_read(self, create_stream_on_read: bool) -> Self {
1001 Self {
1002 create_stream_on_read,
1003 ..self
1004 }
1005 }
1006}
1007
1008impl From<api::config::BasinConfig> for BasinConfig {
1009 fn from(value: api::config::BasinConfig) -> Self {
1010 Self {
1011 default_stream_config: value.default_stream_config.map(Into::into),
1012 stream_cipher: value.stream_cipher.map(Into::into),
1013 create_stream_on_append: value.create_stream_on_append,
1014 create_stream_on_read: value.create_stream_on_read,
1015 }
1016 }
1017}
1018
1019impl From<BasinConfig> for api::config::BasinConfig {
1020 fn from(value: BasinConfig) -> Self {
1021 Self {
1022 default_stream_config: value.default_stream_config.map(Into::into),
1023 stream_cipher: value.stream_cipher.map(Into::into),
1024 create_stream_on_append: value.create_stream_on_append,
1025 create_stream_on_read: value.create_stream_on_read,
1026 }
1027 }
1028}
1029
1030#[derive(Debug, Clone)]
1031#[non_exhaustive]
1032pub struct CreateBasinInput {
1034 pub name: BasinName,
1036 pub config: Option<BasinConfig>,
1040 pub location: Option<LocationName>,
1044 idempotency_token: String,
1045}
1046
1047impl CreateBasinInput {
1048 pub fn new(name: BasinName) -> Self {
1050 Self {
1051 name,
1052 config: None,
1053 location: None,
1054 idempotency_token: idempotency_token(),
1055 }
1056 }
1057
1058 pub fn with_config(self, config: BasinConfig) -> Self {
1060 Self {
1061 config: Some(config),
1062 ..self
1063 }
1064 }
1065
1066 pub fn with_location<S>(self, location: S) -> Result<Self, ValidationError>
1068 where
1069 S: TryInto<LocationName>,
1070 S::Error: fmt::Display,
1071 {
1072 let location = location
1073 .try_into()
1074 .map_err(|e| ValidationError(e.to_string()))?;
1075 Ok(Self {
1076 location: Some(location),
1077 ..self
1078 })
1079 }
1080}
1081
1082impl From<CreateBasinInput> for (api::basin::CreateBasinRequest, String) {
1083 fn from(value: CreateBasinInput) -> Self {
1084 (
1085 api::basin::CreateBasinRequest {
1086 basin: value.name,
1087 config: value.config.map(Into::into),
1088 location: value.location,
1089 },
1090 value.idempotency_token,
1091 )
1092 }
1093}
1094
1095#[derive(Debug, Clone)]
1096#[non_exhaustive]
1097pub struct EnsureBasinInput {
1099 pub name: BasinName,
1101 pub config: Option<BasinConfig>,
1105 pub location: Option<LocationName>,
1110}
1111
1112impl EnsureBasinInput {
1113 pub fn new(name: BasinName) -> Self {
1115 Self {
1116 name,
1117 config: None,
1118 location: None,
1119 }
1120 }
1121
1122 pub fn with_config(self, config: BasinConfig) -> Self {
1124 Self {
1125 config: Some(config),
1126 ..self
1127 }
1128 }
1129
1130 pub fn with_location<S>(self, location: S) -> Result<Self, ValidationError>
1132 where
1133 S: TryInto<LocationName>,
1134 S::Error: fmt::Display,
1135 {
1136 let location = location
1137 .try_into()
1138 .map_err(|e| ValidationError(e.to_string()))?;
1139 Ok(Self {
1140 location: Some(location),
1141 ..self
1142 })
1143 }
1144}
1145
1146impl From<EnsureBasinInput> for (BasinName, Option<api::basin::EnsureBasinRequest>) {
1147 fn from(value: EnsureBasinInput) -> Self {
1148 let config = value.config;
1149 let request = if config.is_some() || value.location.is_some() {
1150 Some(api::basin::EnsureBasinRequest {
1151 config: config.map(Into::into),
1152 location: value.location,
1153 })
1154 } else {
1155 None
1156 };
1157 (value.name, request)
1158 }
1159}
1160
1161#[derive(Debug, Clone)]
1162pub enum EnsureOutput<T> {
1165 Created(T),
1167 ConfigUpdated(T),
1169 ConfigUnchanged(T),
1171}
1172
1173impl<T> From<ProvisionResult<T>> for EnsureOutput<T> {
1174 fn from(result: ProvisionResult<T>) -> Self {
1175 match result {
1176 ProvisionResult::Created(info) => EnsureOutput::Created(info),
1177 ProvisionResult::Updated(info) => EnsureOutput::ConfigUpdated(info),
1178 ProvisionResult::Noop(info) => EnsureOutput::ConfigUnchanged(info),
1179 }
1180 }
1181}
1182
1183#[derive(Debug, Clone, Default)]
1184#[non_exhaustive]
1185pub struct ListBasinsInput {
1187 pub prefix: BasinNamePrefix,
1191 pub start_after: BasinNameStartAfter,
1195 pub limit: Option<usize>,
1199}
1200
1201impl ListBasinsInput {
1202 pub fn new() -> Self {
1204 Self::default()
1205 }
1206
1207 pub fn with_prefix(self, prefix: BasinNamePrefix) -> Self {
1209 Self { prefix, ..self }
1210 }
1211
1212 pub fn with_start_after(self, start_after: BasinNameStartAfter) -> Self {
1215 Self {
1216 start_after,
1217 ..self
1218 }
1219 }
1220
1221 pub fn with_limit(self, limit: usize) -> Self {
1223 Self {
1224 limit: Some(limit),
1225 ..self
1226 }
1227 }
1228}
1229
1230impl From<ListBasinsInput> for api::basin::ListBasinsRequest {
1231 fn from(value: ListBasinsInput) -> Self {
1232 Self {
1233 prefix: Some(value.prefix),
1234 start_after: Some(value.start_after),
1235 limit: value.limit,
1236 }
1237 }
1238}
1239
1240#[derive(Debug, Clone, Default)]
1241pub struct ListAllBasinsInput {
1243 pub prefix: BasinNamePrefix,
1247 pub start_after: BasinNameStartAfter,
1251 pub include_deleted: bool,
1255}
1256
1257impl ListAllBasinsInput {
1258 pub fn new() -> Self {
1260 Self::default()
1261 }
1262
1263 pub fn with_prefix(self, prefix: BasinNamePrefix) -> Self {
1265 Self { prefix, ..self }
1266 }
1267
1268 pub fn with_start_after(self, start_after: BasinNameStartAfter) -> Self {
1271 Self {
1272 start_after,
1273 ..self
1274 }
1275 }
1276
1277 pub fn with_include_deleted(self, include_deleted: bool) -> Self {
1279 Self {
1280 include_deleted,
1281 ..self
1282 }
1283 }
1284}
1285
1286#[derive(Debug, Clone, PartialEq, Eq)]
1287#[non_exhaustive]
1288pub struct BasinInfo {
1290 pub name: BasinName,
1292 pub location: Option<LocationName>,
1294 pub created_at: S2DateTime,
1296 pub deleted_at: Option<S2DateTime>,
1298}
1299
1300impl TryFrom<api::basin::BasinInfo> for BasinInfo {
1301 type Error = ValidationError;
1302
1303 fn try_from(value: api::basin::BasinInfo) -> Result<Self, Self::Error> {
1304 Ok(Self {
1305 name: value.name,
1306 location: value.location,
1307 created_at: value.created_at.try_into()?,
1308 deleted_at: value.deleted_at.map(S2DateTime::try_from).transpose()?,
1309 })
1310 }
1311}
1312
1313#[derive(Debug, Clone)]
1314#[non_exhaustive]
1315pub struct DeleteBasinInput {
1317 pub name: BasinName,
1319 pub ignore_not_found: bool,
1321}
1322
1323impl DeleteBasinInput {
1324 pub fn new(name: BasinName) -> Self {
1326 Self {
1327 name,
1328 ignore_not_found: false,
1329 }
1330 }
1331
1332 pub fn with_ignore_not_found(self, ignore_not_found: bool) -> Self {
1334 Self {
1335 ignore_not_found,
1336 ..self
1337 }
1338 }
1339}
1340
1341#[derive(Debug, Clone, Default)]
1342#[non_exhaustive]
1343pub struct TimestampingReconfiguration {
1345 pub mode: Maybe<Option<TimestampingMode>>,
1347 pub uncapped: Maybe<Option<bool>>,
1349}
1350
1351impl TimestampingReconfiguration {
1352 pub fn new() -> Self {
1354 Self::default()
1355 }
1356
1357 pub fn with_mode(self, mode: TimestampingMode) -> Self {
1359 Self {
1360 mode: Maybe::Specified(Some(mode)),
1361 ..self
1362 }
1363 }
1364
1365 pub fn with_uncapped(self, uncapped: bool) -> Self {
1367 Self {
1368 uncapped: Maybe::Specified(Some(uncapped)),
1369 ..self
1370 }
1371 }
1372}
1373
1374impl From<TimestampingReconfiguration> for api::config::TimestampingReconfiguration {
1375 fn from(value: TimestampingReconfiguration) -> Self {
1376 Self {
1377 mode: value.mode.map(|m| m.map(Into::into)),
1378 uncapped: value.uncapped,
1379 }
1380 }
1381}
1382
1383#[derive(Debug, Clone, Default)]
1384#[non_exhaustive]
1385pub struct DeleteOnEmptyReconfiguration {
1387 pub min_age_secs: Maybe<Option<u64>>,
1389}
1390
1391impl DeleteOnEmptyReconfiguration {
1392 pub fn new() -> Self {
1394 Self::default()
1395 }
1396
1397 pub fn with_min_age(self, min_age: Duration) -> Self {
1399 Self {
1400 min_age_secs: Maybe::Specified(Some(min_age.as_secs())),
1401 }
1402 }
1403}
1404
1405impl From<DeleteOnEmptyReconfiguration> for api::config::DeleteOnEmptyReconfiguration {
1406 fn from(value: DeleteOnEmptyReconfiguration) -> Self {
1407 Self {
1408 min_age_secs: value.min_age_secs,
1409 }
1410 }
1411}
1412
1413#[derive(Debug, Clone, Default)]
1414#[non_exhaustive]
1415pub struct StreamReconfiguration {
1417 pub storage_class: Maybe<Option<StorageClass>>,
1419 pub retention_policy: Maybe<Option<RetentionPolicy>>,
1421 pub timestamping: Maybe<Option<TimestampingReconfiguration>>,
1423 pub delete_on_empty: Maybe<Option<DeleteOnEmptyReconfiguration>>,
1425}
1426
1427impl StreamReconfiguration {
1428 pub fn new() -> Self {
1430 Self::default()
1431 }
1432
1433 pub fn with_storage_class(self, storage_class: StorageClass) -> Self {
1435 Self {
1436 storage_class: Maybe::Specified(Some(storage_class)),
1437 ..self
1438 }
1439 }
1440
1441 pub fn with_retention_policy(self, retention_policy: RetentionPolicy) -> Self {
1443 Self {
1444 retention_policy: Maybe::Specified(Some(retention_policy)),
1445 ..self
1446 }
1447 }
1448
1449 pub fn with_timestamping(self, timestamping: TimestampingReconfiguration) -> Self {
1451 Self {
1452 timestamping: Maybe::Specified(Some(timestamping)),
1453 ..self
1454 }
1455 }
1456
1457 pub fn with_delete_on_empty(self, delete_on_empty: DeleteOnEmptyReconfiguration) -> Self {
1459 Self {
1460 delete_on_empty: Maybe::Specified(Some(delete_on_empty)),
1461 ..self
1462 }
1463 }
1464}
1465
1466impl From<StreamReconfiguration> for api::config::StreamReconfiguration {
1467 fn from(value: StreamReconfiguration) -> Self {
1468 Self {
1469 storage_class: value.storage_class.map(|m| m.map(Into::into)),
1470 retention_policy: value.retention_policy.map(|m| m.map(Into::into)),
1471 timestamping: value.timestamping.map(|m| m.map(Into::into)),
1472 delete_on_empty: value.delete_on_empty.map(|m| m.map(Into::into)),
1473 }
1474 }
1475}
1476
1477#[derive(Debug, Clone, Default)]
1478#[non_exhaustive]
1479pub struct BasinReconfiguration {
1481 pub default_stream_config: Maybe<Option<StreamReconfiguration>>,
1483 pub stream_cipher: Maybe<Option<EncryptionAlgorithm>>,
1485 pub create_stream_on_append: Maybe<bool>,
1488 pub create_stream_on_read: Maybe<bool>,
1490}
1491
1492impl BasinReconfiguration {
1493 pub fn new() -> Self {
1495 Self::default()
1496 }
1497
1498 pub fn with_default_stream_config(self, config: StreamReconfiguration) -> Self {
1501 Self {
1502 default_stream_config: Maybe::Specified(Some(config)),
1503 ..self
1504 }
1505 }
1506
1507 pub fn with_stream_cipher(self, stream_cipher: EncryptionAlgorithm) -> Self {
1509 Self {
1510 stream_cipher: Maybe::Specified(Some(stream_cipher)),
1511 ..self
1512 }
1513 }
1514
1515 pub fn with_create_stream_on_append(self, create_stream_on_append: bool) -> Self {
1518 Self {
1519 create_stream_on_append: Maybe::Specified(create_stream_on_append),
1520 ..self
1521 }
1522 }
1523
1524 pub fn with_create_stream_on_read(self, create_stream_on_read: bool) -> Self {
1527 Self {
1528 create_stream_on_read: Maybe::Specified(create_stream_on_read),
1529 ..self
1530 }
1531 }
1532}
1533
1534impl From<BasinReconfiguration> for api::config::BasinReconfiguration {
1535 fn from(value: BasinReconfiguration) -> Self {
1536 Self {
1537 default_stream_config: value.default_stream_config.map(|m| m.map(Into::into)),
1538 stream_cipher: value.stream_cipher.map(|m| m.map(Into::into)),
1539 create_stream_on_append: value.create_stream_on_append,
1540 create_stream_on_read: value.create_stream_on_read,
1541 }
1542 }
1543}
1544
1545#[derive(Debug, Clone)]
1546#[non_exhaustive]
1547pub struct ReconfigureBasinInput {
1549 pub name: BasinName,
1551 pub config: BasinReconfiguration,
1553}
1554
1555impl ReconfigureBasinInput {
1556 pub fn new(name: BasinName, config: BasinReconfiguration) -> Self {
1558 Self { name, config }
1559 }
1560}
1561
1562#[derive(Debug, Clone, Default)]
1563#[non_exhaustive]
1564pub struct ListAccessTokensInput {
1566 pub prefix: AccessTokenIdPrefix,
1570 pub start_after: AccessTokenIdStartAfter,
1574 pub limit: Option<usize>,
1578}
1579
1580impl ListAccessTokensInput {
1581 pub fn new() -> Self {
1583 Self::default()
1584 }
1585
1586 pub fn with_prefix(self, prefix: AccessTokenIdPrefix) -> Self {
1588 Self { prefix, ..self }
1589 }
1590
1591 pub fn with_start_after(self, start_after: AccessTokenIdStartAfter) -> Self {
1594 Self {
1595 start_after,
1596 ..self
1597 }
1598 }
1599
1600 pub fn with_limit(self, limit: usize) -> Self {
1602 Self {
1603 limit: Some(limit),
1604 ..self
1605 }
1606 }
1607}
1608
1609impl From<ListAccessTokensInput> for api::access::ListAccessTokensRequest {
1610 fn from(value: ListAccessTokensInput) -> Self {
1611 Self {
1612 prefix: Some(value.prefix),
1613 start_after: Some(value.start_after),
1614 limit: value.limit,
1615 }
1616 }
1617}
1618
1619#[derive(Debug, Clone, Default)]
1620pub struct ListAllAccessTokensInput {
1622 pub prefix: AccessTokenIdPrefix,
1626 pub start_after: AccessTokenIdStartAfter,
1630}
1631
1632impl ListAllAccessTokensInput {
1633 pub fn new() -> Self {
1635 Self::default()
1636 }
1637
1638 pub fn with_prefix(self, prefix: AccessTokenIdPrefix) -> Self {
1640 Self { prefix, ..self }
1641 }
1642
1643 pub fn with_start_after(self, start_after: AccessTokenIdStartAfter) -> Self {
1646 Self {
1647 start_after,
1648 ..self
1649 }
1650 }
1651}
1652
1653#[derive(Debug, Clone, PartialEq, Eq)]
1654#[non_exhaustive]
1655pub struct LocationInfo {
1657 pub name: LocationName,
1659 pub is_private: bool,
1661}
1662
1663impl From<api::location::LocationInfo> for LocationInfo {
1664 fn from(value: api::location::LocationInfo) -> Self {
1665 Self {
1666 name: value.name,
1667 is_private: value.is_private,
1668 }
1669 }
1670}
1671
1672#[derive(Debug, Clone)]
1673#[non_exhaustive]
1674pub struct AccessTokenInfo {
1676 pub id: AccessTokenId,
1678 pub expires_at: Option<S2DateTime>,
1680 pub auto_prefix_streams: bool,
1683 pub scope: AccessTokenScope,
1685}
1686
1687impl TryFrom<api::access::AccessTokenInfo> for AccessTokenInfo {
1688 type Error = ValidationError;
1689
1690 fn try_from(value: api::access::AccessTokenInfo) -> Result<Self, Self::Error> {
1691 let expires_at = value.expires_at.map(S2DateTime::try_from).transpose()?;
1692 Ok(Self {
1693 id: value.id,
1694 expires_at,
1695 auto_prefix_streams: value.auto_prefix_streams,
1696 scope: value.scope.into(),
1697 })
1698 }
1699}
1700
1701#[derive(Debug, Clone)]
1702pub enum BasinMatcher {
1706 None,
1708 Exact(BasinName),
1710 Prefix(BasinNamePrefix),
1712}
1713
1714#[derive(Debug, Clone)]
1715pub enum StreamMatcher {
1719 None,
1721 Exact(StreamName),
1723 Prefix(StreamNamePrefix),
1725}
1726
1727#[derive(Debug, Clone)]
1728pub enum AccessTokenMatcher {
1732 None,
1734 Exact(AccessTokenId),
1736 Prefix(AccessTokenIdPrefix),
1738}
1739
1740#[derive(Debug, Clone, Default)]
1741#[non_exhaustive]
1742pub struct ReadWritePermissions {
1744 pub read: bool,
1748 pub write: bool,
1752}
1753
1754impl ReadWritePermissions {
1755 pub fn new() -> Self {
1757 Self::default()
1758 }
1759
1760 pub fn read_only() -> Self {
1762 Self {
1763 read: true,
1764 write: false,
1765 }
1766 }
1767
1768 pub fn write_only() -> Self {
1770 Self {
1771 read: false,
1772 write: true,
1773 }
1774 }
1775
1776 pub fn read_write() -> Self {
1778 Self {
1779 read: true,
1780 write: true,
1781 }
1782 }
1783}
1784
1785impl From<ReadWritePermissions> for api::access::ReadWritePermissions {
1786 fn from(value: ReadWritePermissions) -> Self {
1787 Self {
1788 read: Some(value.read),
1789 write: Some(value.write),
1790 }
1791 }
1792}
1793
1794impl From<api::access::ReadWritePermissions> for ReadWritePermissions {
1795 fn from(value: api::access::ReadWritePermissions) -> Self {
1796 Self {
1797 read: value.read.unwrap_or_default(),
1798 write: value.write.unwrap_or_default(),
1799 }
1800 }
1801}
1802
1803#[derive(Debug, Clone, Default)]
1804#[non_exhaustive]
1805pub struct OperationGroupPermissions {
1809 pub account: Option<ReadWritePermissions>,
1813 pub basin: Option<ReadWritePermissions>,
1817 pub stream: Option<ReadWritePermissions>,
1821}
1822
1823impl OperationGroupPermissions {
1824 pub fn new() -> Self {
1826 Self::default()
1827 }
1828
1829 pub fn read_only_all() -> Self {
1831 Self {
1832 account: Some(ReadWritePermissions::read_only()),
1833 basin: Some(ReadWritePermissions::read_only()),
1834 stream: Some(ReadWritePermissions::read_only()),
1835 }
1836 }
1837
1838 pub fn write_only_all() -> Self {
1840 Self {
1841 account: Some(ReadWritePermissions::write_only()),
1842 basin: Some(ReadWritePermissions::write_only()),
1843 stream: Some(ReadWritePermissions::write_only()),
1844 }
1845 }
1846
1847 pub fn read_write_all() -> Self {
1849 Self {
1850 account: Some(ReadWritePermissions::read_write()),
1851 basin: Some(ReadWritePermissions::read_write()),
1852 stream: Some(ReadWritePermissions::read_write()),
1853 }
1854 }
1855
1856 pub fn with_account(self, account: ReadWritePermissions) -> Self {
1858 Self {
1859 account: Some(account),
1860 ..self
1861 }
1862 }
1863
1864 pub fn with_basin(self, basin: ReadWritePermissions) -> Self {
1866 Self {
1867 basin: Some(basin),
1868 ..self
1869 }
1870 }
1871
1872 pub fn with_stream(self, stream: ReadWritePermissions) -> Self {
1874 Self {
1875 stream: Some(stream),
1876 ..self
1877 }
1878 }
1879}
1880
1881impl From<OperationGroupPermissions> for api::access::PermittedOperationGroups {
1882 fn from(value: OperationGroupPermissions) -> Self {
1883 Self {
1884 account: value.account.map(Into::into),
1885 basin: value.basin.map(Into::into),
1886 stream: value.stream.map(Into::into),
1887 }
1888 }
1889}
1890
1891impl From<api::access::PermittedOperationGroups> for OperationGroupPermissions {
1892 fn from(value: api::access::PermittedOperationGroups) -> Self {
1893 Self {
1894 account: value.account.map(Into::into),
1895 basin: value.basin.map(Into::into),
1896 stream: value.stream.map(Into::into),
1897 }
1898 }
1899}
1900
1901#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1902pub enum Operation {
1906 ListBasins,
1908 CreateBasin,
1910 GetBasinConfig,
1912 DeleteBasin,
1914 ReconfigureBasin,
1916 ListAccessTokens,
1918 IssueAccessToken,
1920 RevokeAccessToken,
1922 GetAccountMetrics,
1924 GetBasinMetrics,
1926 GetStreamMetrics,
1928 ListStreams,
1930 CreateStream,
1932 GetStreamConfig,
1934 DeleteStream,
1936 ReconfigureStream,
1938 CheckTail,
1940 Append,
1942 Read,
1944 Trim,
1946 Fence,
1948 ListLocations,
1950 GetDefaultLocation,
1952 SetDefaultLocation,
1954}
1955
1956impl From<Operation> for api::access::Operation {
1957 fn from(value: Operation) -> Self {
1958 match value {
1959 Operation::ListBasins => api::access::Operation::ListBasins,
1960 Operation::CreateBasin => api::access::Operation::CreateBasin,
1961 Operation::DeleteBasin => api::access::Operation::DeleteBasin,
1962 Operation::ReconfigureBasin => api::access::Operation::ReconfigureBasin,
1963 Operation::GetBasinConfig => api::access::Operation::GetBasinConfig,
1964 Operation::IssueAccessToken => api::access::Operation::IssueAccessToken,
1965 Operation::RevokeAccessToken => api::access::Operation::RevokeAccessToken,
1966 Operation::ListAccessTokens => api::access::Operation::ListAccessTokens,
1967 Operation::ListStreams => api::access::Operation::ListStreams,
1968 Operation::CreateStream => api::access::Operation::CreateStream,
1969 Operation::DeleteStream => api::access::Operation::DeleteStream,
1970 Operation::GetStreamConfig => api::access::Operation::GetStreamConfig,
1971 Operation::ReconfigureStream => api::access::Operation::ReconfigureStream,
1972 Operation::CheckTail => api::access::Operation::CheckTail,
1973 Operation::Append => api::access::Operation::Append,
1974 Operation::Read => api::access::Operation::Read,
1975 Operation::Trim => api::access::Operation::Trim,
1976 Operation::Fence => api::access::Operation::Fence,
1977 Operation::GetAccountMetrics => api::access::Operation::AccountMetrics,
1978 Operation::GetBasinMetrics => api::access::Operation::BasinMetrics,
1979 Operation::GetStreamMetrics => api::access::Operation::StreamMetrics,
1980 Operation::ListLocations => api::access::Operation::ListLocations,
1981 Operation::GetDefaultLocation => api::access::Operation::GetDefaultLocation,
1982 Operation::SetDefaultLocation => api::access::Operation::SetDefaultLocation,
1983 }
1984 }
1985}
1986
1987impl From<api::access::Operation> for Operation {
1988 fn from(value: api::access::Operation) -> Self {
1989 match value {
1990 api::access::Operation::ListBasins => Operation::ListBasins,
1991 api::access::Operation::CreateBasin => Operation::CreateBasin,
1992 api::access::Operation::DeleteBasin => Operation::DeleteBasin,
1993 api::access::Operation::ReconfigureBasin => Operation::ReconfigureBasin,
1994 api::access::Operation::GetBasinConfig => Operation::GetBasinConfig,
1995 api::access::Operation::IssueAccessToken => Operation::IssueAccessToken,
1996 api::access::Operation::RevokeAccessToken => Operation::RevokeAccessToken,
1997 api::access::Operation::ListAccessTokens => Operation::ListAccessTokens,
1998 api::access::Operation::ListStreams => Operation::ListStreams,
1999 api::access::Operation::CreateStream => Operation::CreateStream,
2000 api::access::Operation::DeleteStream => Operation::DeleteStream,
2001 api::access::Operation::GetStreamConfig => Operation::GetStreamConfig,
2002 api::access::Operation::ReconfigureStream => Operation::ReconfigureStream,
2003 api::access::Operation::CheckTail => Operation::CheckTail,
2004 api::access::Operation::Append => Operation::Append,
2005 api::access::Operation::Read => Operation::Read,
2006 api::access::Operation::Trim => Operation::Trim,
2007 api::access::Operation::Fence => Operation::Fence,
2008 api::access::Operation::AccountMetrics => Operation::GetAccountMetrics,
2009 api::access::Operation::BasinMetrics => Operation::GetBasinMetrics,
2010 api::access::Operation::StreamMetrics => Operation::GetStreamMetrics,
2011 api::access::Operation::ListLocations => Operation::ListLocations,
2012 api::access::Operation::GetDefaultLocation => Operation::GetDefaultLocation,
2013 api::access::Operation::SetDefaultLocation => Operation::SetDefaultLocation,
2014 }
2015 }
2016}
2017
2018#[derive(Debug, Clone)]
2019#[non_exhaustive]
2020pub struct AccessTokenScopeInput {
2028 basins: Option<BasinMatcher>,
2029 streams: Option<StreamMatcher>,
2030 access_tokens: Option<AccessTokenMatcher>,
2031 op_group_perms: Option<OperationGroupPermissions>,
2032 ops: HashSet<Operation>,
2033}
2034
2035impl AccessTokenScopeInput {
2036 pub fn from_ops(ops: impl IntoIterator<Item = Operation>) -> Self {
2038 Self {
2039 basins: None,
2040 streams: None,
2041 access_tokens: None,
2042 op_group_perms: None,
2043 ops: ops.into_iter().collect(),
2044 }
2045 }
2046
2047 pub fn from_op_group_perms(op_group_perms: OperationGroupPermissions) -> Self {
2049 Self {
2050 basins: None,
2051 streams: None,
2052 access_tokens: None,
2053 op_group_perms: Some(op_group_perms),
2054 ops: HashSet::default(),
2055 }
2056 }
2057
2058 pub fn with_ops(self, ops: impl IntoIterator<Item = Operation>) -> Self {
2060 Self {
2061 ops: ops.into_iter().collect(),
2062 ..self
2063 }
2064 }
2065
2066 pub fn with_op_group_perms(self, op_group_perms: OperationGroupPermissions) -> Self {
2068 Self {
2069 op_group_perms: Some(op_group_perms),
2070 ..self
2071 }
2072 }
2073
2074 pub fn with_basins(self, basins: BasinMatcher) -> Self {
2078 Self {
2079 basins: Some(basins),
2080 ..self
2081 }
2082 }
2083
2084 pub fn with_streams(self, streams: StreamMatcher) -> Self {
2088 Self {
2089 streams: Some(streams),
2090 ..self
2091 }
2092 }
2093
2094 pub fn with_access_tokens(self, access_tokens: AccessTokenMatcher) -> Self {
2098 Self {
2099 access_tokens: Some(access_tokens),
2100 ..self
2101 }
2102 }
2103}
2104
2105#[derive(Debug, Clone)]
2106#[non_exhaustive]
2107pub struct AccessTokenScope {
2109 pub basins: Option<BasinMatcher>,
2111 pub streams: Option<StreamMatcher>,
2113 pub access_tokens: Option<AccessTokenMatcher>,
2115 pub op_group_perms: Option<OperationGroupPermissions>,
2117 pub ops: HashSet<Operation>,
2119}
2120
2121impl From<api::access::AccessTokenScope> for AccessTokenScope {
2122 fn from(value: api::access::AccessTokenScope) -> Self {
2123 Self {
2124 basins: value.basins.map(|rs| match rs {
2125 api::access::ResourceSet::Exact(api::access::MaybeEmpty::NonEmpty(e)) => {
2126 BasinMatcher::Exact(e)
2127 }
2128 api::access::ResourceSet::Exact(api::access::MaybeEmpty::Empty) => {
2129 BasinMatcher::None
2130 }
2131 api::access::ResourceSet::Prefix(p) => BasinMatcher::Prefix(p),
2132 }),
2133 streams: value.streams.map(|rs| match rs {
2134 api::access::ResourceSet::Exact(api::access::MaybeEmpty::NonEmpty(e)) => {
2135 StreamMatcher::Exact(e)
2136 }
2137 api::access::ResourceSet::Exact(api::access::MaybeEmpty::Empty) => {
2138 StreamMatcher::None
2139 }
2140 api::access::ResourceSet::Prefix(p) => StreamMatcher::Prefix(p),
2141 }),
2142 access_tokens: value.access_tokens.map(|rs| match rs {
2143 api::access::ResourceSet::Exact(api::access::MaybeEmpty::NonEmpty(e)) => {
2144 AccessTokenMatcher::Exact(e)
2145 }
2146 api::access::ResourceSet::Exact(api::access::MaybeEmpty::Empty) => {
2147 AccessTokenMatcher::None
2148 }
2149 api::access::ResourceSet::Prefix(p) => AccessTokenMatcher::Prefix(p),
2150 }),
2151 op_group_perms: value.op_groups.map(Into::into),
2152 ops: value
2153 .ops
2154 .map(|ops| ops.into_iter().map(Into::into).collect())
2155 .unwrap_or_default(),
2156 }
2157 }
2158}
2159
2160impl From<AccessTokenScopeInput> for api::access::AccessTokenScope {
2161 fn from(value: AccessTokenScopeInput) -> Self {
2162 Self {
2163 basins: value.basins.map(|rs| match rs {
2164 BasinMatcher::None => {
2165 api::access::ResourceSet::Exact(api::access::MaybeEmpty::Empty)
2166 }
2167 BasinMatcher::Exact(e) => {
2168 api::access::ResourceSet::Exact(api::access::MaybeEmpty::NonEmpty(e))
2169 }
2170 BasinMatcher::Prefix(p) => api::access::ResourceSet::Prefix(p),
2171 }),
2172 streams: value.streams.map(|rs| match rs {
2173 StreamMatcher::None => {
2174 api::access::ResourceSet::Exact(api::access::MaybeEmpty::Empty)
2175 }
2176 StreamMatcher::Exact(e) => {
2177 api::access::ResourceSet::Exact(api::access::MaybeEmpty::NonEmpty(e))
2178 }
2179 StreamMatcher::Prefix(p) => api::access::ResourceSet::Prefix(p),
2180 }),
2181 access_tokens: value.access_tokens.map(|rs| match rs {
2182 AccessTokenMatcher::None => {
2183 api::access::ResourceSet::Exact(api::access::MaybeEmpty::Empty)
2184 }
2185 AccessTokenMatcher::Exact(e) => {
2186 api::access::ResourceSet::Exact(api::access::MaybeEmpty::NonEmpty(e))
2187 }
2188 AccessTokenMatcher::Prefix(p) => api::access::ResourceSet::Prefix(p),
2189 }),
2190 op_groups: value.op_group_perms.map(Into::into),
2191 ops: if value.ops.is_empty() {
2192 None
2193 } else {
2194 Some(value.ops.into_iter().map(Into::into).collect())
2195 },
2196 }
2197 }
2198}
2199
2200#[derive(Debug, Clone)]
2201#[non_exhaustive]
2202pub struct IssueAccessTokenInput {
2204 pub id: AccessTokenId,
2206 pub expires_at: Option<S2DateTime>,
2211 pub auto_prefix_streams: bool,
2219 pub scope: AccessTokenScopeInput,
2221}
2222
2223impl IssueAccessTokenInput {
2224 pub fn new(id: AccessTokenId, scope: AccessTokenScopeInput) -> Self {
2226 Self {
2227 id,
2228 expires_at: None,
2229 auto_prefix_streams: false,
2230 scope,
2231 }
2232 }
2233
2234 pub fn with_expires_at(self, expires_at: S2DateTime) -> Self {
2236 Self {
2237 expires_at: Some(expires_at),
2238 ..self
2239 }
2240 }
2241
2242 pub fn with_auto_prefix_streams(self, auto_prefix_streams: bool) -> Self {
2245 Self {
2246 auto_prefix_streams,
2247 ..self
2248 }
2249 }
2250}
2251
2252impl From<IssueAccessTokenInput> for api::access::IssueAccessTokenRequest {
2253 fn from(value: IssueAccessTokenInput) -> Self {
2254 Self {
2255 id: value.id,
2256 expires_at: value.expires_at.map(Into::into),
2257 auto_prefix_streams: value.auto_prefix_streams.then_some(true),
2258 scope: value.scope.into(),
2259 }
2260 }
2261}
2262
2263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2264pub enum TimeseriesInterval {
2266 Minute,
2268 Hour,
2270 Day,
2272}
2273
2274impl From<TimeseriesInterval> for api::metrics::TimeseriesInterval {
2275 fn from(value: TimeseriesInterval) -> Self {
2276 match value {
2277 TimeseriesInterval::Minute => api::metrics::TimeseriesInterval::Minute,
2278 TimeseriesInterval::Hour => api::metrics::TimeseriesInterval::Hour,
2279 TimeseriesInterval::Day => api::metrics::TimeseriesInterval::Day,
2280 }
2281 }
2282}
2283
2284impl From<api::metrics::TimeseriesInterval> for TimeseriesInterval {
2285 fn from(value: api::metrics::TimeseriesInterval) -> Self {
2286 match value {
2287 api::metrics::TimeseriesInterval::Minute => TimeseriesInterval::Minute,
2288 api::metrics::TimeseriesInterval::Hour => TimeseriesInterval::Hour,
2289 api::metrics::TimeseriesInterval::Day => TimeseriesInterval::Day,
2290 }
2291 }
2292}
2293
2294#[derive(Debug, Clone, Copy)]
2295#[non_exhaustive]
2296pub struct TimeRange {
2298 pub start: u32,
2300 pub end: u32,
2302}
2303
2304impl TimeRange {
2305 pub fn new(start: u32, end: u32) -> Self {
2307 Self { start, end }
2308 }
2309}
2310
2311#[derive(Debug, Clone, Copy)]
2312#[non_exhaustive]
2313pub struct TimeRangeAndInterval {
2315 pub start: u32,
2317 pub end: u32,
2319 pub interval: Option<TimeseriesInterval>,
2323}
2324
2325impl TimeRangeAndInterval {
2326 pub fn new(start: u32, end: u32) -> Self {
2328 Self {
2329 start,
2330 end,
2331 interval: None,
2332 }
2333 }
2334
2335 pub fn with_interval(self, interval: TimeseriesInterval) -> Self {
2337 Self {
2338 interval: Some(interval),
2339 ..self
2340 }
2341 }
2342}
2343
2344#[derive(Debug, Clone, Copy)]
2345pub enum AccountMetricSet {
2347 ActiveBasins(TimeRange),
2350 AccountOps(TimeRangeAndInterval),
2357}
2358
2359#[derive(Debug, Clone)]
2360#[non_exhaustive]
2361pub struct GetAccountMetricsInput {
2363 pub set: AccountMetricSet,
2365}
2366
2367impl GetAccountMetricsInput {
2368 pub fn new(set: AccountMetricSet) -> Self {
2370 Self { set }
2371 }
2372}
2373
2374impl From<GetAccountMetricsInput> for api::metrics::AccountMetricSetRequest {
2375 fn from(value: GetAccountMetricsInput) -> Self {
2376 let (set, start, end, interval) = match value.set {
2377 AccountMetricSet::ActiveBasins(args) => (
2378 api::metrics::AccountMetricSet::ActiveBasins,
2379 args.start,
2380 args.end,
2381 None,
2382 ),
2383 AccountMetricSet::AccountOps(args) => (
2384 api::metrics::AccountMetricSet::AccountOps,
2385 args.start,
2386 args.end,
2387 args.interval,
2388 ),
2389 };
2390 Self {
2391 set,
2392 start: Some(start),
2393 end: Some(end),
2394 interval: interval.map(Into::into),
2395 }
2396 }
2397}
2398
2399#[derive(Debug, Clone, Copy)]
2400pub enum BasinMetricSet {
2402 Storage(TimeRange),
2405 AppendOps(TimeRangeAndInterval),
2413 ReadOps(TimeRangeAndInterval),
2421 ReadThroughput(TimeRangeAndInterval),
2428 AppendThroughput(TimeRangeAndInterval),
2435 BasinOps(TimeRangeAndInterval),
2442}
2443
2444#[derive(Debug, Clone)]
2445#[non_exhaustive]
2446pub struct GetBasinMetricsInput {
2448 pub name: BasinName,
2450 pub set: BasinMetricSet,
2452}
2453
2454impl GetBasinMetricsInput {
2455 pub fn new(name: BasinName, set: BasinMetricSet) -> Self {
2457 Self { name, set }
2458 }
2459}
2460
2461impl From<GetBasinMetricsInput> for (BasinName, api::metrics::BasinMetricSetRequest) {
2462 fn from(value: GetBasinMetricsInput) -> Self {
2463 let (set, start, end, interval) = match value.set {
2464 BasinMetricSet::Storage(args) => (
2465 api::metrics::BasinMetricSet::Storage,
2466 args.start,
2467 args.end,
2468 None,
2469 ),
2470 BasinMetricSet::AppendOps(args) => (
2471 api::metrics::BasinMetricSet::AppendOps,
2472 args.start,
2473 args.end,
2474 args.interval,
2475 ),
2476 BasinMetricSet::ReadOps(args) => (
2477 api::metrics::BasinMetricSet::ReadOps,
2478 args.start,
2479 args.end,
2480 args.interval,
2481 ),
2482 BasinMetricSet::ReadThroughput(args) => (
2483 api::metrics::BasinMetricSet::ReadThroughput,
2484 args.start,
2485 args.end,
2486 args.interval,
2487 ),
2488 BasinMetricSet::AppendThroughput(args) => (
2489 api::metrics::BasinMetricSet::AppendThroughput,
2490 args.start,
2491 args.end,
2492 args.interval,
2493 ),
2494 BasinMetricSet::BasinOps(args) => (
2495 api::metrics::BasinMetricSet::BasinOps,
2496 args.start,
2497 args.end,
2498 args.interval,
2499 ),
2500 };
2501 (
2502 value.name,
2503 api::metrics::BasinMetricSetRequest {
2504 set,
2505 start: Some(start),
2506 end: Some(end),
2507 interval: interval.map(Into::into),
2508 },
2509 )
2510 }
2511}
2512
2513#[derive(Debug, Clone, Copy)]
2514pub enum StreamMetricSet {
2516 Storage(TimeRange),
2519}
2520
2521#[derive(Debug, Clone)]
2522#[non_exhaustive]
2523pub struct GetStreamMetricsInput {
2525 pub basin_name: BasinName,
2527 pub stream_name: StreamName,
2529 pub set: StreamMetricSet,
2531}
2532
2533impl GetStreamMetricsInput {
2534 pub fn new(basin_name: BasinName, stream_name: StreamName, set: StreamMetricSet) -> Self {
2537 Self {
2538 basin_name,
2539 stream_name,
2540 set,
2541 }
2542 }
2543}
2544
2545impl From<GetStreamMetricsInput> for (BasinName, StreamName, api::metrics::StreamMetricSetRequest) {
2546 fn from(value: GetStreamMetricsInput) -> Self {
2547 let (set, start, end, interval) = match value.set {
2548 StreamMetricSet::Storage(args) => (
2549 api::metrics::StreamMetricSet::Storage,
2550 args.start,
2551 args.end,
2552 None,
2553 ),
2554 };
2555 (
2556 value.basin_name,
2557 value.stream_name,
2558 api::metrics::StreamMetricSetRequest {
2559 set,
2560 start: Some(start),
2561 end: Some(end),
2562 interval,
2563 },
2564 )
2565 }
2566}
2567
2568#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2569pub enum MetricUnit {
2571 Bytes,
2573 Operations,
2575}
2576
2577impl From<api::metrics::MetricUnit> for MetricUnit {
2578 fn from(value: api::metrics::MetricUnit) -> Self {
2579 match value {
2580 api::metrics::MetricUnit::Bytes => MetricUnit::Bytes,
2581 api::metrics::MetricUnit::Operations => MetricUnit::Operations,
2582 }
2583 }
2584}
2585
2586#[derive(Debug, Clone)]
2587#[non_exhaustive]
2588pub struct ScalarMetric {
2590 pub name: String,
2592 pub unit: MetricUnit,
2594 pub value: f64,
2596}
2597
2598#[derive(Debug, Clone)]
2599#[non_exhaustive]
2600pub struct AccumulationMetric {
2603 pub name: String,
2605 pub unit: MetricUnit,
2607 pub interval: TimeseriesInterval,
2609 pub values: Vec<(u32, f64)>,
2613}
2614
2615#[derive(Debug, Clone)]
2616#[non_exhaustive]
2617pub struct GaugeMetric {
2619 pub name: String,
2621 pub unit: MetricUnit,
2623 pub values: Vec<(u32, f64)>,
2626}
2627
2628#[derive(Debug, Clone)]
2629#[non_exhaustive]
2630pub struct LabelMetric {
2632 pub name: String,
2634 pub values: Vec<String>,
2636}
2637
2638#[derive(Debug, Clone)]
2639pub enum Metric {
2641 Scalar(ScalarMetric),
2643 Accumulation(AccumulationMetric),
2646 Gauge(GaugeMetric),
2648 Label(LabelMetric),
2650}
2651
2652impl From<api::metrics::Metric> for Metric {
2653 fn from(value: api::metrics::Metric) -> Self {
2654 match value {
2655 api::metrics::Metric::Scalar(sm) => Metric::Scalar(ScalarMetric {
2656 name: sm.name.into(),
2657 unit: sm.unit.into(),
2658 value: sm.value,
2659 }),
2660 api::metrics::Metric::Accumulation(am) => Metric::Accumulation(AccumulationMetric {
2661 name: am.name.into(),
2662 unit: am.unit.into(),
2663 interval: am.interval.into(),
2664 values: am.values,
2665 }),
2666 api::metrics::Metric::Gauge(gm) => Metric::Gauge(GaugeMetric {
2667 name: gm.name.into(),
2668 unit: gm.unit.into(),
2669 values: gm.values,
2670 }),
2671 api::metrics::Metric::Label(lm) => Metric::Label(LabelMetric {
2672 name: lm.name.into(),
2673 values: lm.values,
2674 }),
2675 }
2676 }
2677}
2678
2679#[derive(Debug, Clone, Default)]
2680#[non_exhaustive]
2681pub struct ListStreamsInput {
2683 pub prefix: StreamNamePrefix,
2687 pub start_after: StreamNameStartAfter,
2691 pub limit: Option<usize>,
2695}
2696
2697impl ListStreamsInput {
2698 pub fn new() -> Self {
2700 Self::default()
2701 }
2702
2703 pub fn with_prefix(self, prefix: StreamNamePrefix) -> Self {
2705 Self { prefix, ..self }
2706 }
2707
2708 pub fn with_start_after(self, start_after: StreamNameStartAfter) -> Self {
2711 Self {
2712 start_after,
2713 ..self
2714 }
2715 }
2716
2717 pub fn with_limit(self, limit: usize) -> Self {
2719 Self {
2720 limit: Some(limit),
2721 ..self
2722 }
2723 }
2724}
2725
2726impl From<ListStreamsInput> for api::stream::ListStreamsRequest {
2727 fn from(value: ListStreamsInput) -> Self {
2728 Self {
2729 prefix: Some(value.prefix),
2730 start_after: Some(value.start_after),
2731 limit: value.limit,
2732 }
2733 }
2734}
2735
2736#[derive(Debug, Clone, Default)]
2737pub struct ListAllStreamsInput {
2739 pub prefix: StreamNamePrefix,
2743 pub start_after: StreamNameStartAfter,
2747 pub include_deleted: bool,
2751}
2752
2753impl ListAllStreamsInput {
2754 pub fn new() -> Self {
2756 Self::default()
2757 }
2758
2759 pub fn with_prefix(self, prefix: StreamNamePrefix) -> Self {
2761 Self { prefix, ..self }
2762 }
2763
2764 pub fn with_start_after(self, start_after: StreamNameStartAfter) -> Self {
2767 Self {
2768 start_after,
2769 ..self
2770 }
2771 }
2772
2773 pub fn with_include_deleted(self, include_deleted: bool) -> Self {
2775 Self {
2776 include_deleted,
2777 ..self
2778 }
2779 }
2780}
2781
2782#[derive(Debug, Clone, PartialEq, Eq)]
2783#[non_exhaustive]
2784pub struct StreamInfo {
2786 pub name: StreamName,
2788 pub created_at: S2DateTime,
2790 pub deleted_at: Option<S2DateTime>,
2792 pub cipher: Option<EncryptionAlgorithm>,
2794}
2795
2796impl TryFrom<api::stream::StreamInfo> for StreamInfo {
2797 type Error = ValidationError;
2798
2799 fn try_from(value: api::stream::StreamInfo) -> Result<Self, Self::Error> {
2800 Ok(Self {
2801 name: value.name,
2802 created_at: value.created_at.try_into()?,
2803 deleted_at: value.deleted_at.map(S2DateTime::try_from).transpose()?,
2804 cipher: value.cipher.map(Into::into),
2805 })
2806 }
2807}
2808
2809#[derive(Debug, Clone)]
2810#[non_exhaustive]
2811pub struct CreateStreamInput {
2813 pub name: StreamName,
2815 pub config: Option<StreamConfig>,
2819 idempotency_token: String,
2820}
2821
2822impl CreateStreamInput {
2823 pub fn new(name: StreamName) -> Self {
2825 Self {
2826 name,
2827 config: None,
2828 idempotency_token: idempotency_token(),
2829 }
2830 }
2831
2832 pub fn with_config(self, config: StreamConfig) -> Self {
2834 Self {
2835 config: Some(config),
2836 ..self
2837 }
2838 }
2839}
2840
2841impl From<CreateStreamInput> for (api::stream::CreateStreamRequest, String) {
2842 fn from(value: CreateStreamInput) -> Self {
2843 (
2844 api::stream::CreateStreamRequest {
2845 stream: value.name,
2846 config: value.config.map(Into::into),
2847 },
2848 value.idempotency_token,
2849 )
2850 }
2851}
2852
2853#[derive(Debug, Clone)]
2854#[non_exhaustive]
2855pub struct EnsureStreamInput {
2858 pub name: StreamName,
2860 pub config: Option<StreamConfig>,
2864}
2865
2866impl EnsureStreamInput {
2867 pub fn new(name: StreamName) -> Self {
2869 Self { name, config: None }
2870 }
2871
2872 pub fn with_config(self, config: StreamConfig) -> Self {
2874 Self {
2875 config: Some(config),
2876 ..self
2877 }
2878 }
2879}
2880
2881impl From<EnsureStreamInput> for (StreamName, Option<api::config::StreamConfig>) {
2882 fn from(value: EnsureStreamInput) -> Self {
2883 (value.name, value.config.map(Into::into))
2884 }
2885}
2886
2887#[derive(Debug, Clone)]
2888#[non_exhaustive]
2889pub struct DeleteStreamInput {
2891 pub name: StreamName,
2893 pub ignore_not_found: bool,
2895}
2896
2897impl DeleteStreamInput {
2898 pub fn new(name: StreamName) -> Self {
2900 Self {
2901 name,
2902 ignore_not_found: false,
2903 }
2904 }
2905
2906 pub fn with_ignore_not_found(self, ignore_not_found: bool) -> Self {
2908 Self {
2909 ignore_not_found,
2910 ..self
2911 }
2912 }
2913}
2914
2915#[derive(Debug, Clone)]
2916#[non_exhaustive]
2917pub struct ReconfigureStreamInput {
2919 pub name: StreamName,
2921 pub config: StreamReconfiguration,
2923}
2924
2925impl ReconfigureStreamInput {
2926 pub fn new(name: StreamName, config: StreamReconfiguration) -> Self {
2928 Self { name, config }
2929 }
2930}
2931
2932#[derive(Debug, Clone, PartialEq, Eq)]
2933pub struct FencingToken(String);
2939
2940impl FencingToken {
2941 pub(crate) fn from_server(value: String) -> Self {
2942 Self(value)
2943 }
2944
2945 pub fn generate(n: usize) -> Result<Self, ValidationError> {
2947 rand::rng()
2948 .sample_iter(&rand::distr::Alphanumeric)
2949 .take(n)
2950 .map(char::from)
2951 .collect::<String>()
2952 .parse()
2953 }
2954}
2955
2956impl FromStr for FencingToken {
2957 type Err = ValidationError;
2958
2959 fn from_str(s: &str) -> Result<Self, Self::Err> {
2960 if s.len() > MAX_FENCING_TOKEN_LENGTH {
2961 return Err(ValidationError(format!(
2962 "fencing token exceeds {MAX_FENCING_TOKEN_LENGTH} bytes in length",
2963 )));
2964 }
2965 Ok(FencingToken(s.to_string()))
2966 }
2967}
2968
2969impl std::fmt::Display for FencingToken {
2970 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2971 write!(f, "{}", self.0)
2972 }
2973}
2974
2975impl Deref for FencingToken {
2976 type Target = str;
2977
2978 fn deref(&self) -> &Self::Target {
2979 &self.0
2980 }
2981}
2982
2983#[derive(Debug, Clone, Copy, PartialEq)]
2984#[non_exhaustive]
2985pub struct StreamPosition {
2987 pub seq_num: u64,
2989 pub timestamp: u64,
2992}
2993
2994impl StreamPosition {
2995 pub fn new(seq_num: u64, timestamp: u64) -> Self {
2999 Self { seq_num, timestamp }
3000 }
3001}
3002
3003impl std::fmt::Display for StreamPosition {
3004 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3005 write!(f, "seq_num={}, timestamp={}", self.seq_num, self.timestamp)
3006 }
3007}
3008
3009impl From<api::stream::proto::StreamPosition> for StreamPosition {
3010 fn from(value: api::stream::proto::StreamPosition) -> Self {
3011 Self {
3012 seq_num: value.seq_num,
3013 timestamp: value.timestamp,
3014 }
3015 }
3016}
3017
3018impl From<api::stream::StreamPosition> for StreamPosition {
3019 fn from(value: api::stream::StreamPosition) -> Self {
3020 Self {
3021 seq_num: value.seq_num,
3022 timestamp: value.timestamp,
3023 }
3024 }
3025}
3026
3027#[derive(Debug, Clone, PartialEq)]
3028#[non_exhaustive]
3029pub struct Header {
3031 pub name: Bytes,
3033 pub value: Bytes,
3035}
3036
3037impl Header {
3038 pub fn new(name: impl Into<Bytes>, value: impl Into<Bytes>) -> Self {
3040 Self {
3041 name: name.into(),
3042 value: value.into(),
3043 }
3044 }
3045}
3046
3047impl From<Header> for api::stream::proto::Header {
3048 fn from(value: Header) -> Self {
3049 Self {
3050 name: value.name,
3051 value: value.value,
3052 }
3053 }
3054}
3055
3056impl From<api::stream::proto::Header> for Header {
3057 fn from(value: api::stream::proto::Header) -> Self {
3058 Self {
3059 name: value.name,
3060 value: value.value,
3061 }
3062 }
3063}
3064
3065#[derive(Debug, Clone, PartialEq)]
3066pub struct AppendRecord {
3068 body: Bytes,
3069 headers: Vec<Header>,
3070 timestamp: Option<u64>,
3071}
3072
3073impl AppendRecord {
3074 fn validate(self) -> Result<Self, ValidationError> {
3075 if self.metered_bytes() > RECORD_BATCH_MAX.bytes {
3076 Err(ValidationError(format!(
3077 "metered_bytes: {} exceeds {}",
3078 self.metered_bytes(),
3079 RECORD_BATCH_MAX.bytes
3080 )))
3081 } else {
3082 Ok(self)
3083 }
3084 }
3085
3086 pub fn new(body: impl Into<Bytes>) -> Result<Self, ValidationError> {
3088 let record = Self {
3089 body: body.into(),
3090 headers: Vec::default(),
3091 timestamp: None,
3092 };
3093 record.validate()
3094 }
3095
3096 pub fn with_headers(
3098 self,
3099 headers: impl IntoIterator<Item = Header>,
3100 ) -> Result<Self, ValidationError> {
3101 let record = Self {
3102 headers: headers.into_iter().collect(),
3103 ..self
3104 };
3105 record.validate()
3106 }
3107
3108 pub fn with_timestamp(self, timestamp: u64) -> Self {
3112 Self {
3113 timestamp: Some(timestamp),
3114 ..self
3115 }
3116 }
3117
3118 pub fn body(&self) -> &[u8] {
3120 &self.body
3121 }
3122
3123 pub fn headers(&self) -> &[Header] {
3125 &self.headers
3126 }
3127
3128 pub fn timestamp(&self) -> Option<u64> {
3130 self.timestamp
3131 }
3132}
3133
3134impl From<AppendRecord> for api::stream::proto::AppendRecord {
3135 fn from(value: AppendRecord) -> Self {
3136 Self {
3137 timestamp: value.timestamp,
3138 headers: value.headers.into_iter().map(Into::into).collect(),
3139 body: value.body,
3140 }
3141 }
3142}
3143
3144pub trait MeteredBytes {
3151 fn metered_bytes(&self) -> usize;
3153}
3154
3155macro_rules! metered_bytes_impl {
3156 ($ty:ty) => {
3157 impl MeteredBytes for $ty {
3158 fn metered_bytes(&self) -> usize {
3159 8 + (2 * self.headers.len())
3160 + self
3161 .headers
3162 .iter()
3163 .map(|h| h.name.len() + h.value.len())
3164 .sum::<usize>()
3165 + self.body.len()
3166 }
3167 }
3168 };
3169}
3170
3171metered_bytes_impl!(AppendRecord);
3172
3173impl MeteredSize for AppendRecord {
3174 fn metered_size(&self) -> usize {
3175 self.metered_bytes()
3176 }
3177}
3178
3179#[derive(Debug, Clone)]
3180pub struct AppendRecordBatch(Metered<Vec<AppendRecord>>);
3189
3190impl From<Metered<Vec<AppendRecord>>> for AppendRecordBatch {
3191 fn from(records: Metered<Vec<AppendRecord>>) -> Self {
3192 Self(records)
3193 }
3194}
3195
3196impl AppendRecordBatch {
3197 pub fn try_from_iter<I>(iter: I) -> Result<Self, ValidationError>
3199 where
3200 I: IntoIterator<Item = AppendRecord>,
3201 {
3202 let mut records = Metered::with_capacity(RECORD_BATCH_MAX.count);
3203
3204 for record in iter {
3205 records.push(Metered::from(record));
3206
3207 if records.metered_size() > RECORD_BATCH_MAX.bytes {
3208 return Err(ValidationError(format!(
3209 "batch size in metered bytes ({}) exceeds {}",
3210 records.metered_size(),
3211 RECORD_BATCH_MAX.bytes
3212 )));
3213 }
3214
3215 if records.len() > RECORD_BATCH_MAX.count {
3216 return Err(ValidationError(format!(
3217 "number of records in the batch exceeds {}",
3218 RECORD_BATCH_MAX.count
3219 )));
3220 }
3221 }
3222
3223 if records.is_empty() {
3224 return Err(ValidationError("batch is empty".into()));
3225 }
3226
3227 Ok(records.into())
3228 }
3229}
3230
3231impl Deref for AppendRecordBatch {
3232 type Target = [AppendRecord];
3233
3234 fn deref(&self) -> &Self::Target {
3235 &self.0[..]
3236 }
3237}
3238
3239impl MeteredBytes for AppendRecordBatch {
3240 fn metered_bytes(&self) -> usize {
3241 self.0.metered_size()
3242 }
3243}
3244
3245impl IntoIterator for AppendRecordBatch {
3246 type Item = AppendRecord;
3247 type IntoIter = std::vec::IntoIter<AppendRecord>;
3248
3249 fn into_iter(self) -> Self::IntoIter {
3250 self.0.into_iter()
3251 }
3252}
3253
3254impl<'a> IntoIterator for &'a AppendRecordBatch {
3255 type Item = &'a AppendRecord;
3256 type IntoIter = std::slice::Iter<'a, AppendRecord>;
3257
3258 fn into_iter(self) -> Self::IntoIter {
3259 self.0.iter()
3260 }
3261}
3262
3263#[derive(Debug, Clone)]
3264pub enum Command {
3266 Fence {
3268 fencing_token: FencingToken,
3270 },
3271 Trim {
3273 trim_point: u64,
3275 },
3276}
3277
3278#[derive(Debug, Clone)]
3279#[non_exhaustive]
3280pub struct CommandRecord {
3284 pub command: Command,
3286 pub timestamp: Option<u64>,
3288}
3289
3290impl CommandRecord {
3291 const FENCE: &[u8] = b"fence";
3292 const TRIM: &[u8] = b"trim";
3293
3294 pub fn fence(fencing_token: FencingToken) -> Self {
3299 Self {
3300 command: Command::Fence { fencing_token },
3301 timestamp: None,
3302 }
3303 }
3304
3305 pub fn trim(trim_point: u64) -> Self {
3312 Self {
3313 command: Command::Trim { trim_point },
3314 timestamp: None,
3315 }
3316 }
3317
3318 pub fn with_timestamp(self, timestamp: u64) -> Self {
3320 Self {
3321 timestamp: Some(timestamp),
3322 ..self
3323 }
3324 }
3325}
3326
3327impl From<CommandRecord> for AppendRecord {
3328 fn from(value: CommandRecord) -> Self {
3329 let (header_value, body) = match value.command {
3330 Command::Fence { fencing_token } => (
3331 CommandRecord::FENCE,
3332 Bytes::copy_from_slice(fencing_token.as_bytes()),
3333 ),
3334 Command::Trim { trim_point } => (
3335 CommandRecord::TRIM,
3336 Bytes::copy_from_slice(&trim_point.to_be_bytes()),
3337 ),
3338 };
3339 Self {
3340 body,
3341 headers: vec![Header::new("", header_value)],
3342 timestamp: value.timestamp,
3343 }
3344 }
3345}
3346
3347#[derive(Debug, Clone)]
3348#[non_exhaustive]
3349pub struct AppendInput {
3352 pub records: AppendRecordBatch,
3354 pub match_seq_num: Option<u64>,
3358 pub fencing_token: Option<FencingToken>,
3363}
3364
3365impl AppendInput {
3366 pub fn new(records: AppendRecordBatch) -> Self {
3368 Self {
3369 records,
3370 match_seq_num: None,
3371 fencing_token: None,
3372 }
3373 }
3374
3375 pub fn with_match_seq_num(self, match_seq_num: u64) -> Self {
3377 Self {
3378 match_seq_num: Some(match_seq_num),
3379 ..self
3380 }
3381 }
3382
3383 pub fn with_fencing_token(self, fencing_token: FencingToken) -> Self {
3385 Self {
3386 fencing_token: Some(fencing_token),
3387 ..self
3388 }
3389 }
3390}
3391
3392impl From<AppendInput> for api::stream::proto::AppendInput {
3393 fn from(value: AppendInput) -> Self {
3394 Self {
3395 records: value.records.iter().cloned().map(Into::into).collect(),
3396 match_seq_num: value.match_seq_num,
3397 fencing_token: value.fencing_token.map(|t| t.to_string()),
3398 }
3399 }
3400}
3401
3402#[derive(Debug, Clone, PartialEq)]
3403#[non_exhaustive]
3404pub struct AppendAck {
3406 pub start: StreamPosition,
3408 pub end: StreamPosition,
3414 pub tail: StreamPosition,
3419}
3420
3421impl AppendAck {
3422 pub fn new(start: StreamPosition, end: StreamPosition, tail: StreamPosition) -> Self {
3426 Self { start, end, tail }
3427 }
3428}
3429
3430impl From<api::stream::proto::AppendAck> for AppendAck {
3431 fn from(value: api::stream::proto::AppendAck) -> Self {
3432 Self {
3433 start: value.start.unwrap_or_default().into(),
3434 end: value.end.unwrap_or_default().into(),
3435 tail: value.tail.unwrap_or_default().into(),
3436 }
3437 }
3438}
3439
3440#[derive(Debug, Clone, Copy)]
3441pub enum ReadFrom {
3443 SeqNum(u64),
3445 Timestamp(u64),
3447 TailOffset(u64),
3449}
3450
3451impl Default for ReadFrom {
3452 fn default() -> Self {
3453 Self::SeqNum(0)
3454 }
3455}
3456
3457#[derive(Debug, Default, Clone)]
3458#[non_exhaustive]
3459pub struct ReadStart {
3461 pub from: ReadFrom,
3465 pub clamp_to_tail: bool,
3469}
3470
3471impl ReadStart {
3472 pub fn new() -> Self {
3474 Self::default()
3475 }
3476
3477 pub fn with_from(self, from: ReadFrom) -> Self {
3479 Self { from, ..self }
3480 }
3481
3482 pub fn with_clamp_to_tail(self, clamp_to_tail: bool) -> Self {
3484 Self {
3485 clamp_to_tail,
3486 ..self
3487 }
3488 }
3489}
3490
3491impl From<ReadStart> for api::stream::ReadStart {
3492 fn from(value: ReadStart) -> Self {
3493 let (seq_num, timestamp, tail_offset) = match value.from {
3494 ReadFrom::SeqNum(n) => (Some(n), None, None),
3495 ReadFrom::Timestamp(t) => (None, Some(t), None),
3496 ReadFrom::TailOffset(o) => (None, None, Some(o)),
3497 };
3498 Self {
3499 seq_num,
3500 timestamp,
3501 tail_offset,
3502 clamp: if value.clamp_to_tail {
3503 Some(true)
3504 } else {
3505 None
3506 },
3507 }
3508 }
3509}
3510
3511#[derive(Debug, Clone, Default)]
3512#[non_exhaustive]
3513pub struct ReadLimits {
3515 pub count: Option<usize>,
3519 pub bytes: Option<usize>,
3523}
3524
3525impl ReadLimits {
3526 pub fn new() -> Self {
3528 Self::default()
3529 }
3530
3531 pub fn with_count(self, count: usize) -> Self {
3533 Self {
3534 count: Some(count),
3535 ..self
3536 }
3537 }
3538
3539 pub fn with_bytes(self, bytes: usize) -> Self {
3541 Self {
3542 bytes: Some(bytes),
3543 ..self
3544 }
3545 }
3546}
3547
3548#[derive(Debug, Clone, Default)]
3549#[non_exhaustive]
3550pub struct ReadStop {
3552 pub limits: ReadLimits,
3556 pub until: Option<RangeTo<u64>>,
3560 pub wait: Option<u32>,
3570}
3571
3572impl ReadStop {
3573 pub fn new() -> Self {
3575 Self::default()
3576 }
3577
3578 pub fn with_limits(self, limits: ReadLimits) -> Self {
3580 Self { limits, ..self }
3581 }
3582
3583 pub fn with_until(self, until: RangeTo<u64>) -> Self {
3585 Self {
3586 until: Some(until),
3587 ..self
3588 }
3589 }
3590
3591 pub fn with_wait(self, wait: u32) -> Self {
3593 Self {
3594 wait: Some(wait),
3595 ..self
3596 }
3597 }
3598}
3599
3600impl From<ReadStop> for api::stream::ReadEnd {
3601 fn from(value: ReadStop) -> Self {
3602 Self {
3603 count: value.limits.count,
3604 bytes: value.limits.bytes,
3605 until: value.until.map(|r| r.end),
3606 wait: value.wait,
3607 }
3608 }
3609}
3610
3611#[derive(Debug, Clone, Default)]
3612#[non_exhaustive]
3613pub struct ReadInput {
3616 pub start: ReadStart,
3620 pub stop: ReadStop,
3624 pub ignore_command_records: bool,
3628}
3629
3630#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
3631#[non_exhaustive]
3632pub enum ReadSessionRetryPolicy {
3634 #[default]
3636 Budgeted,
3637 Indefinite,
3643}
3644
3645#[derive(Debug, Clone, Default)]
3646#[non_exhaustive]
3647pub struct ReadSessionConfig {
3649 pub retry_policy: ReadSessionRetryPolicy,
3655}
3656
3657impl ReadSessionConfig {
3658 pub fn new() -> Self {
3660 Self::default()
3661 }
3662
3663 pub fn with_retry_policy(self, retry_policy: ReadSessionRetryPolicy) -> Self {
3665 Self {
3666 retry_policy,
3667 ..self
3668 }
3669 }
3670}
3671
3672impl ReadInput {
3673 pub fn new() -> Self {
3675 Self::default()
3676 }
3677
3678 pub fn with_start(self, start: ReadStart) -> Self {
3680 Self { start, ..self }
3681 }
3682
3683 pub fn with_stop(self, stop: ReadStop) -> Self {
3685 Self { stop, ..self }
3686 }
3687
3688 pub fn with_ignore_command_records(self, ignore_command_records: bool) -> Self {
3690 Self {
3691 ignore_command_records,
3692 ..self
3693 }
3694 }
3695}
3696
3697#[derive(Debug, Clone)]
3698#[non_exhaustive]
3699pub struct SequencedRecord {
3701 pub seq_num: u64,
3703 pub body: Bytes,
3705 pub headers: Vec<Header>,
3707 pub timestamp: u64,
3709}
3710
3711impl SequencedRecord {
3712 pub fn from_parts(
3716 seq_num: u64,
3717 timestamp: u64,
3718 headers: Vec<Header>,
3719 body: impl Into<Bytes>,
3720 ) -> Self {
3721 Self {
3722 seq_num,
3723 timestamp,
3724 body: body.into(),
3725 headers,
3726 }
3727 }
3728
3729 pub fn is_command_record(&self) -> bool {
3731 self.headers.len() == 1 && *self.headers[0].name == *b""
3732 }
3733}
3734
3735impl From<api::stream::proto::SequencedRecord> for SequencedRecord {
3736 fn from(value: api::stream::proto::SequencedRecord) -> Self {
3737 Self {
3738 seq_num: value.seq_num,
3739 body: value.body,
3740 headers: value.headers.into_iter().map(Into::into).collect(),
3741 timestamp: value.timestamp,
3742 }
3743 }
3744}
3745
3746metered_bytes_impl!(SequencedRecord);
3747
3748#[derive(Debug, Clone)]
3749#[non_exhaustive]
3750pub struct ReadBatch {
3753 pub records: Vec<SequencedRecord>,
3760 pub tail: Option<StreamPosition>,
3765}
3766
3767impl ReadBatch {
3768 pub fn new(records: Vec<SequencedRecord>, tail: Option<StreamPosition>) -> Self {
3772 Self { records, tail }
3773 }
3774
3775 pub(crate) fn from_api(batch: api::stream::proto::ReadBatch) -> Self {
3776 Self {
3777 records: batch.records.into_iter().map(Into::into).collect(),
3778 tail: batch.tail.map(Into::into),
3779 }
3780 }
3781}
3782
3783pub type Streaming<T> = Pin<Box<dyn Send + futures_core::Stream<Item = Result<T, RequestError>>>>;
3785
3786fn idempotency_token() -> String {
3787 uuid::Uuid::new_v4().simple().to_string()
3788}
3789
3790#[cfg(test)]
3791mod tests {
3792 use proptest::prelude::*;
3793 use rstest::rstest;
3794
3795 use super::*;
3796
3797 type HeaderParts = (Vec<u8>, Vec<u8>);
3798 type AppendRecordParts = (Vec<u8>, Vec<HeaderParts>);
3799
3800 fn byte_vec_strategy(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
3801 prop::collection::vec(any::<u8>(), 0..=max_len)
3802 }
3803
3804 fn header_parts_strategy() -> impl Strategy<Value = HeaderParts> {
3805 (byte_vec_strategy(32), byte_vec_strategy(64))
3806 }
3807
3808 fn string_strategy(max_chars: usize) -> impl Strategy<Value = String> {
3809 prop::collection::vec(any::<char>(), 0..=max_chars)
3810 .prop_map(|chars| chars.into_iter().collect())
3811 }
3812
3813 fn read_from_strategy() -> impl Strategy<Value = ReadFrom> {
3814 prop_oneof![
3815 any::<u64>().prop_map(ReadFrom::SeqNum),
3816 any::<u64>().prop_map(ReadFrom::Timestamp),
3817 any::<u64>().prop_map(ReadFrom::TailOffset),
3818 ]
3819 }
3820
3821 fn append_record_parts_strategy() -> impl Strategy<Value = AppendRecordParts> {
3822 (
3823 byte_vec_strategy(256),
3824 prop::collection::vec(header_parts_strategy(), 0..=16),
3825 )
3826 }
3827
3828 fn proto_stream_position_strategy() -> impl Strategy<Value = api::stream::proto::StreamPosition>
3829 {
3830 (any::<u64>(), any::<u64>()).prop_map(|(seq_num, timestamp)| {
3831 api::stream::proto::StreamPosition { seq_num, timestamp }
3832 })
3833 }
3834
3835 fn headers_from_parts(headers: &[HeaderParts]) -> Vec<Header> {
3836 headers
3837 .iter()
3838 .map(|(name, value)| Header::new(Bytes::from(name.clone()), Bytes::from(value.clone())))
3839 .collect()
3840 }
3841
3842 fn expected_metered_bytes(body: &[u8], headers: &[HeaderParts]) -> usize {
3843 8 + (2 * headers.len())
3844 + headers
3845 .iter()
3846 .map(|(name, value)| name.len() + value.len())
3847 .sum::<usize>()
3848 + body.len()
3849 }
3850
3851 #[test]
3854 fn s2_datetime_parse_valid_rfc3339() {
3855 let dt: S2DateTime = "2024-01-15T12:30:00Z".parse().unwrap();
3856 assert_eq!(dt.to_string(), "2024-01-15T12:30:00Z");
3857 }
3858
3859 #[test]
3860 fn s2_datetime_parse_with_offset() {
3861 let dt: S2DateTime = "2024-06-01T08:00:00+05:30".parse().unwrap();
3862 assert_eq!(dt.to_string(), "2024-06-01T08:00:00+05:30");
3863
3864 let offset_dt: time::OffsetDateTime = dt.into();
3865 assert_eq!(
3866 offset_dt.offset(),
3867 time::UtcOffset::from_hms(5, 30, 0).unwrap()
3868 );
3869 }
3870
3871 #[test]
3872 fn s2_datetime_parse_invalid() {
3873 let err = "not-a-date".parse::<S2DateTime>();
3874 assert!(err.is_err());
3875 }
3876
3877 #[test]
3878 fn s2_datetime_roundtrip_via_offset_datetime() {
3879 let odt = time::OffsetDateTime::from_unix_timestamp(1_700_000_000).unwrap();
3880 let dt = S2DateTime::try_from(odt).unwrap();
3881 let back: time::OffsetDateTime = dt.into();
3882 assert_eq!(odt, back);
3883 }
3884
3885 #[rstest]
3888 #[case::https_with_scheme("https://aws.s2.dev", Scheme::HTTPS)]
3889 #[case::http_with_scheme("http://localhost:8080", Scheme::HTTP)]
3890 #[case::default_https("aws.s2.dev", Scheme::HTTPS)]
3891 fn account_endpoint_parse(#[case] input: &str, #[case] expected_scheme: Scheme) {
3892 let ep: AccountEndpoint = input.parse().unwrap();
3893 assert_eq!(ep.scheme, expected_scheme);
3894 }
3895
3896 #[rstest]
3899 #[case::https_parent_zone("https://{basin}.b.s2.dev", Scheme::HTTPS, true)]
3900 #[case::http_direct("http://localhost:8080", Scheme::HTTP, false)]
3901 #[case::default_https_parent_zone("{basin}.b.s2.dev", Scheme::HTTPS, true)]
3902 fn basin_endpoint_parse(
3903 #[case] input: &str,
3904 #[case] expected_scheme: Scheme,
3905 #[case] expected_parent_zone: bool,
3906 ) {
3907 let ep: BasinEndpoint = input.parse().unwrap();
3908 assert_eq!(ep.scheme, expected_scheme);
3909 assert_eq!(
3910 matches!(ep.authority, BasinAuthority::ParentZone(_)),
3911 expected_parent_zone
3912 );
3913 }
3914
3915 #[test]
3918 fn s2_endpoints_new_requires_same_scheme() {
3919 let account: AccountEndpoint = "https://aws.s2.dev".parse().unwrap();
3920 let basin: BasinEndpoint = "http://localhost:8080".parse().unwrap();
3921 let err = S2Endpoints::new(account, basin);
3922 assert!(err.is_err());
3923 }
3924
3925 #[test]
3926 fn s2_endpoints_new_same_scheme_succeeds() {
3927 let account: AccountEndpoint = "https://aws.s2.dev".parse().unwrap();
3928 let basin: BasinEndpoint = "https://{basin}.b.s2.dev".parse().unwrap();
3929 let ep = S2Endpoints::new(account, basin).unwrap();
3930 assert_eq!(ep.scheme, Scheme::HTTPS);
3931 }
3932
3933 #[test]
3934 fn s2_endpoints_for_endpoint_defaults_to_https() {
3935 let ep = S2Endpoints::for_endpoint("localhost:8080").unwrap();
3936 let authority: Authority = "localhost:8080".parse().unwrap();
3937 assert_eq!(ep.scheme, Scheme::HTTPS);
3938 assert_eq!(ep.account_authority, authority);
3939 assert_eq!(ep.basin_authority, BasinAuthority::Direct(authority));
3940 }
3941
3942 #[test]
3943 fn s2_endpoints_for_endpoint_accepts_explicit_scheme() {
3944 let ep = S2Endpoints::for_endpoint("http://localhost:8080").unwrap();
3945 assert_eq!(ep.scheme, Scheme::HTTP);
3946 }
3947
3948 #[test]
3949 fn s2_endpoints_for_endpoint_rejects_invalid_endpoint() {
3950 assert!(S2Endpoints::for_endpoint("not a valid endpoint").is_err());
3951 }
3952
3953 #[rstest]
3956 #[case::none(Compression::None, CompressionAlgorithm::None)]
3957 #[case::gzip(Compression::Gzip, CompressionAlgorithm::Gzip)]
3958 #[case::zstd(Compression::Zstd, CompressionAlgorithm::Zstd)]
3959 fn compression_conversion(#[case] sdk: Compression, #[case] api: CompressionAlgorithm) {
3960 assert_eq!(CompressionAlgorithm::from(sdk), api);
3961 }
3962
3963 #[test]
3966 fn retry_config_defaults() {
3967 let rc = RetryConfig::default();
3968 assert_eq!(rc.max_attempts.get(), 3);
3969 assert_eq!(rc.min_base_delay, Duration::from_millis(100));
3970 assert_eq!(rc.max_base_delay, Duration::from_secs(1));
3971 assert!(matches!(rc.append_retry_policy, AppendRetryPolicy::All));
3972 }
3973
3974 #[test]
3975 fn retry_config_max_retries() {
3976 let rc = RetryConfig::default();
3977 assert_eq!(rc.max_retries(), 2);
3978 }
3979
3980 #[test]
3983 fn s2_config_defaults() {
3984 let cfg = S2Config::new("test-token");
3985 assert_eq!(cfg.connection_timeout, Duration::from_secs(3));
3986 assert_eq!(cfg.request_timeout, Duration::from_secs(5));
3987 assert!(!cfg.insecure_skip_cert_verification);
3988 }
3989
3990 #[rstest]
3993 #[case::standard(StorageClass::Standard)]
3994 #[case::express(StorageClass::Express)]
3995 fn storage_class_roundtrip(#[case] sdk: StorageClass) {
3996 let api: api::config::StorageClass = sdk.into();
3997 let back: StorageClass = api.into();
3998 assert_eq!(back, sdk);
3999 }
4000
4001 #[rstest]
4004 #[case::age(RetentionPolicy::Age(3600))]
4005 #[case::infinite(RetentionPolicy::Infinite)]
4006 fn retention_policy_roundtrip(#[case] sdk: RetentionPolicy) {
4007 let api: api::config::RetentionPolicy = sdk.into();
4008 let back: RetentionPolicy = api.into();
4009 assert_eq!(back, sdk);
4010 }
4011
4012 #[rstest]
4015 #[case::client_prefer(
4016 TimestampingMode::ClientPrefer,
4017 api::config::TimestampingMode::ClientPrefer
4018 )]
4019 #[case::client_require(
4020 TimestampingMode::ClientRequire,
4021 api::config::TimestampingMode::ClientRequire
4022 )]
4023 #[case::arrival(TimestampingMode::Arrival, api::config::TimestampingMode::Arrival)]
4024 fn timestamping_mode_roundtrip(
4025 #[case] sdk: TimestampingMode,
4026 #[case] expected_api: api::config::TimestampingMode,
4027 ) {
4028 let converted: api::config::TimestampingMode = sdk.into();
4029 assert_eq!(converted, expected_api);
4030 let back: TimestampingMode = converted.into();
4031 assert_eq!(back, sdk);
4032 }
4033
4034 #[test]
4037 fn timestamping_config_roundtrip() {
4038 let sdk = TimestampingConfig {
4039 mode: Some(TimestampingMode::Arrival),
4040 uncapped: Some(true),
4041 };
4042 let api: api::config::TimestampingConfig = sdk.into();
4043 let back: TimestampingConfig = api.into();
4044 assert_eq!(back, sdk);
4045 }
4046
4047 #[test]
4050 fn delete_on_empty_config_roundtrip() {
4051 let sdk = DeleteOnEmptyConfig::new().with_min_age(Duration::from_secs(300));
4052 let api: api::config::DeleteOnEmptyConfig = sdk.into();
4053 let back: DeleteOnEmptyConfig = api.into();
4054 assert_eq!(back, sdk);
4055 }
4056
4057 #[test]
4060 fn stream_config_builder_and_roundtrip() {
4061 let sdk = StreamConfig::new()
4062 .with_storage_class(StorageClass::Express)
4063 .with_retention_policy(RetentionPolicy::Age(86400))
4064 .with_timestamping(TimestampingConfig {
4065 mode: Some(TimestampingMode::ClientPrefer),
4066 uncapped: None,
4067 })
4068 .with_delete_on_empty(DeleteOnEmptyConfig { min_age_secs: 60 });
4069 let api: api::config::StreamConfig = sdk.clone().into();
4070 let back: StreamConfig = api.into();
4071 assert_eq!(back, sdk);
4072 }
4073
4074 #[test]
4077 fn basin_config_builder_and_roundtrip() {
4078 let sdk = BasinConfig::new()
4079 .with_default_stream_config(
4080 StreamConfig::new().with_storage_class(StorageClass::Standard),
4081 )
4082 .with_create_stream_on_append(true)
4083 .with_create_stream_on_read(false);
4084 let api: api::config::BasinConfig = sdk.clone().into();
4085 let back: BasinConfig = api.into();
4086 assert_eq!(back, sdk);
4087 }
4088
4089 proptest! {
4092 #[test]
4093 fn fencing_token_parse_accepts_only_within_byte_limit(
4094 token in string_strategy(MAX_FENCING_TOKEN_LENGTH + 8),
4095 ) {
4096 let parsed = token.parse::<FencingToken>();
4097
4098 if token.len() <= MAX_FENCING_TOKEN_LENGTH {
4099 prop_assert_eq!(parsed.unwrap().to_string(), token);
4100 } else {
4101 prop_assert!(parsed.is_err());
4102 }
4103 }
4104 }
4105
4106 #[test]
4109 fn stream_position_display() {
4110 let pos = StreamPosition {
4111 seq_num: 42,
4112 timestamp: 1700000000,
4113 };
4114 assert_eq!(pos.to_string(), "seq_num=42, timestamp=1700000000");
4115 }
4116
4117 proptest! {
4118 #[test]
4119 fn stream_position_conversions_preserve_values(seq_num in any::<u64>(), timestamp in any::<u64>()) {
4120 let proto: StreamPosition = api::stream::proto::StreamPosition {
4121 seq_num,
4122 timestamp,
4123 }
4124 .into();
4125 prop_assert_eq!(proto.seq_num, seq_num);
4126 prop_assert_eq!(proto.timestamp, timestamp);
4127
4128 let api: StreamPosition = api::stream::StreamPosition {
4129 seq_num,
4130 timestamp,
4131 }
4132 .into();
4133 prop_assert_eq!(api.seq_num, seq_num);
4134 prop_assert_eq!(api.timestamp, timestamp);
4135 }
4136 }
4137
4138 proptest! {
4141 #[test]
4142 fn header_proto_roundtrip_preserves_binary_parts(
4143 name in byte_vec_strategy(64),
4144 value in byte_vec_strategy(128),
4145 ) {
4146 let header = Header::new(Bytes::from(name.clone()), Bytes::from(value.clone()));
4147 let proto: api::stream::proto::Header = header.into();
4148 let back: Header = proto.into();
4149
4150 prop_assert_eq!(back.name.as_ref(), name.as_slice());
4151 prop_assert_eq!(back.value.as_ref(), value.as_slice());
4152 }
4153 }
4154
4155 #[test]
4158 fn append_record_too_large() {
4159 let big_body = vec![0u8; RECORD_BATCH_MAX.bytes + 1];
4160 assert!(AppendRecord::new(big_body).is_err());
4161 }
4162
4163 proptest! {
4166 #[test]
4167 fn append_record_preserves_fields_and_metered_byte_formula(
4168 (body, headers) in append_record_parts_strategy(),
4169 timestamp in proptest::option::of(any::<u64>()),
4170 ) {
4171 let mut record = AppendRecord::new(body.clone())
4172 .unwrap()
4173 .with_headers(headers_from_parts(&headers))
4174 .unwrap();
4175 if let Some(timestamp) = timestamp {
4176 record = record.with_timestamp(timestamp);
4177 }
4178
4179 prop_assert_eq!(record.body(), body.as_slice());
4180 prop_assert_eq!(record.headers().len(), headers.len());
4181 prop_assert_eq!(record.timestamp(), timestamp);
4182 prop_assert_eq!(record.metered_bytes(), expected_metered_bytes(&body, &headers));
4183
4184 for (actual, (expected_name, expected_value)) in record.headers().iter().zip(headers.iter()) {
4185 prop_assert_eq!(actual.name.as_ref(), expected_name.as_slice());
4186 prop_assert_eq!(actual.value.as_ref(), expected_value.as_slice());
4187 }
4188 }
4189 }
4190
4191 #[test]
4194 fn append_record_batch_empty_is_err() {
4195 let result = AppendRecordBatch::try_from_iter(vec![]);
4196 assert!(result.is_err());
4197 }
4198
4199 #[test]
4200 fn append_record_batch_too_many_records() {
4201 let records: Vec<_> = (0..1001).map(|_| AppendRecord::new("x").unwrap()).collect();
4202 let result = AppendRecordBatch::try_from_iter(records);
4203 assert!(result.is_err());
4204 }
4205
4206 proptest! {
4207 #[test]
4208 fn append_record_batch_metered_bytes_is_sum_of_records(
4209 records in prop::collection::vec(append_record_parts_strategy(), 1..=32),
4210 ) {
4211 let expected = records
4212 .iter()
4213 .map(|(body, headers)| expected_metered_bytes(body, headers))
4214 .sum::<usize>();
4215 let records = records
4216 .into_iter()
4217 .map(|(body, headers)| {
4218 AppendRecord::new(body)
4219 .unwrap()
4220 .with_headers(headers_from_parts(&headers))
4221 .unwrap()
4222 })
4223 .collect::<Vec<_>>();
4224
4225 let batch = AppendRecordBatch::try_from_iter(records).unwrap();
4226 prop_assert_eq!(batch.metered_bytes(), expected);
4227 prop_assert_eq!(batch.iter().map(MeteredBytes::metered_bytes).sum::<usize>(), expected);
4228 }
4229 }
4230
4231 #[test]
4234 fn command_record_fence() {
4235 let token: FencingToken = "tok".parse().unwrap();
4236 let cmd = CommandRecord::fence(token);
4237 let record: AppendRecord = cmd.into();
4238 assert_eq!(record.headers().len(), 1);
4239 assert_eq!(record.headers()[0].name.as_ref(), b"");
4240 assert_eq!(record.headers()[0].value.as_ref(), b"fence");
4241 assert_eq!(record.body(), b"tok");
4242 }
4243
4244 #[test]
4245 fn command_record_trim() {
4246 let cmd = CommandRecord::trim(42);
4247 let record: AppendRecord = cmd.into();
4248 assert_eq!(record.headers().len(), 1);
4249 assert_eq!(record.headers()[0].value.as_ref(), b"trim");
4250 assert_eq!(record.body(), &42u64.to_be_bytes());
4251 }
4252
4253 #[rstest]
4256 #[case::command(vec![Header::new("", "fence")], true)]
4257 #[case::regular(vec![Header::new("key", "value")], false)]
4258 #[case::no_headers(vec![], false)]
4259 fn sequenced_record_command_detection(#[case] headers: Vec<Header>, #[case] expected: bool) {
4260 let record = SequencedRecord {
4261 seq_num: 0,
4262 body: Bytes::from("data"),
4263 headers,
4264 timestamp: 0,
4265 };
4266 assert_eq!(record.is_command_record(), expected);
4267 }
4268
4269 proptest! {
4272 #[test]
4273 fn read_start_to_api_sets_only_selected_position_field(
4274 from in read_from_strategy(),
4275 clamp_to_tail in any::<bool>(),
4276 ) {
4277 let (seq_num, timestamp, tail_offset) = match from {
4278 ReadFrom::SeqNum(value) => (Some(value), None, None),
4279 ReadFrom::Timestamp(value) => (None, Some(value), None),
4280 ReadFrom::TailOffset(value) => (None, None, Some(value)),
4281 };
4282 let api: api::stream::ReadStart = ReadStart::new()
4283 .with_from(from)
4284 .with_clamp_to_tail(clamp_to_tail)
4285 .into();
4286
4287 prop_assert_eq!(api.seq_num, seq_num);
4288 prop_assert_eq!(api.timestamp, timestamp);
4289 prop_assert_eq!(api.tail_offset, tail_offset);
4290 prop_assert_eq!(api.clamp, clamp_to_tail.then_some(true));
4291 }
4292 }
4293
4294 #[test]
4297 fn read_stop_to_api() {
4298 let stop = ReadStop::new()
4299 .with_limits(ReadLimits::new().with_count(50))
4300 .with_until(..1000)
4301 .with_wait(30);
4302 let api: api::stream::ReadEnd = stop.into();
4303 assert_eq!(api.count, Some(50));
4304 assert_eq!(api.until, Some(1000));
4305 assert_eq!(api.wait, Some(30));
4306 }
4307
4308 #[test]
4311 fn operation_roundtrip_all_variants() {
4312 let variants = [
4313 Operation::ListBasins,
4314 Operation::CreateBasin,
4315 Operation::GetBasinConfig,
4316 Operation::DeleteBasin,
4317 Operation::ReconfigureBasin,
4318 Operation::ListAccessTokens,
4319 Operation::IssueAccessToken,
4320 Operation::RevokeAccessToken,
4321 Operation::GetAccountMetrics,
4322 Operation::GetBasinMetrics,
4323 Operation::GetStreamMetrics,
4324 Operation::ListStreams,
4325 Operation::CreateStream,
4326 Operation::GetStreamConfig,
4327 Operation::DeleteStream,
4328 Operation::ReconfigureStream,
4329 Operation::CheckTail,
4330 Operation::Append,
4331 Operation::Read,
4332 Operation::Trim,
4333 Operation::Fence,
4334 Operation::ListLocations,
4335 Operation::GetDefaultLocation,
4336 Operation::SetDefaultLocation,
4337 ];
4338 for op in variants {
4339 let api_op: api::access::Operation = op.into();
4340 let back: Operation = api_op.into();
4341 assert_eq!(back, op);
4342 }
4343 }
4344
4345 #[test]
4348 fn metric_unit_conversion() {
4349 assert_eq!(
4350 MetricUnit::from(api::metrics::MetricUnit::Bytes),
4351 MetricUnit::Bytes
4352 );
4353 assert_eq!(
4354 MetricUnit::from(api::metrics::MetricUnit::Operations),
4355 MetricUnit::Operations
4356 );
4357 }
4358
4359 proptest! {
4362 #[test]
4363 fn append_ack_from_proto_preserves_present_positions_and_defaults_missing(
4364 start in proptest::option::of(proto_stream_position_strategy()),
4365 end in proptest::option::of(proto_stream_position_strategy()),
4366 tail in proptest::option::of(proto_stream_position_strategy()),
4367 ) {
4368 let expected_start = start.unwrap_or_default();
4369 let expected_end = end.unwrap_or_default();
4370 let expected_tail = tail.unwrap_or_default();
4371 let ack: AppendAck = api::stream::proto::AppendAck { start, end, tail }.into();
4372
4373 prop_assert_eq!(ack.start.seq_num, expected_start.seq_num);
4374 prop_assert_eq!(ack.start.timestamp, expected_start.timestamp);
4375 prop_assert_eq!(ack.end.seq_num, expected_end.seq_num);
4376 prop_assert_eq!(ack.end.timestamp, expected_end.timestamp);
4377 prop_assert_eq!(ack.tail.seq_num, expected_tail.seq_num);
4378 prop_assert_eq!(ack.tail.timestamp, expected_tail.timestamp);
4379 }
4380 }
4381
4382 #[test]
4385 fn read_batch_from_api() {
4386 let proto_batch = api::stream::proto::ReadBatch {
4387 records: vec![api::stream::proto::SequencedRecord {
4388 seq_num: 0,
4389 body: Bytes::from("hi"),
4390 headers: vec![api::stream::proto::Header {
4391 name: Bytes::from("k"),
4392 value: Bytes::from("v"),
4393 }],
4394 timestamp: 42,
4395 }],
4396 tail: Some(api::stream::proto::StreamPosition {
4397 seq_num: 1,
4398 timestamp: 42,
4399 }),
4400 };
4401 let batch = ReadBatch::from_api(proto_batch);
4402 assert_eq!(batch.records.len(), 1);
4403 assert_eq!(batch.records[0].seq_num, 0);
4404 assert_eq!(batch.records[0].timestamp, 42);
4405 assert_eq!(batch.records[0].body.as_ref(), b"hi");
4406 assert_eq!(batch.records[0].headers.len(), 1);
4407 assert_eq!(batch.records[0].headers[0].name.as_ref(), b"k");
4408 assert_eq!(batch.records[0].headers[0].value.as_ref(), b"v");
4409 assert_eq!(
4410 batch.tail,
4411 Some(StreamPosition {
4412 seq_num: 1,
4413 timestamp: 42,
4414 })
4415 );
4416 }
4417
4418 #[test]
4421 fn create_basin_input_to_api() {
4422 let name: BasinName = "test-basin-name".parse().unwrap();
4423 let input = CreateBasinInput::new(name.clone()).with_config(BasinConfig::new());
4424 let (req, token): (api::basin::CreateBasinRequest, String) = input.into();
4425 assert_eq!(req.basin, name);
4426 assert!(req.config.is_some());
4427 assert!(!token.is_empty());
4428 }
4429
4430 #[test]
4433 fn create_stream_input_to_api() {
4434 let name: StreamName = "my-stream".parse().unwrap();
4435 let input = CreateStreamInput::new(name.clone()).with_config(StreamConfig::new());
4436 let (req, token): (api::stream::CreateStreamRequest, String) = input.into();
4437 assert_eq!(req.stream, name);
4438 assert!(req.config.is_some());
4439 assert!(!token.is_empty());
4440 }
4441
4442 #[test]
4445 fn sequenced_record_from_proto() {
4446 let proto = api::stream::proto::SequencedRecord {
4447 seq_num: 99,
4448 body: Bytes::from("data"),
4449 headers: vec![api::stream::proto::Header {
4450 name: Bytes::from("k"),
4451 value: Bytes::from("v"),
4452 }],
4453 timestamp: 1234,
4454 };
4455 let record: SequencedRecord = proto.into();
4456 assert_eq!(record.seq_num, 99);
4457 assert_eq!(record.body.as_ref(), b"data");
4458 assert_eq!(record.headers.len(), 1);
4459 assert_eq!(record.headers[0].name.as_ref(), b"k");
4460 assert_eq!(record.headers[0].value.as_ref(), b"v");
4461 assert_eq!(record.timestamp, 1234);
4462 }
4463}