kanban_persistence/
traits.rs1use crate::PersistenceResult;
2use async_trait::async_trait;
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::path::{Path, PathBuf};
6use uuid::Uuid;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct PersistenceMetadata {
11 pub instance_id: Uuid,
13 pub saved_at: DateTime<Utc>,
15}
16
17impl PersistenceMetadata {
18 pub fn new(instance_id: Uuid) -> Self {
19 Self {
20 instance_id,
21 saved_at: Utc::now(),
22 }
23 }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct StoreSnapshot {
29 pub data: Vec<u8>,
31 pub metadata: PersistenceMetadata,
33}
34
35#[derive(Debug, Clone)]
37pub enum PersistenceEvent {
38 Saved(PersistenceMetadata),
40 ExternalChangeDetected {
42 path: PathBuf,
43 saved_at: DateTime<Utc>,
44 },
45 ConflictDetected { reason: String },
47 Error(String),
49}
50
51#[async_trait]
54pub trait PersistenceStore: Send + Sync {
55 async fn save(&self, snapshot: StoreSnapshot) -> PersistenceResult<PersistenceMetadata>;
57
58 async fn load(&self) -> PersistenceResult<(StoreSnapshot, PersistenceMetadata)>;
60
61 async fn exists(&self) -> bool;
63
64 fn path(&self) -> &Path;
66
67 fn instance_id(&self) -> uuid::Uuid;
69
70 #[allow(clippy::type_complexity)]
76 fn load_sync(&self) -> PersistenceResult<Option<(StoreSnapshot, PersistenceMetadata)>> {
77 Err(crate::PersistenceError::Unsupported(
78 "load_sync not supported by this backend".into(),
79 ))
80 }
81
82 async fn close(&self) {}
91}
92
93#[async_trait]
96pub trait ChangeDetector: Send + Sync {
97 async fn start_watching(&self, path: PathBuf) -> PersistenceResult<()>;
99
100 async fn stop_watching(&self) -> PersistenceResult<()>;
102
103 fn subscribe(&self) -> tokio::sync::broadcast::Receiver<ChangeEvent>;
106
107 fn is_watching(&self) -> bool;
109}
110
111#[derive(Debug, Clone)]
113pub struct ChangeEvent {
114 pub path: PathBuf,
116 pub detected_at: DateTime<Utc>,
118}
119
120pub trait Serializer<T: Send + Sync>: Send + Sync {
123 fn serialize(&self, data: &T) -> PersistenceResult<Vec<u8>>;
125
126 fn deserialize(&self, bytes: &[u8]) -> PersistenceResult<T>;
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
132pub enum FormatVersion {
133 V1,
134 V2,
135 V3,
136 V4,
137 V5,
138}
139
140impl FormatVersion {
141 pub fn as_u32(self) -> u32 {
142 match self {
143 Self::V1 => 1,
144 Self::V2 => 2,
145 Self::V3 => 3,
146 Self::V4 => 4,
147 Self::V5 => 5,
148 }
149 }
150
151 pub fn from_u32(v: u32) -> Option<Self> {
152 match v {
153 1 => Some(Self::V1),
154 2 => Some(Self::V2),
155 3 => Some(Self::V3),
156 4 => Some(Self::V4),
157 5 => Some(Self::V5),
158 _ => None,
159 }
160 }
161}
162
163#[async_trait]
165pub trait MigrationStrategy: Send + Sync {
166 async fn detect_version(&self, path: &Path) -> PersistenceResult<FormatVersion>;
168
169 async fn migrate(
172 &self,
173 from: FormatVersion,
174 to: FormatVersion,
175 path: &Path,
176 ) -> PersistenceResult<PathBuf>;
177}
178
179pub trait ConflictResolver: Send + Sync {
181 fn should_use_external(
184 &self,
185 local_metadata: &PersistenceMetadata,
186 external_metadata: &PersistenceMetadata,
187 ) -> bool;
188
189 fn explain_resolution(
191 &self,
192 local_metadata: &PersistenceMetadata,
193 external_metadata: &PersistenceMetadata,
194 ) -> String;
195}