Skip to content

Commit 77a67cc

Browse files
committed
libgrovedb: add structured error codes across the FFI bridge
Replace fragile substring-based error classification with tagged prefixes. The Rust side now tags every error string with a prefix (NOTFOUND, CORRUPTION, INVALIDARG, NOTSUPPORTED, IOERROR) based on the grovedb::Error variant. The C++ StringToError parses the tag to reconstruct the correct Error type, falling back to IOError for untagged strings. This eliminates silent misclassification when upstream GroveDB changes error message wording.
1 parent 59103d1 commit 77a67cc

File tree

14 files changed

+162
-99
lines changed

14 files changed

+162
-99
lines changed

rust/grovedb_cxx/src/auxiliary.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub fn grovedb_put_aux(
2525
) -> Result<FfiOperationCost, String> {
2626
let ctx = db.db.put_aux(key, value, None, None);
2727
let cost = operation_cost_to_ffi(&ctx.cost);
28-
ctx.value.map_err(|e| e.to_string())?;
28+
ctx.value.map_err(crate::ffi_error)?;
2929
Ok(cost)
3030
}
3131

@@ -38,7 +38,7 @@ pub fn grovedb_put_aux_with_tx(
3838
) -> Result<FfiOperationCost, String> {
3939
let ctx = db.db.put_aux(key, value, None, Some(&tx.tx));
4040
let cost = operation_cost_to_ffi(&ctx.cost);
41-
ctx.value.map_err(|e| e.to_string())?;
41+
ctx.value.map_err(crate::ffi_error)?;
4242
Ok(cost)
4343
}
4444

@@ -55,7 +55,7 @@ pub fn grovedb_get_aux(
5555
) -> Result<FfiOptionalBytesResult, String> {
5656
let ctx = db.db.get_aux(key, None);
5757
let cost = operation_cost_to_ffi(&ctx.cost);
58-
let maybe = ctx.value.map_err(|e| e.to_string())?;
58+
let maybe = ctx.value.map_err(crate::ffi_error)?;
5959
match maybe {
6060
Some(v) => Ok(FfiOptionalBytesResult {
6161
has_value: true,
@@ -78,7 +78,7 @@ pub fn grovedb_get_aux_with_tx(
7878
) -> Result<FfiOptionalBytesResult, String> {
7979
let ctx = db.db.get_aux(key, Some(&tx.tx));
8080
let cost = operation_cost_to_ffi(&ctx.cost);
81-
let maybe = ctx.value.map_err(|e| e.to_string())?;
81+
let maybe = ctx.value.map_err(crate::ffi_error)?;
8282
match maybe {
8383
Some(v) => Ok(FfiOptionalBytesResult {
8484
has_value: true,
@@ -104,7 +104,7 @@ pub fn grovedb_delete_aux(
104104
) -> Result<FfiOperationCost, String> {
105105
let ctx = db.db.delete_aux(key, None, None);
106106
let cost = operation_cost_to_ffi(&ctx.cost);
107-
ctx.value.map_err(|e| e.to_string())?;
107+
ctx.value.map_err(crate::ffi_error)?;
108108
Ok(cost)
109109
}
110110

@@ -116,7 +116,7 @@ pub fn grovedb_delete_aux_with_tx(
116116
) -> Result<FfiOperationCost, String> {
117117
let ctx = db.db.delete_aux(key, None, Some(&tx.tx));
118118
let cost = operation_cost_to_ffi(&ctx.cost);
119-
ctx.value.map_err(|e| e.to_string())?;
119+
ctx.value.map_err(crate::ffi_error)?;
120120
Ok(cost)
121121
}
122122

@@ -153,7 +153,7 @@ pub fn grovedb_find_subtrees(
153153
let subtree_path: SubtreePath<Vec<u8>> = segments.as_slice().into();
154154
let ctx = db.db.find_subtrees(&subtree_path, None, version);
155155
let cost = operation_cost_to_ffi(&ctx.cost);
156-
let paths = ctx.value.map_err(|e| e.to_string())?;
156+
let paths = ctx.value.map_err(crate::ffi_error)?;
157157
Ok(FfiOptionalBytesResult {
158158
has_value: true,
159159
value: encode_paths(&paths),
@@ -174,7 +174,7 @@ pub fn grovedb_find_subtrees_with_tx(
174174
.db
175175
.find_subtrees(&subtree_path, Some(&tx.tx), version);
176176
let cost = operation_cost_to_ffi(&ctx.cost);
177-
let paths = ctx.value.map_err(|e| e.to_string())?;
177+
let paths = ctx.value.map_err(crate::ffi_error)?;
178178
Ok(FfiOptionalBytesResult {
179179
has_value: true,
180180
value: encode_paths(&paths),

rust/grovedb_cxx/src/batch.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ struct WireBatchOpList {
9898
/// Decode a wire-encoded batch operation list into `QualifiedGroveDbOp` values.
9999
fn decode_batch_ops(encoded: &[u8]) -> Result<Vec<QualifiedGroveDbOp>, String> {
100100
let mut cursor = Cursor::new(encoded);
101-
let list = WireBatchOpList::read_le(&mut cursor).map_err(|e| e.to_string())?;
101+
let list = WireBatchOpList::read_le(&mut cursor).map_err(crate::ffi_error_generic)?;
102102
let version = GroveVersion::latest();
103103

104104
let mut ops = Vec::with_capacity(list.ops.len());
@@ -115,31 +115,31 @@ fn decode_batch_ops(encoded: &[u8]) -> Result<Vec<QualifiedGroveDbOp>, String> {
115115
OP_INSERT_ONLY => {
116116
let elem_bytes = wire_op
117117
.element
118-
.ok_or_else(|| "missing element for InsertOnly".to_string())?;
118+
.ok_or_else(|| "INVALIDARG:missing element for InsertOnly".to_string())?;
119119
let elem = deserialize_element(&elem_bytes.data, version)?;
120120
QualifiedGroveDbOp::insert_only_op(path, key, elem)
121121
}
122122
OP_INSERT_OR_REPLACE => {
123123
let elem_bytes = wire_op
124124
.element
125-
.ok_or_else(|| "missing element for InsertOrReplace".to_string())?;
125+
.ok_or_else(|| "INVALIDARG:missing element for InsertOrReplace".to_string())?;
126126
let elem = deserialize_element(&elem_bytes.data, version)?;
127127
QualifiedGroveDbOp::insert_or_replace_op(path, key, elem)
128128
}
129129
OP_REPLACE => {
130130
let elem_bytes = wire_op
131131
.element
132-
.ok_or_else(|| "missing element for Replace".to_string())?;
132+
.ok_or_else(|| "INVALIDARG:missing element for Replace".to_string())?;
133133
let elem = deserialize_element(&elem_bytes.data, version)?;
134134
QualifiedGroveDbOp::replace_op(path, key, elem)
135135
}
136136
OP_DELETE => QualifiedGroveDbOp::delete_op(path, key),
137137
OP_DELETE_TREE => {
138138
let tt = wire_op.tree_type.unwrap_or(0);
139-
let tree_type = TreeType::try_from(tt).map_err(|e| e.to_string())?;
139+
let tree_type = TreeType::try_from(tt).map_err(crate::ffi_error_generic)?;
140140
QualifiedGroveDbOp::delete_tree_op(path, key, tree_type)
141141
}
142-
other => return Err(format!("unknown batch op discriminant: {other}")),
142+
other => return Err(format!("INVALIDARG:unknown batch op discriminant: {other}")),
143143
};
144144
ops.push(op);
145145
}
@@ -179,7 +179,7 @@ pub fn grovedb_apply_batch(
179179
let opts = convert_options(options);
180180
let ctx = db.db.apply_batch(ops, Some(opts), None, version);
181181
let cost = operation_cost_to_ffi(&ctx.cost);
182-
ctx.value.map_err(|e| e.to_string())?;
182+
ctx.value.map_err(crate::ffi_error)?;
183183
Ok(cost)
184184
}
185185

@@ -197,6 +197,6 @@ pub fn grovedb_apply_batch_with_tx(
197197
.db
198198
.apply_batch(ops, Some(opts), Some(&tx.tx), version);
199199
let cost = operation_cost_to_ffi(&ctx.cost);
200-
ctx.value.map_err(|e| e.to_string())?;
200+
ctx.value.map_err(crate::ffi_error)?;
201201
Ok(cost)
202202
}

rust/grovedb_cxx/src/checkpoint.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@ use crate::BoxedGroveDb;
1414

1515
/// Create a checkpoint of the database at the given filesystem path.
1616
pub fn grovedb_create_checkpoint(db: &BoxedGroveDb, path: &str) -> Result<(), String> {
17-
db.db.create_checkpoint(path).map_err(|e| e.to_string())
17+
db.db.create_checkpoint(path).map_err(crate::ffi_error)
1818
}
1919

2020
/// Open an existing checkpoint as a read-only database handle.
2121
pub fn grovedb_open_checkpoint(path: &str) -> Result<Box<BoxedGroveDb>, String> {
22-
let db = GroveDb::open_checkpoint(path).map_err(|e| e.to_string())?;
22+
let db = GroveDb::open_checkpoint(path).map_err(crate::ffi_error)?;
2323
Ok(Box::new(BoxedGroveDb { db }))
2424
}
2525

2626
/// Delete a checkpoint directory.
2727
///
2828
/// Verifies the path is a valid checkpoint before deletion.
2929
pub fn grovedb_delete_checkpoint(path: &str) -> Result<(), String> {
30-
GroveDb::delete_checkpoint(path).map_err(|e| e.to_string())
30+
GroveDb::delete_checkpoint(path).map_err(crate::ffi_error)
3131
}

rust/grovedb_cxx/src/delete.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub fn grovedb_delete(
2929
.db
3030
.delete(segments.as_slice(), key, None, None, version);
3131
let cost = operation_cost_to_ffi(&ctx.cost);
32-
ctx.value.map_err(|e| e.to_string())?;
32+
ctx.value.map_err(crate::ffi_error)?;
3333
Ok(cost)
3434
}
3535

@@ -46,7 +46,7 @@ pub fn grovedb_delete_with_tx(
4646
.db
4747
.delete(segments.as_slice(), key, None, Some(&tx.tx), version);
4848
let cost = operation_cost_to_ffi(&ctx.cost);
49-
ctx.value.map_err(|e| e.to_string())?;
49+
ctx.value.map_err(crate::ffi_error)?;
5050
Ok(cost)
5151
}
5252

@@ -69,7 +69,7 @@ pub fn grovedb_delete_if_empty_tree(
6969
.db
7070
.delete_if_empty_tree(segments.as_slice(), key, None, version);
7171
let cost = operation_cost_to_ffi(&ctx.cost);
72-
let deleted = ctx.value.map_err(|e| e.to_string())?;
72+
let deleted = ctx.value.map_err(crate::ffi_error)?;
7373
Ok(FfiBoolResult {
7474
value: deleted,
7575
cost,
@@ -92,7 +92,7 @@ pub fn grovedb_delete_if_empty_tree_with_tx(
9292
version,
9393
);
9494
let cost = operation_cost_to_ffi(&ctx.cost);
95-
let deleted = ctx.value.map_err(|e| e.to_string())?;
95+
let deleted = ctx.value.map_err(crate::ffi_error)?;
9696
Ok(FfiBoolResult {
9797
value: deleted,
9898
cost,
@@ -125,7 +125,7 @@ pub fn grovedb_delete_up_tree_while_empty(
125125
version,
126126
);
127127
let cost = operation_cost_to_ffi(&ctx.cost);
128-
let count = ctx.value.map_err(|e| e.to_string())?;
128+
let count = ctx.value.map_err(crate::ffi_error)?;
129129
Ok(FfiU32Result {
130130
value: u32::from(count),
131131
cost,
@@ -154,7 +154,7 @@ pub fn grovedb_delete_up_tree_while_empty_with_tx(
154154
version,
155155
);
156156
let cost = operation_cost_to_ffi(&ctx.cost);
157-
let count = ctx.value.map_err(|e| e.to_string())?;
157+
let count = ctx.value.map_err(crate::ffi_error)?;
158158
Ok(FfiU32Result {
159159
value: u32::from(count),
160160
cost,
@@ -174,7 +174,7 @@ pub fn grovedb_clear_subtree(
174174
let segments = decode_path(path)?;
175175
db.db
176176
.clear_subtree(segments.as_slice(), None, None, version)
177-
.map_err(|e| e.to_string())
177+
.map_err(crate::ffi_error)
178178
}
179179

180180
/// Remove all elements within the subtree, within a transaction.
@@ -187,5 +187,5 @@ pub fn grovedb_clear_subtree_with_tx(
187187
let segments = decode_path(path)?;
188188
db.db
189189
.clear_subtree(segments.as_slice(), None, Some(&tx.tx), version)
190-
.map_err(|e| e.to_string())
190+
.map_err(crate::ffi_error)
191191
}

rust/grovedb_cxx/src/element.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@ pub(crate) fn serialize_element(
1111
element: &grovedb::Element,
1212
version: &GroveVersion,
1313
) -> Result<Vec<u8>, String> {
14-
element.serialize(version).map_err(|e| e.to_string())
14+
element
15+
.serialize(version)
16+
.map_err(|e| crate::ffi_error(grovedb::Error::from(e)))
1517
}
1618

1719
/// Deserialize an Element from its bincode wire format.
1820
pub(crate) fn deserialize_element(
1921
bytes: &[u8],
2022
version: &GroveVersion,
2123
) -> Result<grovedb::Element, String> {
22-
grovedb::Element::deserialize(bytes, version).map_err(|e| e.to_string())
24+
grovedb::Element::deserialize(bytes, version)
25+
.map_err(|e| crate::ffi_error(grovedb::Error::from(e)))
2326
}
2427

2528
// ---------------------------------------------------------------------------

rust/grovedb_cxx/src/get.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub fn grovedb_get(
2828
let segments = decode_path(path)?;
2929
let ctx = db.db.get(segments.as_slice(), key, None, version);
3030
let cost = operation_cost_to_ffi(&ctx.cost);
31-
let element = ctx.value.map_err(|e| e.to_string())?;
31+
let element = ctx.value.map_err(crate::ffi_error)?;
3232
Ok(FfiElementResult {
3333
element: serialize_element(&element, version)?,
3434
cost,
@@ -48,7 +48,7 @@ pub fn grovedb_get_with_tx(
4848
.db
4949
.get(segments.as_slice(), key, Some(&tx.tx), version);
5050
let cost = operation_cost_to_ffi(&ctx.cost);
51-
let element = ctx.value.map_err(|e| e.to_string())?;
51+
let element = ctx.value.map_err(crate::ffi_error)?;
5252
Ok(FfiElementResult {
5353
element: serialize_element(&element, version)?,
5454
cost,
@@ -71,7 +71,7 @@ pub fn grovedb_get_raw(
7171
.db
7272
.get_raw(segments.as_slice().into(), key, None, version);
7373
let cost = operation_cost_to_ffi(&ctx.cost);
74-
let element = ctx.value.map_err(|e| e.to_string())?;
74+
let element = ctx.value.map_err(crate::ffi_error)?;
7575
Ok(FfiElementResult {
7676
element: serialize_element(&element, version)?,
7777
cost,
@@ -91,7 +91,7 @@ pub fn grovedb_get_raw_with_tx(
9191
.db
9292
.get_raw(segments.as_slice().into(), key, Some(&tx.tx), version);
9393
let cost = operation_cost_to_ffi(&ctx.cost);
94-
let element = ctx.value.map_err(|e| e.to_string())?;
94+
let element = ctx.value.map_err(crate::ffi_error)?;
9595
Ok(FfiElementResult {
9696
element: serialize_element(&element, version)?,
9797
cost,
@@ -115,7 +115,7 @@ pub fn grovedb_get_raw_optional(
115115
.db
116116
.get_raw_optional(segments.as_slice().into(), key, None, version);
117117
let cost = operation_cost_to_ffi(&ctx.cost);
118-
let maybe = ctx.value.map_err(|e| e.to_string())?;
118+
let maybe = ctx.value.map_err(crate::ffi_error)?;
119119
match maybe {
120120
Some(element) => Ok(FfiOptionalElementResult {
121121
has_element: true,
@@ -143,7 +143,7 @@ pub fn grovedb_get_raw_optional_with_tx(
143143
.db
144144
.get_raw_optional(segments.as_slice().into(), key, Some(&tx.tx), version);
145145
let cost = operation_cost_to_ffi(&ctx.cost);
146-
let maybe = ctx.value.map_err(|e| e.to_string())?;
146+
let maybe = ctx.value.map_err(crate::ffi_error)?;
147147
match maybe {
148148
Some(element) => Ok(FfiOptionalElementResult {
149149
has_element: true,
@@ -172,7 +172,7 @@ pub fn grovedb_has_raw(
172172
let segments = decode_path(path)?;
173173
let ctx = db.db.has_raw(segments.as_slice(), key, None, version);
174174
let cost = operation_cost_to_ffi(&ctx.cost);
175-
let exists = ctx.value.map_err(|e| e.to_string())?;
175+
let exists = ctx.value.map_err(crate::ffi_error)?;
176176
Ok(FfiBoolResult {
177177
value: exists,
178178
cost,
@@ -192,7 +192,7 @@ pub fn grovedb_has_raw_with_tx(
192192
.db
193193
.has_raw(segments.as_slice(), key, Some(&tx.tx), version);
194194
let cost = operation_cost_to_ffi(&ctx.cost);
195-
let exists = ctx.value.map_err(|e| e.to_string())?;
195+
let exists = ctx.value.map_err(crate::ffi_error)?;
196196
Ok(FfiBoolResult {
197197
value: exists,
198198
cost,
@@ -257,7 +257,7 @@ pub fn grovedb_is_empty_tree(
257257
let subtree_path: SubtreePath<Vec<u8>> = segments.as_slice().into();
258258
let ctx = db.db.is_empty_tree(subtree_path, None, version);
259259
let cost = operation_cost_to_ffi(&ctx.cost);
260-
let empty = ctx.value.map_err(|e| e.to_string())?;
260+
let empty = ctx.value.map_err(crate::ffi_error)?;
261261
Ok(FfiBoolResult {
262262
value: empty,
263263
cost,
@@ -275,7 +275,7 @@ pub fn grovedb_is_empty_tree_with_tx(
275275
let subtree_path: SubtreePath<Vec<u8>> = segments.as_slice().into();
276276
let ctx = db.db.is_empty_tree(subtree_path, Some(&tx.tx), version);
277277
let cost = operation_cost_to_ffi(&ctx.cost);
278-
let empty = ctx.value.map_err(|e| e.to_string())?;
278+
let empty = ctx.value.map_err(crate::ffi_error)?;
279279
Ok(FfiBoolResult {
280280
value: empty,
281281
cost,

0 commit comments

Comments
 (0)