Skip to main content

winterbaume_codecommit/
views.rs

1//! Serde-compatible view types for CodeCommit state snapshots.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6use winterbaume_core::{StateChangeNotifier, StateViewError, StatefulService};
7
8use crate::handlers::CodeCommitService;
9use crate::state::CodeCommitState;
10
11/// Serializable view of the entire CodeCommit state for one account/region.
12#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13pub struct CodeCommitStateView {
14    /// Repositories keyed by repository name.
15    #[serde(default)]
16    pub repositories: HashMap<String, RepositoryView>,
17    #[serde(default)]
18    pub branches: HashMap<String, HashMap<String, BranchView>>,
19    #[serde(default)]
20    pub commits: HashMap<String, HashMap<String, CommitView>>,
21    #[serde(default)]
22    pub files: HashMap<String, HashMap<String, HashMap<String, FileEntryView>>>,
23    #[serde(default)]
24    pub pull_requests: HashMap<String, PullRequestView>,
25    #[serde(default)]
26    pub pull_request_counter: u64,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct RepositoryView {
31    pub repository_id: String,
32    pub repository_name: String,
33    pub arn: String,
34    pub description: String,
35    pub clone_url_http: String,
36    pub clone_url_ssh: String,
37    pub creation_date: String,
38    pub last_modified_date: String,
39    pub account_id: String,
40    #[serde(default)]
41    pub default_branch: Option<String>,
42    #[serde(default)]
43    pub tags: HashMap<String, String>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct BranchView {
48    pub branch_name: String,
49    pub commit_id: String,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct CommitView {
54    pub commit_id: String,
55    pub tree_id: String,
56    pub parent_ids: Vec<String>,
57    pub message: String,
58    pub author_name: String,
59    pub author_email: String,
60    pub date: String,
61}
62
63/// File content is excluded from snapshots; restored as empty bytes.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct FileEntryView {
66    pub file_path: String,
67    pub blob_id: String,
68    pub file_mode: String,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct PullRequestView {
73    pub pull_request_id: String,
74    pub title: String,
75    pub description: String,
76    pub status: String,
77    pub repository_name: String,
78    pub source_reference: String,
79    pub destination_reference: String,
80    pub source_commit: String,
81    pub destination_commit: String,
82    pub creation_date: String,
83    pub last_activity_date: String,
84    pub author_arn: String,
85}
86
87fn parse_datetime(s: &str) -> chrono::DateTime<chrono::Utc> {
88    chrono::DateTime::parse_from_rfc3339(s)
89        .map(|dt| dt.with_timezone(&chrono::Utc))
90        .unwrap_or_else(|_| chrono::Utc::now())
91}
92
93// --- From internal types to view types ---
94
95impl From<&CodeCommitState> for CodeCommitStateView {
96    fn from(state: &CodeCommitState) -> Self {
97        let repositories = state
98            .repositories
99            .iter()
100            .map(|(k, r)| {
101                (
102                    k.clone(),
103                    RepositoryView {
104                        repository_id: r.repository_id.clone(),
105                        repository_name: r.repository_name.clone(),
106                        arn: r.arn.clone(),
107                        description: r.description.clone(),
108                        clone_url_http: r.clone_url_http.clone(),
109                        clone_url_ssh: r.clone_url_ssh.clone(),
110                        creation_date: r.creation_date.to_rfc3339(),
111                        last_modified_date: r.last_modified_date.to_rfc3339(),
112                        account_id: r.account_id.clone(),
113                        default_branch: r.default_branch.clone(),
114                        tags: r.tags.clone(),
115                    },
116                )
117            })
118            .collect();
119
120        let branches = state
121            .branches
122            .iter()
123            .map(|(repo, bmap)| {
124                (
125                    repo.clone(),
126                    bmap.iter()
127                        .map(|(bn, b)| {
128                            (
129                                bn.clone(),
130                                BranchView {
131                                    branch_name: b.branch_name.clone(),
132                                    commit_id: b.commit_id.clone(),
133                                },
134                            )
135                        })
136                        .collect(),
137                )
138            })
139            .collect();
140
141        let commits = state
142            .commits
143            .iter()
144            .map(|(repo, cmap)| {
145                (
146                    repo.clone(),
147                    cmap.iter()
148                        .map(|(cid, c)| {
149                            (
150                                cid.clone(),
151                                CommitView {
152                                    commit_id: c.commit_id.clone(),
153                                    tree_id: c.tree_id.clone(),
154                                    parent_ids: c.parent_ids.clone(),
155                                    message: c.message.clone(),
156                                    author_name: c.author_name.clone(),
157                                    author_email: c.author_email.clone(),
158                                    date: c.date.to_rfc3339(),
159                                },
160                            )
161                        })
162                        .collect(),
163                )
164            })
165            .collect();
166
167        let files = state
168            .files
169            .iter()
170            .map(|(repo, commit_map)| {
171                (
172                    repo.clone(),
173                    commit_map
174                        .iter()
175                        .map(|(cid, fmap)| {
176                            (
177                                cid.clone(),
178                                fmap.iter()
179                                    .map(|(fp, fe)| {
180                                        (
181                                            fp.clone(),
182                                            FileEntryView {
183                                                file_path: fe.file_path.clone(),
184                                                blob_id: fe.blob_id.clone(),
185                                                file_mode: fe.file_mode.clone(),
186                                            },
187                                        )
188                                    })
189                                    .collect(),
190                            )
191                        })
192                        .collect(),
193                )
194            })
195            .collect();
196
197        let pull_requests = state
198            .pull_requests
199            .iter()
200            .map(|(id, pr)| {
201                (
202                    id.clone(),
203                    PullRequestView {
204                        pull_request_id: pr.pull_request_id.clone(),
205                        title: pr.title.clone(),
206                        description: pr.description.clone(),
207                        status: pr.status.clone(),
208                        repository_name: pr.repository_name.clone(),
209                        source_reference: pr.source_reference.clone(),
210                        destination_reference: pr.destination_reference.clone(),
211                        source_commit: pr.source_commit.clone(),
212                        destination_commit: pr.destination_commit.clone(),
213                        creation_date: pr.creation_date.to_rfc3339(),
214                        last_activity_date: pr.last_activity_date.to_rfc3339(),
215                        author_arn: pr.author_arn.clone(),
216                    },
217                )
218            })
219            .collect();
220
221        CodeCommitStateView {
222            repositories,
223            branches,
224            commits,
225            files,
226            pull_requests,
227            pull_request_counter: state.pull_request_counter,
228        }
229    }
230}
231
232// --- From view types to internal types ---
233
234impl From<CodeCommitStateView> for CodeCommitState {
235    fn from(view: CodeCommitStateView) -> Self {
236        let repositories = view
237            .repositories
238            .into_iter()
239            .map(|(k, r)| {
240                (
241                    k,
242                    crate::types::Repository {
243                        repository_id: r.repository_id,
244                        repository_name: r.repository_name,
245                        arn: r.arn,
246                        description: r.description,
247                        clone_url_http: r.clone_url_http,
248                        clone_url_ssh: r.clone_url_ssh,
249                        creation_date: parse_datetime(&r.creation_date),
250                        last_modified_date: parse_datetime(&r.last_modified_date),
251                        account_id: r.account_id,
252                        default_branch: r.default_branch,
253                        tags: r.tags,
254                    },
255                )
256            })
257            .collect();
258
259        let branches = view
260            .branches
261            .into_iter()
262            .map(|(repo, bmap)| {
263                (
264                    repo,
265                    bmap.into_iter()
266                        .map(|(bn, b)| {
267                            (
268                                bn,
269                                crate::types::Branch {
270                                    branch_name: b.branch_name,
271                                    commit_id: b.commit_id,
272                                },
273                            )
274                        })
275                        .collect(),
276                )
277            })
278            .collect();
279
280        let commits = view
281            .commits
282            .into_iter()
283            .map(|(repo, cmap)| {
284                (
285                    repo,
286                    cmap.into_iter()
287                        .map(|(cid, c)| {
288                            (
289                                cid,
290                                crate::types::CommitRecord {
291                                    commit_id: c.commit_id,
292                                    tree_id: c.tree_id,
293                                    parent_ids: c.parent_ids,
294                                    message: c.message,
295                                    author_name: c.author_name,
296                                    author_email: c.author_email,
297                                    date: parse_datetime(&c.date),
298                                },
299                            )
300                        })
301                        .collect(),
302                )
303            })
304            .collect();
305
306        let files = view
307            .files
308            .into_iter()
309            .map(|(repo, commit_map)| {
310                (
311                    repo,
312                    commit_map
313                        .into_iter()
314                        .map(|(cid, fmap)| {
315                            (
316                                cid,
317                                fmap.into_iter()
318                                    .map(|(fp, fe)| {
319                                        (
320                                            fp,
321                                            crate::types::FileEntry {
322                                                file_path: fe.file_path,
323                                                blob_id: fe.blob_id,
324                                                file_mode: fe.file_mode,
325                                            },
326                                        )
327                                    })
328                                    .collect(),
329                            )
330                        })
331                        .collect(),
332                )
333            })
334            .collect();
335
336        let pull_requests = view
337            .pull_requests
338            .into_iter()
339            .map(|(id, pr)| {
340                (
341                    id,
342                    crate::types::PullRequestRecord {
343                        pull_request_id: pr.pull_request_id,
344                        title: pr.title,
345                        description: pr.description,
346                        status: pr.status,
347                        repository_name: pr.repository_name,
348                        source_reference: pr.source_reference,
349                        destination_reference: pr.destination_reference,
350                        source_commit: pr.source_commit,
351                        destination_commit: pr.destination_commit,
352                        creation_date: parse_datetime(&pr.creation_date),
353                        last_activity_date: parse_datetime(&pr.last_activity_date),
354                        author_arn: pr.author_arn,
355                    },
356                )
357            })
358            .collect();
359
360        CodeCommitState {
361            repositories,
362            branches,
363            commits,
364            files,
365            pull_requests,
366            pull_request_counter: view.pull_request_counter,
367        }
368    }
369}
370
371// --- StatefulService implementation ---
372
373impl StatefulService for CodeCommitService {
374    type StateView = CodeCommitStateView;
375
376    async fn snapshot(&self, account_id: &str, region: &str) -> Self::StateView {
377        let state = self.state.get(account_id, region);
378        let guard = state.read().await;
379        CodeCommitStateView::from(&*guard)
380    }
381
382    async fn restore(
383        &self,
384        account_id: &str,
385        region: &str,
386        view: Self::StateView,
387    ) -> Result<(), StateViewError> {
388        let state = self.state.get(account_id, region);
389        {
390            let mut guard = state.write().await;
391            *guard = CodeCommitState::from(view);
392        }
393        self.notify_state_changed(account_id, region).await;
394        Ok(())
395    }
396
397    async fn merge(
398        &self,
399        account_id: &str,
400        region: &str,
401        view: Self::StateView,
402    ) -> Result<(), StateViewError> {
403        let state = self.state.get(account_id, region);
404        {
405            let mut guard = state.write().await;
406            let merged = CodeCommitState::from(view);
407            for (k, v) in merged.repositories {
408                guard.repositories.insert(k, v);
409            }
410            for (k, v) in merged.branches {
411                guard.branches.insert(k, v);
412            }
413            for (k, v) in merged.commits {
414                guard.commits.insert(k, v);
415            }
416            for (k, v) in merged.files {
417                guard.files.insert(k, v);
418            }
419            for (k, v) in merged.pull_requests {
420                guard.pull_requests.insert(k, v);
421            }
422        }
423        self.notify_state_changed(account_id, region).await;
424        Ok(())
425    }
426
427    fn notifier(&self) -> &StateChangeNotifier<Self::StateView> {
428        &self.notifier
429    }
430}