1use super::file_repository::FileRepository;
11use super::git_repository::GitRepository;
12use super::repository::{BacklogItemRepository, SprintRepository};
13#[cfg(feature = "sqlite")]
14use super::sqlite_repository::SqliteRepository;
15use crate::backlog::{BacklogItem, ItemId};
16use crate::config::StorageBackend;
17use crate::error::Result;
18use crate::sprint::{Sprint, SprintId};
19use std::path::PathBuf;
20
21#[derive(Debug, Clone)]
23pub enum Backend {
24 File(FileRepository),
26 Git(GitRepository),
28 #[cfg(feature = "sqlite")]
30 Sqlite(SqliteRepository),
31}
32
33impl Backend {
34 pub async fn open(root: impl Into<PathBuf>, backend: StorageBackend) -> Result<Self> {
39 let root = root.into();
40 match backend {
41 StorageBackend::File => Ok(Backend::File(FileRepository::new(root))),
42 StorageBackend::Git => Ok(Backend::Git(GitRepository::new(root))),
43 #[cfg(feature = "sqlite")]
44 StorageBackend::Sqlite => Ok(Backend::Sqlite(SqliteRepository::new(root))),
45 }
46 }
47
48 pub(crate) async fn open_for_write(
50 root: impl Into<PathBuf>,
51 backend: StorageBackend,
52 ) -> Result<Self> {
53 let root = root.into();
54 match backend {
55 StorageBackend::File => Ok(Backend::File(FileRepository::new(root))),
56 StorageBackend::Git => Ok(Backend::Git(GitRepository::new(root).prepare().await?)),
57 #[cfg(feature = "sqlite")]
58 StorageBackend::Sqlite => Ok(Backend::Sqlite(SqliteRepository::new(root))),
59 }
60 }
61
62 pub(crate) async fn commit(&self, message: &str) -> Result<()> {
65 match self {
66 Backend::File(_) => Ok(()),
67 Backend::Git(repository) => repository.commit(message).await,
68 #[cfg(feature = "sqlite")]
69 Backend::Sqlite(_) => Ok(()),
70 }
71 }
72}
73
74impl BacklogItemRepository for Backend {
75 async fn save(&self, item: &BacklogItem) -> Result<()> {
76 match self {
77 Backend::File(r) => BacklogItemRepository::save(r, item).await,
78 Backend::Git(r) => BacklogItemRepository::save(r, item).await,
79 #[cfg(feature = "sqlite")]
80 Backend::Sqlite(r) => BacklogItemRepository::save(r, item).await,
81 }
82 }
83
84 async fn load(&self, id: &ItemId) -> Result<BacklogItem> {
85 match self {
86 Backend::File(r) => BacklogItemRepository::load(r, id).await,
87 Backend::Git(r) => BacklogItemRepository::load(r, id).await,
88 #[cfg(feature = "sqlite")]
89 Backend::Sqlite(r) => BacklogItemRepository::load(r, id).await,
90 }
91 }
92
93 async fn list(&self) -> Result<Vec<BacklogItem>> {
94 match self {
95 Backend::File(r) => BacklogItemRepository::list(r).await,
96 Backend::Git(r) => BacklogItemRepository::list(r).await,
97 #[cfg(feature = "sqlite")]
98 Backend::Sqlite(r) => BacklogItemRepository::list(r).await,
99 }
100 }
101
102 async fn delete(&self, id: &ItemId) -> Result<()> {
103 match self {
104 Backend::File(r) => BacklogItemRepository::delete(r, id).await,
105 Backend::Git(r) => BacklogItemRepository::delete(r, id).await,
106 #[cfg(feature = "sqlite")]
107 Backend::Sqlite(r) => BacklogItemRepository::delete(r, id).await,
108 }
109 }
110
111 async fn archive(&self, id: &ItemId) -> Result<PathBuf> {
112 match self {
113 Backend::File(r) => r.archive(id).await,
114 Backend::Git(r) => r.archive(id).await,
115 #[cfg(feature = "sqlite")]
116 Backend::Sqlite(r) => r.archive(id).await,
117 }
118 }
119
120 async fn next_id(&self, prefix: &str) -> Result<ItemId> {
121 match self {
122 Backend::File(r) => r.next_id(prefix).await,
123 Backend::Git(r) => r.next_id(prefix).await,
124 #[cfg(feature = "sqlite")]
125 Backend::Sqlite(r) => r.next_id(prefix).await,
126 }
127 }
128}
129
130impl SprintRepository for Backend {
131 async fn save(&self, sprint: &Sprint) -> Result<()> {
132 match self {
133 Backend::File(r) => SprintRepository::save(r, sprint).await,
134 Backend::Git(r) => SprintRepository::save(r, sprint).await,
135 #[cfg(feature = "sqlite")]
136 Backend::Sqlite(r) => SprintRepository::save(r, sprint).await,
137 }
138 }
139
140 async fn load(&self, id: &SprintId) -> Result<Sprint> {
141 match self {
142 Backend::File(r) => SprintRepository::load(r, id).await,
143 Backend::Git(r) => SprintRepository::load(r, id).await,
144 #[cfg(feature = "sqlite")]
145 Backend::Sqlite(r) => SprintRepository::load(r, id).await,
146 }
147 }
148
149 async fn list(&self) -> Result<Vec<Sprint>> {
150 match self {
151 Backend::File(r) => SprintRepository::list(r).await,
152 Backend::Git(r) => SprintRepository::list(r).await,
153 #[cfg(feature = "sqlite")]
154 Backend::Sqlite(r) => SprintRepository::list(r).await,
155 }
156 }
157
158 async fn delete(&self, id: &SprintId) -> Result<()> {
159 match self {
160 Backend::File(r) => SprintRepository::delete(r, id).await,
161 Backend::Git(r) => SprintRepository::delete(r, id).await,
162 #[cfg(feature = "sqlite")]
163 Backend::Sqlite(r) => SprintRepository::delete(r, id).await,
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::backlog::Status;
172 use crate::rank::Rank;
173 use chrono::{TimeZone, Utc};
174 use tempfile::TempDir;
175
176 #[tokio::test]
178 async fn file_backend_dispatches_save_and_load() {
179 let dir = TempDir::new().expect("temp dir");
180 let backend = Backend::open(dir.path().join(".pinto"), StorageBackend::File)
181 .await
182 .expect("open file backend");
183
184 let item = BacklogItem::new(
185 ItemId::new("T", 1),
186 "Dispatch",
187 Status::new("todo"),
188 Rank::after(None),
189 Utc.timestamp_opt(1_000, 0).single().unwrap(),
190 )
191 .expect("valid item");
192
193 BacklogItemRepository::save(&backend, &item)
194 .await
195 .expect("save via backend");
196 let loaded = BacklogItemRepository::load(&backend, &item.id)
197 .await
198 .expect("load via backend");
199 assert_eq!(loaded, item);
200 }
201
202 #[cfg(feature = "sqlite")]
204 #[tokio::test]
205 async fn sqlite_backend_dispatches_save_and_load() {
206 let dir = TempDir::new().expect("temp dir");
207 let backend = Backend::open(dir.path().join(".pinto"), StorageBackend::Sqlite)
208 .await
209 .expect("open sqlite backend");
210
211 let item = BacklogItem::new(
212 ItemId::new("T", 1),
213 "Dispatch",
214 Status::new("todo"),
215 Rank::after(None),
216 Utc.timestamp_opt(1_000, 0).single().unwrap(),
217 )
218 .expect("valid item");
219
220 BacklogItemRepository::save(&backend, &item)
221 .await
222 .expect("save via backend");
223 let loaded = BacklogItemRepository::load(&backend, &item.id)
224 .await
225 .expect("load via backend");
226 assert_eq!(loaded, item);
227 }
228}