use core::ops::RangeInclusive;
use time::Date;
use super::StoreError;
use crate::core::{BalanceKey, ResolutionKind};
use crate::log::Hash;
use crate::model::{
AccountHandle, AccountKind, AccountPath, Amount, Balance, Currency, Dimensions, EntryId,
Fingerprint, IdempotencyKey, Label, LedgerId, MemoSalt, PeriodId, PostingRef, Record,
RecordKind, Side,
};
pub type Position = u64;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LedgerInfo {
pub ledger: LedgerId,
pub precision: u8,
pub size: u64,
pub genesis: Hash,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Leaf {
pub position: Position,
pub kind: RecordKind,
pub bytes: Vec<u8>,
}
impl Leaf {
pub fn record<const P: u8>(&self) -> Result<Record<P>, StoreError> {
Record::decode(&self.bytes)
.ok()
.filter(|record| record.kind() == self.kind)
.ok_or_else(|| {
StoreError::integrity("decode", "a leaf does not decode", Some(self.position))
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Page<T, C> {
pub items: Vec<T>,
pub next: Option<C>,
pub size: u64,
pub through: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PageRead<C> {
pub after: Option<C>,
pub limit: u32,
pub through: Option<u64>,
}
impl<C> PageRead<C> {
pub const MAX_LIMIT: u32 = 1000;
#[must_use]
pub const fn first(limit: u32) -> Self {
Self {
after: None,
limit,
through: None,
}
}
#[must_use]
pub fn after(self, cursor: C) -> Self {
Self {
after: Some(cursor),
..self
}
}
#[must_use]
pub fn through(self, through: u64) -> Self {
Self {
through: Some(through),
..self
}
}
pub(crate) fn checked_limit(&self, operation: &'static str) -> Result<u32, StoreError> {
checked_limit(self.limit, operation)
}
}
pub(crate) fn checked_limit(limit: u32, operation: &'static str) -> Result<u32, StoreError> {
if (1..=PageRead::<()>::MAX_LIMIT).contains(&limit) {
Ok(limit)
} else {
Err(StoreError::backend_message(operation, "limit out of range"))
}
}
pub(crate) fn bound(through: Option<u64>, size: u64) -> u64 {
through.map_or(size, |through| through.min(size))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LeafRead {
pub from: Position,
pub through: Option<u64>,
pub kinds: Option<Vec<RecordKind>>,
pub limit: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SealRow {
pub period: PeriodId,
pub position: Position,
pub leaf: Leaf,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccountRow {
pub handle: AccountHandle,
pub path: AccountPath,
pub kind: AccountKind,
pub position: Position,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum EntryRef<'a> {
Id(EntryId),
Key(&'a IdempotencyKey),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StoredEntry {
pub position: Position,
pub id: EntryId,
pub key: IdempotencyKey,
pub fingerprint: Option<Fingerprint>,
pub leaf: Leaf,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum HoldRead {
ById {
id: EntryId,
through: Option<u64>,
},
OpenByAccount {
handle: AccountHandle,
page: PageRead<HoldCursor>,
},
Due {
on_or_before: Date,
page: PageRead<HoldCursor>,
},
}
pub type HoldCursor = (Option<Date>, Position);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HoldRow<const P: u8> {
pub id: EntryId,
pub position: Position,
pub booking: Date,
pub expires: Option<Date>,
pub debit: AccountHandle,
pub credit: AccountHandle,
pub currency: Currency,
pub amount: Amount<P>,
pub resolution: Option<(EntryId, ResolutionKind)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum BalanceScope {
Current,
At(Position),
Sealed(PeriodId),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BalanceRead {
pub scope: BalanceScope,
pub keys: Option<Vec<BalanceKey>>,
pub page: PageRead<BalanceKey>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum PostingOrder {
Position,
Booking,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PostingRead {
pub key: BalanceKey,
pub order: PostingOrder,
pub booking: Option<RangeInclusive<Date>>,
pub value: Option<RangeInclusive<Date>>,
pub dimensions: Vec<(Label, Label)>,
pub page: PageRead<PostingCursor>,
}
impl PostingRead {
pub const MAX_DIMENSIONS: usize = 8;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct PostingCursor {
pub booking: Option<Date>,
pub position: Position,
pub index: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PostingLine<const P: u8> {
pub position: Position,
pub index: u16,
pub entry: EntryId,
pub side: Side,
pub amount: Amount<P>,
pub booking: Date,
pub value: Date,
pub dimensions: Dimensions,
pub balance_after: Balance<P>,
pub cleared: Amount<P>,
}
impl<const P: u8> PostingLine<P> {
#[must_use]
pub const fn posting_ref(&self) -> PostingRef {
PostingRef::at(self.entry, self.index)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Memo {
None,
Kept {
text: String,
salt: MemoSalt,
},
Erased,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Erasure {
Erased,
AlreadyErased,
NoMemo,
UnknownEntry,
}
pub(crate) fn paged<T, C>(
mut rows: Vec<(C, T)>,
limit: u32,
size: u64,
through: u64,
) -> Page<T, C> {
let limit = usize::try_from(limit).unwrap_or(usize::MAX);
let more = rows.len() > limit;
rows.truncate(limit);
let mut next = None;
let items = rows
.into_iter()
.map(|(cursor, item)| {
next = Some(cursor);
item
})
.collect();
Page {
items,
next: if more { next } else { None },
size,
through,
}
}