claw_branch/error.rs
1//! Error types for branch orchestration.
2
3use std::path::PathBuf;
4
5use thiserror::Error;
6use uuid::Uuid;
7
8/// The result type used across the branch engine.
9pub type BranchResult<T> = Result<T, BranchError>;
10
11/// Enumerates errors produced by branching, snapshotting, merging, and metrics code.
12#[derive(Debug, Error)]
13pub enum BranchError {
14 /// Indicates a runtime configuration is invalid.
15 #[error("invalid config: {0}")]
16 InvalidConfig(String),
17 /// Indicates permission checks denied the operation.
18 #[error("permission denied: {0}")]
19 PermissionDenied(String),
20 /// Indicates branch data escaped workspace-scoped query isolation.
21 #[error("workspace isolation violation: expected {expected}, found {found}")]
22 WorkspaceIsolationViolation {
23 /// The expected workspace id configured for this engine.
24 expected: Uuid,
25 /// The unexpected workspace id returned by persistence.
26 found: Uuid,
27 },
28 /// Indicates a branch name failed strict path-safe validation.
29 #[error("invalid branch name: {0}")]
30 InvalidBranchName(String),
31 /// Indicates the workspace has reached its configured branch limit.
32 #[error("branch limit exceeded")]
33 BranchLimitExceeded,
34 /// Wraps SQLx database failures.
35 #[error("database error: {0}")]
36 Database(#[from] sqlx::Error),
37 /// Wraps SQLx migration failures.
38 #[error("migration error: {0}")]
39 Migration(#[from] sqlx::migrate::MigrateError),
40 /// Wraps filesystem errors.
41 #[error("io error: {0}")]
42 Io(#[from] std::io::Error),
43 /// Wraps JSON serialization errors.
44 #[error("serialization error: {0}")]
45 Serialization(#[from] serde_json::Error),
46 /// Indicates a branch lookup failed.
47 #[error("branch not found: {0}")]
48 BranchNotFound(Uuid),
49 /// Indicates a workspace already has a branch with the same logical name.
50 #[error("branch already exists: {0}")]
51 BranchAlreadyExists(String),
52 /// Indicates an operation requires an active branch.
53 #[error("branch {id} is not active: {status}")]
54 BranchNotActive {
55 /// The branch identifier that failed the active-state check.
56 id: Uuid,
57 /// The current lifecycle status string for the branch.
58 status: String,
59 },
60 /// Indicates snapshot creation or restore failed.
61 #[error("snapshot failed for branch {branch_id}: {reason}")]
62 SnapshotFailed {
63 /// The branch identifier associated with the snapshot failure.
64 branch_id: Uuid,
65 /// The human-readable failure reason.
66 reason: String,
67 },
68 /// Indicates a snapshot file failed integrity validation.
69 #[error("snapshot corrupt for branch {branch_id} at {path:?}")]
70 SnapshotCorrupt {
71 /// The branch identifier associated with the corrupt snapshot.
72 branch_id: Uuid,
73 /// The path to the corrupt snapshot file.
74 path: PathBuf,
75 },
76 /// Indicates a required snapshot hash sidecar file is missing.
77 #[error("snapshot hash missing for branch {branch_id} at {path:?}")]
78 SnapshotHashMissing {
79 /// The branch identifier associated with the missing hash sidecar.
80 branch_id: Uuid,
81 /// The path to the expected sidecar hash file.
82 path: PathBuf,
83 },
84 /// Indicates merge conflicts were not fully resolved.
85 #[error("merge conflicts unresolved for entities: {entity_ids:?}")]
86 MergeConflictUnresolved {
87 /// The entity identifiers that still have unresolved conflicts.
88 entity_ids: Vec<String>,
89 },
90 /// Indicates two branches cannot be merged with the selected base.
91 #[error("merge incompatible base for ours={ours} theirs={theirs}: {reason}")]
92 MergeIncompatibleBase {
93 /// The local branch identifier.
94 ours: Uuid,
95 /// The incoming branch identifier.
96 theirs: Uuid,
97 /// The reason the base is incompatible.
98 reason: String,
99 },
100 /// Indicates a lineage edge would create a DAG cycle.
101 #[error("dag cycle detected from {from} to {to}")]
102 DagCycle {
103 /// The proposed source branch id.
104 from: Uuid,
105 /// The proposed destination branch id.
106 to: Uuid,
107 },
108 /// Indicates a branch operation would introduce a lineage cycle.
109 #[error("cycle detected from {from} to {to}")]
110 CycleDetected {
111 /// The proposed source branch id.
112 from: Uuid,
113 /// The proposed destination branch id.
114 to: Uuid,
115 },
116 /// Indicates a DAG node lookup failed.
117 #[error("dag node not found: {0}")]
118 DagNodeNotFound(Uuid),
119 /// Indicates diff extraction failed.
120 #[error("diff failed between {branch_a} and {branch_b}: {reason}")]
121 DiffFailed {
122 /// The first branch identifier in the failed diff.
123 branch_a: Uuid,
124 /// The second branch identifier in the failed diff.
125 branch_b: Uuid,
126 /// The human-readable diff failure reason.
127 reason: String,
128 },
129 /// Indicates a selective commit failed validation.
130 #[error("commit validation failed for branch {branch_id}: {violations:?}")]
131 CommitValidationFailed {
132 /// The branch identifier whose commit was rejected.
133 branch_id: Uuid,
134 /// The list of validation violations.
135 violations: Vec<String>,
136 },
137 /// Indicates sandbox execution failed.
138 #[error("sandbox error: {0}")]
139 SandboxError(String),
140 /// Indicates metrics handling failed.
141 #[error("metrics error: {0}")]
142 MetricsError(String),
143 /// Indicates a branch name failed validation.
144 #[error("naming error: {0}")]
145 NamingError(String),
146 /// Indicates an orphaned snapshot path was encountered.
147 #[error("orphaned snapshot: {0:?}")]
148 OrphanedSnapshot(PathBuf),
149}
150
151impl BranchError {
152 /// Returns true if this error indicates a missing branch or DAG node.
153 pub fn is_not_found(&self) -> bool {
154 matches!(self, Self::BranchNotFound(_) | Self::DagNodeNotFound(_))
155 }
156
157 /// Returns true if this error represents an unresolved merge conflict.
158 pub fn is_merge_conflict(&self) -> bool {
159 matches!(
160 self,
161 Self::MergeConflictUnresolved { .. } | Self::MergeIncompatibleBase { .. }
162 )
163 }
164
165 /// Returns true if this error belongs to the snapshot subsystem.
166 pub fn is_snapshot_error(&self) -> bool {
167 matches!(
168 self,
169 Self::SnapshotFailed { .. }
170 | Self::SnapshotCorrupt { .. }
171 | Self::SnapshotHashMissing { .. }
172 | Self::OrphanedSnapshot(_)
173 )
174 }
175
176 /// Returns true if this error belongs to DAG management.
177 pub fn is_dag_error(&self) -> bool {
178 matches!(
179 self,
180 Self::DagCycle { .. } | Self::CycleDetected { .. } | Self::DagNodeNotFound(_)
181 )
182 }
183}