use candid::CandidType;
use serde::{Deserialize, Serialize};
use crate::{
identity::CallerId,
types::{PageData, QueryPage, QueryPageError},
};
#[derive(
CandidType, Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord,
)]
pub struct RecordId(u64);
impl From<u64> for RecordId {
fn from(value: u64) -> Self {
Self(value)
}
}
impl RecordId {
pub fn into_inner(&self) -> u64 {
self.0
}
pub fn next(self) -> Self {
Self(self.0 + 1)
}
}
#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
pub struct MigratedRecords<Record> {
pub removed: u64,
pub next_id: u64,
pub records: Vec<Record>,
}
pub trait Searchable<Record> {
fn test(&self, record: &Record) -> bool;
}
pub trait Recordable<Record, RecordTopic, Search: Searchable<Record>> {
fn record_find_all(&self) -> &[Record];
fn record_push(&mut self, caller: CallerId, topic: RecordTopic, content: String) -> RecordId;
fn record_update(&mut self, record_id: RecordId, done: String);
fn record_migrate(&mut self, max: u32) -> MigratedRecords<Record>;
fn record_find_by_page(
&self,
page: &QueryPage,
max: u32,
search: &Option<Search>,
) -> Result<PageData<&Record>, QueryPageError> {
let list = self.record_find_all();
if let Some(search) = search {
return page.query_desc_by_list_and_filter(list, max, |item| search.test(item));
}
page.query_desc_by_list(list, max)
}
}
pub mod basic {
use std::collections::HashSet;
use candid::CandidType;
use serde::{Deserialize, Serialize};
use crate::{
functions::{
record::MigratedRecords,
types::{RecordId, Recordable, Searchable},
},
identity::CallerId,
types::TimestampNanos,
};
pub type RecordTopic = u8;
#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
pub struct Record {
pub id: RecordId,
pub created: TimestampNanos,
pub caller: CallerId,
pub topic: RecordTopic,
pub content: String,
pub done: Option<(TimestampNanos, String)>,
}
impl Record {
#[inline]
fn same(&self, id: &RecordId) -> bool {
self.id == *id
}
#[inline]
fn update(&mut self, done: String) {
self.done = Some((crate::times::now(), done));
}
}
#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
pub struct RecordSearch {
pub id: Option<(Option<RecordId>, Option<RecordId>)>,
pub created: Option<(Option<TimestampNanos>, Option<TimestampNanos>)>,
pub caller: Option<HashSet<CallerId>>,
pub topic: Option<HashSet<RecordTopic>>,
pub content: Option<String>,
}
impl Searchable<Record> for RecordSearch {
#[allow(unused)]
#[inline]
fn test(&self, record: &Record) -> bool {
if let Some((id_min, id_max)) = &self.id {
if let Some(id_min) = &id_min {
if record.id < *id_min {
return false;
}
}
if let Some(id_max) = &id_max {
if *id_max < record.id {
return false;
}
}
}
if let Some(created) = self.created {
let (created_min, created_max) = created;
if let Some(created_min) = created_min {
if record.created < created_min {
return false;
}
}
if let Some(created_max) = created_max {
if created_max < record.created {
return false;
}
}
}
if let Some(caller) = &self.caller {
if !caller.contains(&record.caller) {
return false;
}
}
if let Some(topic) = &self.topic {
if !topic.contains(&record.topic) {
return false;
}
}
if let Some(content) = &self.content {
if !record.content.contains(content) {
return false;
}
}
true
}
}
#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
pub struct Records {
pub max: u64,
pub removed: u64,
pub next_id: RecordId,
pub records: Vec<Record>,
}
impl Default for Records {
fn default() -> Self {
Self {
max: 1024 * 64, removed: Default::default(),
next_id: Default::default(),
records: Default::default(),
}
}
}
impl Recordable<Record, RecordTopic, RecordSearch> for Records {
fn record_find_all(&self) -> &[Record] {
&self.records
}
fn record_push(
&mut self,
caller: CallerId,
topic: RecordTopic,
content: String,
) -> RecordId {
if self.max <= self.records.len() as u64 {
let (_migrated, left) = self.records.split_at(1);
self.records = left.to_owned();
self.removed += 1;
}
let id = self.next_id;
self.next_id = self.next_id.next();
self.records.push(Record {
id,
created: crate::times::now(),
caller,
topic,
content,
done: None,
});
id
}
fn record_update(&mut self, record_id: RecordId, done: String) {
let list = &mut self.records;
let mut index = list.len();
loop {
index -= 1;
if let Some(item) = list.get_mut(index) {
if item.same(&record_id) {
item.update(done);
break;
}
} else {
break;
}
if index == 0 {
break;
}
}
}
fn record_migrate(&mut self, max: u32) -> MigratedRecords<Record> {
let removed = self.removed;
let next_id = self.next_id.into_inner();
let records = if self.records.len() < max as usize {
std::mem::take(&mut self.records) } else {
let (migrated, left) = self.records.split_at(max as usize);
let migrated = migrated.to_owned();
self.records = left.to_owned();
migrated
};
MigratedRecords {
removed,
next_id,
records,
}
}
}
#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
pub struct RecordSearchArg {
pub id: Option<(Option<u64>, Option<u64>)>,
pub created: Option<(Option<u64>, Option<u64>)>,
pub caller: Option<HashSet<CallerId>>,
pub topic: Option<HashSet<String>>,
pub content: Option<String>,
}
impl RecordSearchArg {
pub fn into<E, F: Fn(&str) -> Result<RecordTopic, E>>(
self,
f: F,
) -> Result<RecordSearch, E> {
Ok(RecordSearch {
id: self
.id
.map(|(a, b)| (a.map(|a| a.into()), b.map(|b| b.into()))),
created: self
.created
.map(|(a, b)| (a.map(|a| (a as i128).into()), b.map(|b| (b as i128).into()))),
caller: self.caller,
topic: self
.topic
.map(|topic| {
topic
.iter()
.map(|t| f(t))
.collect::<Result<HashSet<_>, _>>()
})
.transpose()?,
content: self.content,
})
}
}
}