1use mkit_core::Hash;
9use mkit_core::hash::to_hex;
10use std::fmt;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum Refusal {
17 Remix { object: Hash },
19 FixedSizeChunking { object: Hash, chunk_size: u32 },
22 NonCanonicalChunking { object: Hash, detail: &'static str },
27 TimestampOverflow { object: Hash, timestamp: u64 },
29 TagName { object: Hash },
32 RefName { name: String, reason: &'static str },
34 SchemaVersion { object: Hash },
37 Gitlink { object: Hash, path: String },
40 TreeEntryName { object: Hash, name: String },
43 UnknownTreeMode { object: Hash, mode: String },
45 NormalizedModeInFork { object: Hash, mode: String },
48 NegativeTimestamp { object: Hash, timestamp: i64 },
50 Unparsable { object: Hash, detail: String },
53 BlobTooLarge { object: Hash, size: u64 },
55 TooManyTreeEntries { object: Hash, count: usize },
59 TreeEntryKind { object: Hash, name: String },
64 Unrepresentable { object: Hash, detail: String },
68 TooManyParents { object: Hash },
70 AuthorPayload { object: Hash },
72 TagChain { object: Hash },
74 DuplicateTreeEntry { object: Hash },
77 TreeTooDeep { object: Hash },
79}
80
81impl fmt::Display for Refusal {
82 #[allow(clippy::too_many_lines)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 match self {
85 Self::Remix { object } => write!(
86 f,
87 "remix object {} is not translatable in bridge v1 (SPEC-GIT-BRIDGE §8)",
88 to_hex(object)
89 ),
90 Self::FixedSizeChunking { object, chunk_size } => write!(
91 f,
92 "chunked blob {} uses fixed-size chunking ({chunk_size}); only \
93 content-defined manifests translate (SPEC-GIT-BRIDGE §4)",
94 to_hex(object)
95 ),
96 Self::NonCanonicalChunking { object, detail } => write!(
97 f,
98 "chunked blob {} cannot have been produced by a conformant \
99 mkit writer ({detail}); refusing a non-round-trippable \
100 translation (SPEC-GIT-BRIDGE §4)",
101 to_hex(object)
102 ),
103 Self::TimestampOverflow { object, timestamp } => write!(
104 f,
105 "object {} timestamp {timestamp} exceeds the git-representable range",
106 to_hex(object)
107 ),
108 Self::TagName { object } => write!(
109 f,
110 "tag object {} has a name outside the mkit ref grammar; \
111 it cannot ride in a git tag header",
112 to_hex(object)
113 ),
114 Self::RefName { name, reason } => {
115 write!(f, "ref {name:?} is not a legal git ref name ({reason})")
116 }
117 Self::SchemaVersion { object } => write!(
118 f,
119 "object {} has a schema_version other than 1; bridge v1 maps schema 1 only",
120 to_hex(object)
121 ),
122 Self::Gitlink { object, path } => write!(
123 f,
124 "git tree {} contains a submodule gitlink at {path:?}; submodules are out \
125 of scope (vendor the submodule, or exclude this ref) — SPEC-GIT-IMPORT §3.3",
126 &to_hex(object)[..40]
127 ),
128 Self::TreeEntryName { object, name } => write!(
129 f,
130 "git tree {} entry {name:?} is not a storable mkit name (SPEC-OBJECTS §4.1); \
131 rename it upstream or exclude this ref",
132 &to_hex(object)[..40]
133 ),
134 Self::UnknownTreeMode { object, mode } => write!(
135 f,
136 "git tree {} carries mode {mode} outside the import mapping (SPEC-GIT-IMPORT §3.3)",
137 &to_hex(object)[..40]
138 ),
139 Self::NormalizedModeInFork { object, mode } => write!(
140 f,
141 "git tree {} carries historic mode {mode}, which would normalize lossily; \
142 this state dir is fork-mode, where normalized trees cannot reproduce their \
143 original sha1 — refusing (SPEC-GIT-IMPORT §3.3)",
144 &to_hex(object)[..40]
145 ),
146 Self::Unparsable { object, detail } => write!(
147 f,
148 "git object {} is structurally unparsable ({detail}); refused per SPEC-GIT-IMPORT §3.2",
149 &to_hex(object)[..40]
150 ),
151 Self::TooManyTreeEntries { object, count } => write!(
152 f,
153 "git tree {} has {count} entries, over mkit's decode cap (SPEC-GIT-IMPORT §3.3)",
154 &to_hex(object)[..40]
155 ),
156 Self::TreeEntryKind { object, name } => write!(
157 f,
158 "git tree {} entry {name:?} names an object of a kind its mode contradicts (SPEC-GIT-IMPORT §3.3)",
159 &to_hex(object)[..40]
160 ),
161 Self::Unrepresentable { object, detail } => write!(
162 f,
163 "git object {} does not serialize under SPEC-OBJECTS ({detail}); refused per SPEC-GIT-IMPORT §3",
164 &to_hex(object)[..40]
165 ),
166 Self::BlobTooLarge { object, size } => write!(
167 f,
168 "git blob {} is {size} bytes, over the 1 GiB per-file cap (SPEC-GIT-IMPORT §3.1)",
169 &to_hex(object)[..40]
170 ),
171 Self::NegativeTimestamp { object, timestamp } => write!(
172 f,
173 "git object {} has pre-1970 timestamp {timestamp}; mkit timestamps are unsigned",
174 &to_hex(object)[..40]
175 ),
176 Self::TooManyParents { object } => write!(
177 f,
178 "git commit {} has more than 1000 parents (MAX_PARENTS)",
179 &to_hex(object)[..40]
180 ),
181 Self::AuthorPayload { object } => write!(
182 f,
183 "git object {} has an author/tagger identity that is empty or over 4096 bytes",
184 &to_hex(object)[..40]
185 ),
186 Self::TagChain { object } => write!(
187 f,
188 "git tag {} heads a tag chain deeper than 16; refusing (SPEC-GIT-IMPORT §3.4)",
189 &to_hex(object)[..40]
190 ),
191 Self::DuplicateTreeEntry { object } => write!(
192 f,
193 "git tree {} contains entries whose names collide byte-equal in mkit \
194 order (e.g. a file and a directory of one name); refusing",
195 &to_hex(object)[..40]
196 ),
197 Self::TreeTooDeep { object } => write!(
198 f,
199 "git tree {} nests deeper than 128 levels; refusing (matches mkit's \
200 MAX_TREE_DEPTH defense)",
201 &to_hex(object)[..40]
202 ),
203 }
204 }
205}
206
207#[derive(Debug)]
209pub enum BridgeError {
210 Refused(Refusal),
212 Source(String),
214 NotBridgeObject(String),
218 Integrity(String),
221 Io(std::io::Error),
223}
224
225impl fmt::Display for BridgeError {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 match self {
228 Self::Refused(r) => write!(f, "refused: {r}"),
229 Self::Source(m) => write!(f, "source object: {m}"),
230 Self::NotBridgeObject(m) => write!(f, "not a bridge-emitted git object: {m}"),
231 Self::Integrity(m) => write!(f, "integrity: {m}"),
232 Self::Io(e) => write!(f, "io: {e}"),
233 }
234 }
235}
236
237impl std::error::Error for BridgeError {}
238
239impl From<std::io::Error> for BridgeError {
240 fn from(e: std::io::Error) -> Self {
241 Self::Io(e)
242 }
243}
244
245impl From<Refusal> for BridgeError {
246 fn from(r: Refusal) -> Self {
247 Self::Refused(r)
248 }
249}