use aws_sdk_kinesis::operation::get_shard_iterator::builders::GetShardIteratorFluentBuilder;
use aws_sdk_kinesis::primitives::DateTime;
use aws_sdk_kinesis::types::ShardIteratorType;
#[cfg(doc)]
use crate::message::KinesisPosition;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum StreamStart {
Horizon,
Latest,
Timestamp(u64),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ShardStart {
Stream(StreamStart),
At(String),
After(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Lineage {
Opens,
Continues,
}
pub(crate) fn iterator_at(
request: GetShardIteratorFluentBuilder,
start: &ShardStart,
) -> GetShardIteratorFluentBuilder {
match start {
ShardStart::Stream(StreamStart::Latest) => {
request.shard_iterator_type(ShardIteratorType::Latest)
}
ShardStart::Stream(StreamStart::Horizon) => {
request.shard_iterator_type(ShardIteratorType::TrimHorizon)
}
ShardStart::Stream(StreamStart::Timestamp(millis)) => request
.shard_iterator_type(ShardIteratorType::AtTimestamp)
.timestamp(DateTime::from_millis(
i64::try_from(*millis).unwrap_or(i64::MAX),
)),
ShardStart::At(sequence) => request
.shard_iterator_type(ShardIteratorType::AtSequenceNumber)
.starting_sequence_number(sequence),
ShardStart::After(sequence) => request
.shard_iterator_type(ShardIteratorType::AfterSequenceNumber)
.starting_sequence_number(sequence),
}
}
pub(crate) fn reopen_at(
checkpoint: Option<String>,
rewind: Option<&ShardStart>,
lineage: Lineage,
installed: Option<StreamStart>,
) -> ShardStart {
match (checkpoint, rewind, lineage, installed) {
(Some(sequence), ..) => ShardStart::After(sequence),
(None, Some(rewind), ..) => rewind.clone(),
(None, None, Lineage::Continues, Some(StreamStart::Timestamp(millis))) => {
ShardStart::Stream(StreamStart::Timestamp(millis))
}
(None, None, Lineage::Continues, _) => ShardStart::Stream(StreamStart::Horizon),
(None, None, Lineage::Opens, installed) => {
ShardStart::Stream(installed.unwrap_or(StreamStart::Latest))
}
}
}
pub(crate) fn rewind_to(start: &ShardStart) -> Option<ShardStart> {
match start {
ShardStart::Stream(StreamStart::Latest) => None,
other => Some(other.clone()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_reader_without_a_checkpoint_reopens_at_the_first_record_it_read() {
let first =
ShardStart::At("49590338271490256608559692538361571095921575989136588898".into());
assert_eq!(reopen_at(None, Some(&first), Lineage::Opens, None), first);
assert_eq!(
reopen_at(
None,
Some(&first),
Lineage::Opens,
Some(StreamStart::Latest)
),
first
);
}
#[test]
fn the_checkpoint_wins_over_where_the_reader_started() {
let first = ShardStart::At("1".into());
assert_eq!(
reopen_at(Some("7".into()), Some(&first), Lineage::Opens, None),
ShardStart::After("7".into())
);
}
#[test]
fn the_tip_is_no_place_to_rewind_to() {
assert_eq!(rewind_to(&ShardStart::Stream(StreamStart::Latest)), None);
assert_eq!(
rewind_to(&ShardStart::Stream(StreamStart::Horizon)),
Some(ShardStart::Stream(StreamStart::Horizon))
);
assert_eq!(
rewind_to(&ShardStart::After("7".into())),
Some(ShardStart::After("7".into()))
);
}
}