use polyc_eventlog::{Event, TrustTag};
use polyc_state::feed::FeedRecord;
use polyc_state::journal::RecordTrust;
use polyc_state::page::Positioned as _;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartitionChange {
Destroyed,
MigratedAway,
Rewritten,
}
pub trait PartitionInvalidation: Send + Sync {
fn invalidate_partition(&self, partition: &str);
}
#[must_use]
pub fn commit_events(records: &[FeedRecord]) -> Vec<(u64, Event)> {
records
.iter()
.flat_map(FeedRecord::records)
.map(|record| {
(
crate::journal::port_position(record.position()),
Event::with_trust(
record.kind().as_str().to_owned(),
record.payload().to_vec(),
trust_tag(record.trust()),
),
)
})
.collect()
}
const fn trust_tag(trust: RecordTrust) -> TrustTag {
match trust {
RecordTrust::Unspecified => TrustTag::Unspecified,
RecordTrust::TrustedUser => TrustTag::TrustedUser,
RecordTrust::QuarantinedContent => TrustTag::QuarantinedContent,
}
}
#[cfg(test)]
fn test_checkpoint(
partition: &str,
feed_position: polyc_state::revision::JournalPosition,
journal_position: polyc_state::revision::JournalPosition,
) -> polyc_state::feed::SourceCheckpoint {
use polyc_state::{
feed::{ATTESTATION_SIGNATURE_BYTES, ATTESTATION_SIGNER_BYTES, SourceCheckpoint},
journal::JournalAttestation,
revision::{CommitRoot, JournalSource, PartitionIncarnation},
};
let evidence_leaf = journal_position.get();
SourceCheckpoint::try_new(
JournalSource::new(
polyc_state::id::PartitionId::new(partition),
PartitionIncarnation::from_bytes([1; PartitionIncarnation::LEN]),
),
feed_position,
journal_position,
evidence_leaf,
JournalAttestation::new(
CommitRoot::from_bytes(
[u8::try_from(journal_position.get() % 256).unwrap_or_default(); CommitRoot::LEN],
),
evidence_leaf.saturating_add(1),
vec![2; ATTESTATION_SIGNATURE_BYTES],
vec![3; ATTESTATION_SIGNER_BYTES],
),
)
.expect("the test checkpoint is structurally complete")
}
#[cfg(test)]
pub(crate) fn test_commit(partition: &str, events: &[Event], positions: &[u64]) -> FeedRecord {
use polyc_state::digest::ContentDigest;
use polyc_state::feed::CommitEnvelope;
use polyc_state::id::CommandId;
use polyc_state::journal::{JournalRecord, RecordKind};
use polyc_state::revision::JournalPosition;
let records: Vec<JournalRecord> = events
.iter()
.zip(positions.iter().copied())
.map(|(event, position)| {
JournalRecord::new(
crate::journal::state_position(position),
RecordKind::new(event.kind.clone()),
match event.trust {
TrustTag::Unspecified => RecordTrust::Unspecified,
TrustTag::TrustedUser => RecordTrust::TrustedUser,
TrustTag::QuarantinedContent => RecordTrust::QuarantinedContent,
},
event.payload.clone(),
)
})
.collect();
let head_before = JournalPosition::new(positions.first().copied().unwrap_or(0));
let head_after = JournalPosition::new(
positions
.last()
.copied()
.map_or(0, |last| last.saturating_add(1)),
);
let feed_position = JournalPosition::new(head_before.get().saturating_add(1));
FeedRecord::new(
feed_position,
CommitEnvelope::new(
test_checkpoint(partition, feed_position, head_after),
CommandId::new(format!("test-commit-{}", head_before.get())),
ContentDigest::from_bytes([0u8; ContentDigest::LEN]),
head_before,
head_after,
records.len() as u64,
),
records,
)
}
#[cfg(test)]
mod tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs, clippy::unwrap_used)]
use super::*;
use polyc_state::digest::ContentDigest;
use polyc_state::feed::CommitEnvelope;
use polyc_state::id::CommandId;
use polyc_state::journal::{JournalRecord, RecordKind};
use polyc_state::revision::JournalPosition;
fn commit(partition: &str, records: Vec<JournalRecord>) -> FeedRecord {
let head_before = JournalPosition::ORIGIN;
let head_after = JournalPosition::new(records.len() as u64);
FeedRecord::new(
JournalPosition::new(1),
CommitEnvelope::new(
test_checkpoint(partition, JournalPosition::new(1), head_after),
CommandId::new("cmd-1".to_owned()),
ContentDigest::from_bytes([7u8; ContentDigest::LEN]),
head_before,
head_after,
records.len() as u64,
),
records,
)
}
#[test]
fn a_commits_records_arrive_with_their_positions_and_provenance() {
let chunk = vec![commit(
"conv-a",
vec![
JournalRecord::new(
JournalPosition::new(1),
RecordKind::new("turn_start".to_owned()),
RecordTrust::TrustedUser,
b"a".to_vec(),
),
JournalRecord::new(
JournalPosition::new(2),
RecordKind::new("user_msg".to_owned()),
RecordTrust::QuarantinedContent,
b"b".to_vec(),
),
],
)];
let events = commit_events(&chunk);
assert_eq!(events.len(), 2);
assert_eq!(events[0].0, 0);
assert_eq!(events[0].1.kind, "turn_start");
assert_eq!(events[0].1.trust, TrustTag::TrustedUser);
assert_eq!(events[1].0, 1);
assert_eq!(events[1].1.trust, TrustTag::QuarantinedContent);
assert_eq!(events[1].1.payload, b"b".to_vec());
}
#[test]
fn several_commits_flatten_into_one_ordered_run() {
let chunk = vec![
commit(
"conv-a",
vec![JournalRecord::new(
JournalPosition::new(1),
RecordKind::new("k0".to_owned()),
RecordTrust::Unspecified,
Vec::new(),
)],
),
commit(
"conv-a",
vec![JournalRecord::new(
JournalPosition::new(2),
RecordKind::new("k1".to_owned()),
RecordTrust::Unspecified,
Vec::new(),
)],
),
];
let positions: Vec<u64> = commit_events(&chunk)
.into_iter()
.map(|(position, _)| position)
.collect();
assert_eq!(positions, vec![0, 1]);
}
#[test]
fn the_first_record_a_partition_holds_is_port_position_zero() {
let chunk = vec![commit(
"conv-a",
vec![JournalRecord::new(
JournalPosition::new(1),
RecordKind::new("turn_start".to_owned()),
RecordTrust::Unspecified,
Vec::new(),
)],
)];
assert_eq!(
commit_events(&chunk)[0].0,
0,
"State's first durable position is one and the port's is zero; a feed that reported \
one here would name a position no replay of a one-record partition returns"
);
}
#[test]
fn a_chunks_positions_round_trip_through_the_ports_coordinate() {
let chunk = vec![commit(
"conv-a",
vec![
JournalRecord::new(
JournalPosition::new(1),
RecordKind::new("k0".to_owned()),
RecordTrust::Unspecified,
Vec::new(),
),
JournalRecord::new(
JournalPosition::new(2),
RecordKind::new("k1".to_owned()),
RecordTrust::Unspecified,
Vec::new(),
),
JournalRecord::new(
JournalPosition::new(3),
RecordKind::new("k2".to_owned()),
RecordTrust::Unspecified,
Vec::new(),
),
],
)];
for (position, event) in commit_events(&chunk) {
assert_eq!(
crate::journal::state_position(position),
chunk[0]
.records()
.iter()
.find(|record| record.kind().as_str() == event.kind)
.expect("the event came off this chunk")
.position(),
"a port position must name the State record it was derived from"
);
}
}
}