use super::{AllocationRetirementError, LedgerCompatibilityError, LedgerIntegrityError};
use crate::{
declaration::{AllocationDeclaration, DeclarationSnapshotError, validate_runtime_fingerprint},
key::StableKey,
schema::{SchemaMetadata, SchemaMetadataError},
slot::AllocationSlotDescriptor,
};
use serde::{Deserialize, Serialize};
pub const CURRENT_LEDGER_SCHEMA_VERSION: u32 = 1;
pub const CURRENT_PHYSICAL_FORMAT_ID: u32 = 1;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AllocationLedger {
pub(crate) ledger_schema_version: u32,
pub(crate) physical_format_id: u32,
pub(crate) current_generation: u64,
pub(crate) allocation_history: AllocationHistory,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AllocationHistory {
pub(crate) records: Vec<AllocationRecord>,
pub(crate) generations: Vec<GenerationRecord>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AllocationRecord {
pub(crate) stable_key: StableKey,
pub(crate) slot: AllocationSlotDescriptor,
pub(crate) state: AllocationState,
pub(crate) first_generation: u64,
pub(crate) last_seen_generation: u64,
pub(crate) retired_generation: Option<u64>,
pub(crate) schema_history: Vec<SchemaMetadataRecord>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AllocationRetirement {
pub stable_key: StableKey,
pub slot: AllocationSlotDescriptor,
}
impl AllocationRetirement {
pub fn new(
stable_key: impl AsRef<str>,
slot: AllocationSlotDescriptor,
) -> Result<Self, AllocationRetirementError> {
Ok(Self {
stable_key: StableKey::parse(stable_key).map_err(AllocationRetirementError::Key)?,
slot,
})
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum AllocationState {
Reserved,
Active,
Retired,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SchemaMetadataRecord {
pub(crate) generation: u64,
pub(crate) schema: SchemaMetadata,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct GenerationRecord {
pub(crate) generation: u64,
pub(crate) parent_generation: Option<u64>,
pub(crate) runtime_fingerprint: Option<String>,
pub(crate) declaration_count: u32,
pub(crate) committed_at: Option<u64>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LedgerCompatibility {
pub min_ledger_schema_version: u32,
pub max_ledger_schema_version: u32,
pub physical_format_id: u32,
}
impl LedgerCompatibility {
#[must_use]
pub const fn current() -> Self {
Self {
min_ledger_schema_version: CURRENT_LEDGER_SCHEMA_VERSION,
max_ledger_schema_version: CURRENT_LEDGER_SCHEMA_VERSION,
physical_format_id: CURRENT_PHYSICAL_FORMAT_ID,
}
}
pub const fn validate(
&self,
ledger: &AllocationLedger,
) -> Result<(), LedgerCompatibilityError> {
self.validate_versions(ledger.ledger_schema_version, ledger.physical_format_id)
}
pub(crate) const fn validate_versions(
&self,
ledger_schema_version: u32,
physical_format_id: u32,
) -> Result<(), LedgerCompatibilityError> {
if ledger_schema_version < self.min_ledger_schema_version {
return Err(LedgerCompatibilityError::UnsupportedLedgerSchemaVersion {
found: ledger_schema_version,
min_supported: self.min_ledger_schema_version,
max_supported: self.max_ledger_schema_version,
});
}
if ledger_schema_version > self.max_ledger_schema_version {
return Err(LedgerCompatibilityError::UnsupportedLedgerSchemaVersion {
found: ledger_schema_version,
min_supported: self.min_ledger_schema_version,
max_supported: self.max_ledger_schema_version,
});
}
if physical_format_id != self.physical_format_id {
return Err(LedgerCompatibilityError::UnsupportedPhysicalFormat {
found: physical_format_id,
supported: self.physical_format_id,
});
}
Ok(())
}
}
impl Default for LedgerCompatibility {
fn default() -> Self {
Self::current()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RecoveredLedger {
ledger: AllocationLedger,
physical_generation: u64,
ledger_schema_version: u32,
envelope_version: u16,
}
impl RecoveredLedger {
pub(crate) const fn from_trusted_parts(
ledger: AllocationLedger,
physical_generation: u64,
envelope_version: u16,
) -> Self {
let ledger_schema_version = ledger.ledger_schema_version;
Self {
ledger,
physical_generation,
ledger_schema_version,
envelope_version,
}
}
#[must_use]
pub const fn ledger(&self) -> &AllocationLedger {
&self.ledger
}
#[must_use]
pub const fn physical_generation(&self) -> u64 {
self.physical_generation
}
#[must_use]
pub const fn current_generation(&self) -> u64 {
self.ledger.current_generation
}
#[must_use]
pub const fn ledger_schema_version(&self) -> u32 {
self.ledger_schema_version
}
#[must_use]
pub const fn envelope_version(&self) -> u16 {
self.envelope_version
}
pub(crate) fn into_ledger(self) -> AllocationLedger {
self.ledger
}
}
impl AllocationHistory {
#[cfg(test)]
pub(crate) const fn from_parts(
records: Vec<AllocationRecord>,
generations: Vec<GenerationRecord>,
) -> Self {
Self {
records,
generations,
}
}
#[must_use]
pub fn records(&self) -> &[AllocationRecord] {
&self.records
}
#[must_use]
pub fn generations(&self) -> &[GenerationRecord] {
&self.generations
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.records.is_empty() && self.generations.is_empty()
}
pub(crate) const fn records_mut(&mut self) -> &mut Vec<AllocationRecord> {
&mut self.records
}
#[cfg(test)]
pub(crate) const fn generations_mut(&mut self) -> &mut Vec<GenerationRecord> {
&mut self.generations
}
pub(crate) fn push_record(&mut self, record: AllocationRecord) {
self.records.push(record);
}
pub(crate) fn push_generation(&mut self, generation: GenerationRecord) {
self.generations.push(generation);
}
}
impl SchemaMetadataRecord {
pub fn new(generation: u64, schema: SchemaMetadata) -> Result<Self, SchemaMetadataError> {
schema.validate()?;
Ok(Self { generation, schema })
}
#[must_use]
pub const fn generation(&self) -> u64 {
self.generation
}
#[must_use]
pub const fn schema(&self) -> &SchemaMetadata {
&self.schema
}
}
impl GenerationRecord {
pub fn new(
generation: u64,
parent_generation: Option<u64>,
runtime_fingerprint: Option<String>,
declaration_count: u32,
committed_at: Option<u64>,
) -> Result<Self, DeclarationSnapshotError> {
validate_runtime_fingerprint(runtime_fingerprint.as_deref())?;
Ok(Self {
generation,
parent_generation,
runtime_fingerprint,
declaration_count,
committed_at,
})
}
#[must_use]
pub const fn generation(&self) -> u64 {
self.generation
}
#[must_use]
pub const fn parent_generation(&self) -> Option<u64> {
self.parent_generation
}
#[must_use]
pub fn runtime_fingerprint(&self) -> Option<&str> {
self.runtime_fingerprint.as_deref()
}
#[must_use]
pub const fn declaration_count(&self) -> u32 {
self.declaration_count
}
#[must_use]
pub const fn committed_at(&self) -> Option<u64> {
self.committed_at
}
}
impl AllocationRecord {
#[must_use]
pub(crate) fn from_declaration(
generation: u64,
declaration: AllocationDeclaration,
state: AllocationState,
) -> Self {
Self {
stable_key: declaration.stable_key,
slot: declaration.slot,
state,
first_generation: generation,
last_seen_generation: generation,
retired_generation: None,
schema_history: vec![
SchemaMetadataRecord::new(generation, declaration.schema)
.expect("declarations validate schema metadata"),
],
}
}
#[must_use]
pub(crate) fn reserved(generation: u64, declaration: AllocationDeclaration) -> Self {
Self::from_declaration(generation, declaration, AllocationState::Reserved)
}
#[must_use]
pub const fn stable_key(&self) -> &StableKey {
&self.stable_key
}
#[must_use]
pub const fn slot(&self) -> &AllocationSlotDescriptor {
&self.slot
}
#[must_use]
pub const fn state(&self) -> AllocationState {
self.state
}
#[must_use]
pub const fn first_generation(&self) -> u64 {
self.first_generation
}
#[must_use]
pub const fn last_seen_generation(&self) -> u64 {
self.last_seen_generation
}
#[must_use]
pub const fn retired_generation(&self) -> Option<u64> {
self.retired_generation
}
#[must_use]
pub fn schema_history(&self) -> &[SchemaMetadataRecord] {
&self.schema_history
}
pub(crate) fn observe_declaration(
&mut self,
generation: u64,
declaration: &AllocationDeclaration,
) {
self.last_seen_generation = generation;
if self.state == AllocationState::Reserved {
self.state = AllocationState::Active;
}
let latest_schema = self.schema_history.last().map(|record| &record.schema);
if latest_schema != Some(&declaration.schema) {
self.schema_history.push(
SchemaMetadataRecord::new(generation, declaration.schema.clone())
.expect("declarations validate schema metadata"),
);
}
}
pub(crate) fn observe_reservation(
&mut self,
generation: u64,
reservation: &AllocationDeclaration,
) {
self.last_seen_generation = generation;
let latest_schema = self.schema_history.last().map(|record| &record.schema);
if latest_schema != Some(&reservation.schema) {
self.schema_history.push(
SchemaMetadataRecord::new(generation, reservation.schema.clone())
.expect("reservations validate schema metadata"),
);
}
}
}
impl AllocationLedger {
pub fn new(
ledger_schema_version: u32,
physical_format_id: u32,
current_generation: u64,
allocation_history: AllocationHistory,
) -> Result<Self, LedgerIntegrityError> {
let ledger = Self {
ledger_schema_version,
physical_format_id,
current_generation,
allocation_history,
};
ledger.validate_integrity()?;
Ok(ledger)
}
pub fn new_committed(
ledger_schema_version: u32,
physical_format_id: u32,
current_generation: u64,
allocation_history: AllocationHistory,
) -> Result<Self, LedgerIntegrityError> {
let ledger = Self::new(
ledger_schema_version,
physical_format_id,
current_generation,
allocation_history,
)?;
ledger.validate_committed_integrity()?;
Ok(ledger)
}
#[must_use]
pub const fn ledger_schema_version(&self) -> u32 {
self.ledger_schema_version
}
#[must_use]
pub const fn physical_format_id(&self) -> u32 {
self.physical_format_id
}
#[must_use]
pub const fn current_generation(&self) -> u64 {
self.current_generation
}
#[must_use]
pub const fn allocation_history(&self) -> &AllocationHistory {
&self.allocation_history
}
}