1use std::path::PathBuf;
4
5use thiserror::Error;
6
7use crate::core::BeadId;
8use crate::core::CoreError;
9use crate::error::{Effect, Transience};
10
11#[derive(Error, Debug)]
13#[non_exhaustive]
14pub enum SyncError {
15 #[error("failed to open repository at {0}: {1}")]
16 OpenRepo(PathBuf, #[source] git2::Error),
17
18 #[error("failed to fetch from remote: {0}")]
19 Fetch(#[source] git2::Error),
20
21 #[error("local ref not found: {0}")]
22 NoLocalRef(String),
23
24 #[error("remote ref not found: {0}")]
25 NoRemoteRef(String),
26
27 #[error("failed to find merge base: {0}")]
28 MergeBase(#[source] git2::Error),
29
30 #[error("missing file in tree: {0}")]
31 MissingFile(String),
32
33 #[error("expected blob but got different object type: {0}")]
34 NotABlob(&'static str),
35
36 #[error("failed to write blob: {0}")]
37 WriteBlob(#[source] git2::Error),
38
39 #[error("failed to build tree: {0}")]
40 BuildTree(#[source] git2::Error),
41
42 #[error("failed to create commit: {0}")]
43 Commit(#[source] git2::Error),
44
45 #[error("push rejected (non-fast-forward)")]
46 NonFastForward,
47
48 #[error("failed to push: {0}")]
49 Push(#[source] git2::Error),
50
51 #[error("too many sync retries ({0})")]
52 TooManyRetries(usize),
53
54 #[error("ID collision detected: {0} and {1} have same ID but different content")]
55 IdCollision(BeadId, BeadId),
56
57 #[error("no common ancestor between local and remote")]
58 NoCommonAncestor,
59
60 #[error(transparent)]
61 Wire(#[from] WireError),
62
63 #[error("merge conflict: {errors:?}")]
64 MergeConflict { errors: Vec<CoreError> },
65
66 #[error("collision resolution failed: {0}")]
67 CollisionResolution(CoreError),
68
69 #[error(transparent)]
70 PushRejected(#[from] PushRejected),
71
72 #[error("init failed: {0}")]
73 InitFailed(#[source] git2::Error),
74
75 #[error("git operation failed: {0}")]
76 Git(#[from] git2::Error),
77}
78
79impl SyncError {
80 pub fn transience(&self) -> Transience {
82 match self {
83 SyncError::Fetch(_)
84 | SyncError::NonFastForward
85 | SyncError::Push(_)
86 | SyncError::PushRejected(_)
87 | SyncError::TooManyRetries(_)
88 | SyncError::InitFailed(_) => Transience::Retryable,
89
90 SyncError::OpenRepo(_, _)
91 | SyncError::NoLocalRef(_)
92 | SyncError::NoRemoteRef(_)
93 | SyncError::MergeBase(_)
94 | SyncError::MissingFile(_)
95 | SyncError::NotABlob(_)
96 | SyncError::WriteBlob(_)
97 | SyncError::BuildTree(_)
98 | SyncError::Commit(_)
99 | SyncError::IdCollision(_, _)
100 | SyncError::NoCommonAncestor
101 | SyncError::Wire(_)
102 | SyncError::MergeConflict { .. }
103 | SyncError::CollisionResolution(_)
104 | SyncError::Git(_) => Transience::Permanent,
105 }
106 }
107
108 pub fn effect(&self) -> Effect {
110 match self {
111 SyncError::NonFastForward
113 | SyncError::Push(_)
114 | SyncError::PushRejected(_)
115 | SyncError::TooManyRetries(_)
116 | SyncError::InitFailed(_) => Effect::Some,
117
118 SyncError::Git(_) => Effect::Unknown,
120
121 _ => Effect::None,
123 }
124 }
125}
126
127#[derive(Error, Debug)]
129pub enum WireError {
130 #[error("JSON error: {0}")]
131 Json(#[from] serde_json::Error),
132
133 #[error("UTF-8 error: {0}")]
134 Utf8(#[from] std::string::FromUtf8Error),
135
136 #[error("missing required field: {0}")]
137 MissingField(&'static str),
138
139 #[error("invalid field value: {0}")]
140 InvalidValue(String),
141}
142
143#[derive(Error, Debug)]
145#[error("push rejected: {message}")]
146pub struct PushRejected {
147 pub message: String,
148}