1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
//! Error and result types shared across the crate.
use std::fmt;
/// Convenient result alias used throughout the crate.
pub type Result<Value> = std::result::Result<Value, Error>;
/// The error type for every fallible operation in this crate.
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
/// An underlying input/output operation failed.
InputOutput(std::io::Error),
/// A file or byte sequence did not match the segment-tar storage format.
InvalidFormat {
/// Human-readable description of what was expected and what was found.
details: String,
},
/// A record referenced a segment that no archive of the repository
/// contains. This mirrors Oak's `SegmentNotFoundException` and can be
/// caused by garbage collection removing a segment while old record
/// identifiers to it survive.
SegmentNotFound {
/// The identifier of the missing segment.
segment_identifier: crate::segment::identifier::SegmentIdentifier,
},
/// The content of a binary stored in an external blob store was
/// requested; the segment store only holds the binary's identifier.
ExternalBinaryContentUnavailable {
/// The identifier of the binary in the external blob store.
blob_identifier: String,
},
/// The content of an external binary was requested, but its identifier
/// is itself stored in another record. Bounded callers deliberately do
/// not follow that record merely to construct an error message.
ExternalBinaryContentUnavailableByRecord {
/// The external binary value record that was requested.
value_identifier: crate::segment::record::RecordIdentifier,
/// The string record holding the external blob identifier.
blob_identifier_record: crate::segment::record::RecordIdentifier,
},
/// Materializing another stored string would exceed a caller-provided
/// cumulative byte limit.
StringMaterializationBudgetExceeded {
/// Maximum stored string bytes the caller permits.
maximum_stored_bytes: u64,
/// Cumulative stored bytes including the rejected string.
attempted_stored_bytes: u64,
/// String value that was rejected before its content was read.
value_identifier: crate::segment::record::RecordIdentifier,
},
/// Parsing a template would materialize more property slots than a
/// caller-provided limit.
TemplatePropertyBudgetExceeded {
/// Maximum property slots the caller permits.
maximum_properties: u64,
/// Property slots declared by the template.
attempted_properties: u64,
},
/// Bounded map enumeration encountered more concrete entries than the
/// caller permitted.
MapEntryBudgetExceeded {
/// Maximum concrete map entries permitted.
maximum_entries: u64,
/// Entry count including the rejected entry.
attempted_entries: u64,
},
/// Bounded map enumeration would exceed its combined map-record and
/// stored-name-byte work limit.
MapTraversalWorkBudgetExceeded {
/// Maximum combined map enumeration work.
maximum_work_units: u64,
/// Work including the rejected map record or stored name bytes.
attempted_work_units: u64,
},
/// A content traversal refused to materialize one node's child list
/// because its declared size exceeds the caller-provided scheduling
/// budget.
TraversalSchedulingBudgetExceeded {
/// Maximum children the caller allowed this traversal step to
/// schedule.
maximum_scheduled_children: u64,
/// Children the node declared before any child-list allocation.
attempted_scheduled_children: u64,
},
/// A traversal refused to materialize a node's child names because their
/// cumulative stored bytes exceed the caller-provided scheduling limit.
TraversalChildNameBudgetExceeded {
/// Maximum cumulative stored child-name bytes permitted.
maximum_stored_child_name_bytes: u64,
/// Cumulative stored bytes including the rejected name.
attempted_stored_child_name_bytes: u64,
/// Children whose scheduling work accompanies those name bytes.
scheduled_children: u64,
},
/// A traversal's per-node scheduling expansion would exceed the caller's
/// combined work allowance.
TraversalSchedulingWorkBudgetExceeded {
/// Maximum combined scheduling work permitted.
maximum_scheduling_work: u64,
/// Combined work including the rejected operation.
attempted_scheduling_work: u64,
},
/// Scheduling another node would exceed the caller's total pending-node
/// limit.
TraversalPendingBudgetExceeded {
/// Maximum pending node visits permitted.
maximum_pending_nodes: u64,
/// Pending visits after the rejected expansion.
attempted_pending_nodes: u64,
},
/// A unique index's key named more than one node, which Oak refuses to
/// commit. Typed rather than a message, because a reindex that hit this
/// found a store whose content no longer satisfies a constraint the
/// definition declares, and a caller may want to name the paths.
DuplicateUniqueKey {
/// The key, as it is stored — already URL-encoded.
key: String,
/// Every content path found under it, in the order they were seen.
paths: Vec<String>,
},
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InputOutput(source) => write!(formatter, "input/output error: {source}"),
Error::InvalidFormat { details } => {
write!(formatter, "invalid segment-tar data: {details}")
}
Error::SegmentNotFound { segment_identifier } => {
write!(
formatter,
"segment {segment_identifier} not found in any archive"
)
}
Error::ExternalBinaryContentUnavailable { blob_identifier } => {
write!(
formatter,
"binary {blob_identifier} is stored in an external blob store; \
only its identifier is available"
)
}
Error::ExternalBinaryContentUnavailableByRecord {
value_identifier,
blob_identifier_record,
} => write!(
formatter,
"binary value {value_identifier} is stored in an external blob store; its \
identifier is held in record {blob_identifier_record} and was not read"
),
budget => write_budget_refusal(formatter, budget),
}
}
}
/// The budget refusals, which are half the variants and none of the
/// interesting ones.
///
/// Split out so `Display::fmt` stays under the line gate: a match arm per
/// budget is the clearest way to write them and there are eight.
fn write_budget_refusal(formatter: &mut fmt::Formatter<'_>, error: &Error) -> fmt::Result {
match error {
Error::StringMaterializationBudgetExceeded {
maximum_stored_bytes,
attempted_stored_bytes,
value_identifier,
} => write!(
formatter,
"materializing string {value_identifier} would retain {attempted_stored_bytes} \
stored bytes, exceeding the limit of {maximum_stored_bytes}"
),
Error::TemplatePropertyBudgetExceeded {
maximum_properties,
attempted_properties,
} => write!(
formatter,
"template declares {attempted_properties} properties, exceeding the parsing \
limit of {maximum_properties}"
),
Error::MapEntryBudgetExceeded {
maximum_entries,
attempted_entries,
} => write!(
formatter,
"map enumeration would return {attempted_entries} entries, exceeding the limit \
of {maximum_entries}"
),
Error::MapTraversalWorkBudgetExceeded {
maximum_work_units,
attempted_work_units,
} => write!(
formatter,
"map enumeration would consume {attempted_work_units} work units, exceeding the \
limit of {maximum_work_units}"
),
Error::TraversalSchedulingBudgetExceeded {
maximum_scheduled_children,
attempted_scheduled_children,
} => write!(
formatter,
"content traversal would schedule {attempted_scheduled_children} children in one \
step, exceeding its budget of {maximum_scheduled_children}"
),
Error::TraversalChildNameBudgetExceeded {
maximum_stored_child_name_bytes,
attempted_stored_child_name_bytes,
..
} => write!(
formatter,
"content traversal would materialize {attempted_stored_child_name_bytes} stored \
child-name bytes in one step, exceeding its budget of \
{maximum_stored_child_name_bytes}"
),
Error::TraversalSchedulingWorkBudgetExceeded {
maximum_scheduling_work,
attempted_scheduling_work,
} => write!(
formatter,
"content traversal expansion would consume {attempted_scheduling_work} work \
units, exceeding its budget of {maximum_scheduling_work}"
),
Error::DuplicateUniqueKey { key, paths } => write!(
formatter,
"the unique index key {key:?} names {} nodes ({}), which Oak refuses \
to commit",
paths.len(),
paths.join(", ")
),
Error::TraversalPendingBudgetExceeded {
maximum_pending_nodes,
attempted_pending_nodes,
} => write!(
formatter,
"content traversal would retain {attempted_pending_nodes} pending node visits, \
exceeding its budget of {maximum_pending_nodes}"
),
_ => unreachable!("every non-budget variant is handled by the caller"),
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::InputOutput(source) => Some(source),
Error::InvalidFormat { .. }
| Error::SegmentNotFound { .. }
| Error::ExternalBinaryContentUnavailable { .. }
| Error::ExternalBinaryContentUnavailableByRecord { .. }
| Error::StringMaterializationBudgetExceeded { .. }
| Error::TemplatePropertyBudgetExceeded { .. }
| Error::MapEntryBudgetExceeded { .. }
| Error::MapTraversalWorkBudgetExceeded { .. }
| Error::TraversalSchedulingBudgetExceeded { .. }
| Error::TraversalChildNameBudgetExceeded { .. }
| Error::TraversalSchedulingWorkBudgetExceeded { .. }
| Error::TraversalPendingBudgetExceeded { .. }
| Error::DuplicateUniqueKey { .. } => None,
}
}
}
impl From<std::io::Error> for Error {
fn from(source: std::io::Error) -> Self {
Error::InputOutput(source)
}
}