use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::DocumentId;
use super::{Collaborator, CrdtFormat};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RevisionHistory {
pub revisions: Vec<Revision>,
}
impl RevisionHistory {
#[must_use]
pub fn new() -> Self {
Self {
revisions: Vec::new(),
}
}
pub fn add(&mut self, revision: Revision) {
self.revisions.push(revision);
}
#[must_use]
pub fn latest(&self) -> Option<&Revision> {
self.revisions.last()
}
#[must_use]
pub fn get_version(&self, version: u32) -> Option<&Revision> {
self.revisions.iter().find(|r| r.version == version)
}
#[must_use]
pub fn next_version(&self) -> u32 {
self.revisions
.iter()
.map(|r| r.version)
.max()
.map_or(1, |v| v + 1)
}
#[must_use]
pub fn len(&self) -> usize {
self.revisions.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.revisions.is_empty()
}
}
impl Default for RevisionHistory {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Revision {
pub version: u32,
pub document_id: DocumentId,
pub created: DateTime<Utc>,
pub author: Collaborator,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
}
impl Revision {
#[must_use]
pub fn new(version: u32, document_id: DocumentId, author: Collaborator) -> Self {
Self {
version,
document_id,
created: Utc::now(),
author,
note: None,
tags: Vec::new(),
}
}
#[must_use]
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.note = Some(note.into());
self
}
#[must_use]
pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
self.tags.push(tag.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MaterializationEvent {
pub timestamp: DateTime<Utc>,
pub agent: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub from_crdt_format: Option<CrdtFormat>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub to_crdt_format: Option<CrdtFormat>,
pub reason: MaterializationReason,
}
impl MaterializationEvent {
#[must_use]
pub fn new(agent: impl Into<String>, reason: MaterializationReason) -> Self {
Self {
timestamp: Utc::now(),
agent: agent.into(),
from_crdt_format: None,
to_crdt_format: None,
reason,
}
}
#[must_use]
pub fn with_source_format(mut self, format: CrdtFormat) -> Self {
self.from_crdt_format = Some(format);
self
}
#[must_use]
pub fn with_target_format(mut self, format: CrdtFormat) -> Self {
self.to_crdt_format = Some(format);
self
}
#[must_use]
pub fn cross_tool_exchange(agent: impl Into<String>, from: CrdtFormat, to: CrdtFormat) -> Self {
Self::new(agent, MaterializationReason::CrossToolExchange)
.with_source_format(from)
.with_target_format(to)
}
#[must_use]
pub fn export(agent: impl Into<String>, from: CrdtFormat) -> Self {
Self::new(agent, MaterializationReason::Export).with_source_format(from)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::Display)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum MaterializationReason {
CrossToolExchange,
Export,
Archive,
UserRequest,
}