firewood_ffi/value/results.rs
1// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
2// See the file LICENSE.md for licensing terms.
3
4use firewood::api;
5use firewood::merkle;
6use firewood_storage::TrieHash;
7use std::fmt;
8
9use crate::revision::{GetRevisionResult, RevisionHandle};
10use crate::{
11 ChangeProofContext, CodeIteratorHandle, CreateIteratorResult, CreateProposalResult, HashKey,
12 IteratorHandle, KeyRange, NextKeyRange, OwnedBytes, OwnedKeyValueBatch, OwnedKeyValuePair,
13 ProposalHandle, ProposedChangeProofContext, RangeProofContext, ReconstructedHandle,
14 VerifiedChangeProofContext,
15};
16
17/// The result type returned from an FFI function that returns no value but may
18/// return an error.
19#[derive(Debug)]
20#[repr(C, usize)]
21pub enum VoidResult {
22 /// The caller provided a null pointer to the input handle.
23 NullHandlePointer,
24
25 /// The operation was successful and no error occurred.
26 Ok,
27
28 /// An error occurred and the message is returned as an [`OwnedBytes`]. Its
29 /// value is guaranteed to contain only valid UTF-8.
30 ///
31 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
32 /// associated with this error.
33 ///
34 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
35 Err(OwnedBytes),
36}
37
38impl From<()> for VoidResult {
39 fn from((): ()) -> Self {
40 VoidResult::Ok
41 }
42}
43
44impl<E: fmt::Display> From<Result<(), E>> for VoidResult {
45 fn from(value: Result<(), E>) -> Self {
46 match value {
47 Ok(()) => VoidResult::Ok,
48 Err(err) => VoidResult::Err(err.to_string().into_bytes().into()),
49 }
50 }
51}
52
53/// The result type returned from the open or create database functions.
54#[derive(Debug)]
55#[repr(C, usize)]
56pub enum HandleResult {
57 /// The database was opened or created successfully and the handle is
58 /// returned as an opaque pointer.
59 ///
60 /// The caller must ensure that [`fwd_close_db`] is called to free resources
61 /// associated with this handle when it is no longer needed.
62 ///
63 /// [`fwd_close_db`]: crate::fwd_close_db
64 Ok(Box<crate::DatabaseHandle>),
65
66 /// An error occurred and the message is returned as an [`OwnedBytes`]. If
67 /// value is guaranteed to contain only valid UTF-8.
68 ///
69 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
70 /// associated with this error.
71 ///
72 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
73 Err(OwnedBytes),
74}
75
76impl<E: fmt::Display> From<Result<crate::DatabaseHandle, E>> for HandleResult {
77 fn from(value: Result<crate::DatabaseHandle, E>) -> Self {
78 match value {
79 Ok(handle) => HandleResult::Ok(Box::new(handle)),
80 Err(err) => HandleResult::Err(err.to_string().into_bytes().into()),
81 }
82 }
83}
84
85/// A result type returned from FFI functions that retrieve a single value.
86#[derive(Debug)]
87#[repr(C, usize)]
88pub enum ValueResult {
89 /// The caller provided a null pointer to a database handle.
90 NullHandlePointer,
91 /// The provided root was not found in the database.
92 RevisionNotFound(HashKey),
93 /// The provided key was not found in the database or proposal.
94 None,
95 /// A value was found and is returned.
96 ///
97 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
98 /// associated with this value.
99 ///
100 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
101 Some(OwnedBytes),
102 /// An error occurred and the message is returned as an [`OwnedBytes`]. If
103 /// value is guaranteed to contain only valid UTF-8.
104 ///
105 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
106 /// associated with this error.
107 ///
108 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
109 Err(OwnedBytes),
110}
111
112impl<E: fmt::Display> From<Result<String, E>> for ValueResult {
113 fn from(value: Result<String, E>) -> Self {
114 match value {
115 Ok(data) => ValueResult::Some(data.into_bytes().into()),
116 Err(err) => ValueResult::Err(err.to_string().into_bytes().into()),
117 }
118 }
119}
120
121impl From<Result<Option<Box<[u8]>>, api::Error>> for ValueResult {
122 fn from(value: Result<Option<Box<[u8]>>, api::Error>) -> Self {
123 match value {
124 Ok(None) => ValueResult::None,
125 Err(api::Error::RevisionNotFound { provided }) => ValueResult::RevisionNotFound(
126 HashKey::from(provided.unwrap_or_else(api::HashKey::empty)),
127 ),
128 Ok(Some(data)) => ValueResult::Some(data.into()),
129 Err(err) => ValueResult::Err(err.to_string().into_bytes().into()),
130 }
131 }
132}
133
134impl From<Result<Option<Box<[u8]>>, firewood::db::DbError>> for ValueResult {
135 fn from(value: Result<Option<Box<[u8]>>, firewood::db::DbError>) -> Self {
136 value.map_err(api::Error::from).into()
137 }
138}
139
140impl From<Vec<u8>> for ValueResult {
141 fn from(value: Vec<u8>) -> Self {
142 value.into_boxed_slice().into()
143 }
144}
145
146impl From<Box<[u8]>> for ValueResult {
147 fn from(value: Box<[u8]>) -> Self {
148 ValueResult::Some(value.into())
149 }
150}
151
152/// A result type returned from FFI functions return the database root hash. This
153/// may or may not be after a mutation.
154#[derive(Debug)]
155#[repr(C, usize)]
156pub enum HashResult {
157 /// The caller provided a null pointer to a database handle.
158 NullHandlePointer,
159 /// The proposal resulted in an empty database or the database currently has
160 /// no root hash.
161 None,
162 /// The mutation was successful and the root hash is returned, if this result
163 /// was from a mutation. Otherwise, this is the current root hash of the
164 /// database.
165 Some(HashKey),
166 /// An error occurred and the message is returned as an [`OwnedBytes`]. If
167 /// value is guaranteed to contain only valid UTF-8.
168 ///
169 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
170 /// associated with this error.
171 ///
172 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
173 Err(OwnedBytes),
174}
175
176impl<E: fmt::Display> From<Result<Option<api::HashKey>, E>> for HashResult {
177 fn from(value: Result<Option<api::HashKey>, E>) -> Self {
178 match value {
179 Ok(hash) => hash.into(),
180 Err(err) => HashResult::Err(err.to_string().into_bytes().into()),
181 }
182 }
183}
184
185impl From<Option<TrieHash>> for HashResult {
186 fn from(value: Option<TrieHash>) -> Self {
187 match value {
188 Some(hash) => HashResult::Some(hash.into()),
189 None => HashResult::None,
190 }
191 }
192}
193
194impl From<Option<Result<HashKey, api::Error>>> for HashResult {
195 fn from(value: Option<Result<HashKey, api::Error>>) -> Self {
196 match value {
197 Some(value) => match value {
198 Ok(hash) => HashResult::Some(hash),
199 Err(err) => HashResult::Err(err.to_string().into_bytes().into()),
200 },
201 None => HashResult::None,
202 }
203 }
204}
205
206/// A result type returned from FFI functions that create or parse range proofs.
207///
208/// The caller must ensure that [`fwd_free_range_proof`] is called to
209/// free the memory associated with the returned context when it is no longer
210/// needed.
211///
212/// [`fwd_free_range_proof`]: crate::fwd_free_range_proof
213#[derive(Debug)]
214#[repr(C, usize)]
215pub enum RangeProofResult<'db> {
216 /// The caller provided a null pointer to the input handle.
217 NullHandlePointer,
218 /// The provided root was not found in the database.
219 RevisionNotFound(HashKey),
220 /// A range proof was requested on an empty trie.
221 EmptyTrie,
222 /// The proof was successfully created or parsed.
223 ///
224 /// If the value was parsed from a serialized proof, this does not imply that
225 /// the proof is valid, only that it is well-formed. The verify method must
226 /// be called to ensure the proof is cryptographically valid.
227 Ok(Box<RangeProofContext<'db>>),
228 /// An error occurred and the message is returned as an [`OwnedBytes`]. If
229 /// value is guaranteed to contain only valid UTF-8.
230 ///
231 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
232 /// associated with this error.
233 ///
234 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
235 Err(OwnedBytes),
236}
237
238impl From<Result<api::FrozenRangeProof, api::Error>> for RangeProofResult<'_> {
239 fn from(value: Result<api::FrozenRangeProof, api::Error>) -> Self {
240 match value {
241 Ok(proof) => RangeProofResult::Ok(Box::new(proof.into())),
242 Err(api::Error::RevisionNotFound { provided }) => RangeProofResult::RevisionNotFound(
243 HashKey::from(provided.unwrap_or_else(api::HashKey::empty)),
244 ),
245 Err(api::Error::RangeProofOnEmptyTrie) => RangeProofResult::EmptyTrie,
246 Err(err) => RangeProofResult::Err(err.to_string().into_bytes().into()),
247 }
248 }
249}
250
251/// A result type returned from FFI functions that create or parse change proofs.
252///
253/// The caller must ensure that [`fwd_free_change_proof`] is called to
254/// free the memory associated with the returned context when it is no longer
255/// needed.
256///
257/// [`fwd_free_change_proof`]: crate::fwd_free_change_proof
258#[derive(Debug)]
259#[repr(C, usize)]
260pub enum ChangeProofResult {
261 /// The caller provided a null pointer to the input handle.
262 NullHandlePointer,
263 /// The provided start root was not found in the database.
264 StartRevisionNotFound(HashKey),
265 /// The provided end root was not found in the database.
266 EndRevisionNotFound(HashKey),
267 /// The proof was successfully created or parsed.
268 ///
269 /// If the value was parsed from a serialized proof, this does not imply that
270 /// the proof is valid, only that it is well-formed. The verify method must
271 /// be called to ensure the proof is cryptographically valid.
272 Ok(Box<ChangeProofContext>),
273 /// An error occurred and the message is returned as an [`OwnedBytes`]. If
274 /// value is guaranteed to contain only valid UTF-8.
275 ///
276 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
277 /// associated with this error.
278 ///
279 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
280 Err(OwnedBytes),
281}
282
283#[derive(Debug)]
284#[repr(C, usize)]
285pub enum VerifiedChangeProofResult {
286 /// The caller provided a null pointer to the input handle.
287 NullHandlePointer,
288 // The proof was successfully verified.
289 Ok(Box<VerifiedChangeProofContext>),
290 /// An error occurred and the message is returned as an [`OwnedBytes`]. If
291 /// value is guaranteed to contain only valid UTF-8.
292 ///
293 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
294 /// associated with this error.
295 ///
296 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
297 Err(OwnedBytes),
298}
299
300#[derive(Debug)]
301#[repr(C, usize)]
302pub enum ProposedChangeProofResult<'db> {
303 /// The caller provided a null pointer to the input handle.
304 NullHandlePointer,
305 /// A proposal was successfully created for this proof.
306 Ok(Box<ProposedChangeProofContext<'db>>),
307 /// An error occurred and the message is returned as an [`OwnedBytes`]. If
308 /// value is guaranteed to contain only valid UTF-8.
309 ///
310 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
311 /// associated with this error.
312 ///
313 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
314 Err(OwnedBytes),
315}
316
317#[derive(Debug)]
318#[repr(C, usize)]
319pub enum NextKeyRangeResult {
320 /// The caller provided a null pointer to the input handle.
321 NullHandlePointer,
322 /// The proof has not prepared into a proposal nor committed to the database.
323 NotPrepared,
324 /// There are no more keys to fetch.
325 None,
326 /// The next key range to fetch is returned.
327 Some(NextKeyRange),
328 /// An error occurred and the message is returned as an [`OwnedBytes`]. If
329 /// value is guaranteed to contain only valid UTF-8.
330 ///
331 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
332 /// associated with this error.
333 ///
334 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
335 Err(OwnedBytes),
336}
337
338impl From<Result<Option<KeyRange>, api::Error>> for NextKeyRangeResult {
339 fn from(value: Result<Option<KeyRange>, api::Error>) -> Self {
340 match value {
341 Ok(None) => NextKeyRangeResult::None,
342 Ok(Some((start_key, end_key))) => NextKeyRangeResult::Some(NextKeyRange {
343 start_key: start_key.into(),
344 end_key: end_key.map(Into::into).into(),
345 }),
346 Err(api::Error::ProofError(firewood::ProofError::Unverified)) => {
347 NextKeyRangeResult::NotPrepared
348 }
349 Err(err) => NextKeyRangeResult::Err(err.to_string().into_bytes().into()),
350 }
351 }
352}
353
354/// A result type returned from FFI functions that create an code hash iterator
355#[derive(Debug)]
356#[repr(C, usize)]
357pub enum CodeIteratorResult<'p> {
358 /// The caller provided a null pointer to a proof handle.
359 NullHandlePointer,
360 /// Building the iterator was successful and the iterator handle is returned
361 Ok {
362 /// An opaque pointer to the [`CodeIteratorHandle`].
363 /// The value should be freed with [`fwd_code_hash_iter_free`]
364 ///
365 /// [`fwd_code_hash_iter_free`]: crate::fwd_code_hash_iter_free
366 handle: Box<CodeIteratorHandle<'p>>,
367 },
368 /// An error occurred and the message is returned as an [`OwnedBytes`].
369 ///
370 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
371 /// associated with this error.
372 ///
373 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
374 Err(OwnedBytes),
375}
376
377impl<'a> From<Result<CodeIteratorHandle<'a>, api::Error>> for CodeIteratorResult<'a> {
378 fn from(value: Result<CodeIteratorHandle<'a>, api::Error>) -> Self {
379 match value {
380 Ok(res) => CodeIteratorResult::Ok {
381 handle: Box::new(res),
382 },
383 Err(err) => CodeIteratorResult::Err(err.to_string().into_bytes().into()),
384 }
385 }
386}
387
388/// A result type returned from FFI functions that create a proposal but do not
389/// commit it to the database.
390#[derive(Debug)]
391#[repr(C, usize)]
392pub enum ProposalResult<'db> {
393 /// The caller provided a null pointer to a database handle.
394 NullHandlePointer,
395 /// Buulding the proposal was successful and the proposal ID and root hash
396 /// are returned.
397 Ok {
398 /// An opaque pointer to the [`ProposalHandle`] that can be use to create
399 /// an additional proposal or later commit. The caller must ensure that this
400 /// pointer is freed with [`fwd_free_proposal`] if it is not committed.
401 ///
402 /// [`fwd_free_proposal`]: crate::fwd_free_proposal
403 // note: opaque pointers mut be boxed because the FFI does not the structure definition.
404 handle: Box<ProposalHandle<'db>>,
405 /// The root hash of the proposal. Zeroed if the proposal resulted in an
406 /// empty database.
407 root_hash: HashKey,
408 },
409 /// An error occurred and the message is returned as an [`OwnedBytes`]. If
410 /// value is guaranteed to contain only valid UTF-8.
411 ///
412 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
413 /// associated with this error.
414 ///
415 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
416 Err(OwnedBytes),
417}
418
419/// A result type returned from FFI functions that create an iterator
420#[derive(Debug)]
421#[repr(C, usize)]
422pub enum IteratorResult<'db> {
423 /// The caller provided a null pointer to a revision/proposal handle.
424 NullHandlePointer,
425 /// Building the iterator was successful and the iterator handle is returned
426 Ok {
427 /// An opaque pointer to the [`IteratorHandle`].
428 /// The value should be freed with [`fwd_free_iterator`]
429 ///
430 /// [`fwd_free_iterator`]: crate::fwd_free_iterator
431 handle: Box<IteratorHandle<'db>>,
432 },
433 /// An error occurred and the message is returned as an [`OwnedBytes`].
434 ///
435 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
436 /// associated with this error.
437 ///
438 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
439 Err(OwnedBytes),
440}
441
442/// A result type returned from iterator FFI functions
443#[derive(Debug)]
444#[repr(C, usize)]
445pub enum KeyValueResult {
446 /// The caller provided a null pointer to an iterator handle.
447 NullHandlePointer,
448 /// The iterator is exhausted
449 None,
450 /// The next item is returned.
451 ///
452 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
453 /// associated with the key and the value of this pair.
454 ///
455 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
456 Some(OwnedKeyValuePair),
457 /// An error occurred and the message is returned as an [`OwnedBytes`]. The
458 /// value is guaranteed to contain only valid UTF-8.
459 ///
460 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
461 /// associated with this error.
462 ///
463 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
464 Err(OwnedBytes),
465}
466
467impl From<Option<Result<(merkle::Key, merkle::Value), api::Error>>> for KeyValueResult {
468 fn from(value: Option<Result<(merkle::Key, merkle::Value), api::Error>>) -> Self {
469 match value {
470 Some(value) => match value {
471 Ok(value) => KeyValueResult::Some(value.into()),
472 Err(err) => KeyValueResult::Err(err.to_string().into_bytes().into()),
473 },
474 None => KeyValueResult::None,
475 }
476 }
477}
478
479/// A result type returned from iterator FFI functions
480#[derive(Debug)]
481#[repr(C, usize)]
482pub enum KeyValueBatchResult {
483 /// The caller provided a null pointer to an iterator handle.
484 NullHandlePointer,
485 /// The next batch of items on iterator are returned.
486 Some(OwnedKeyValueBatch),
487 /// An error occurred and the message is returned as an [`OwnedBytes`]. If
488 /// value is guaranteed to contain only valid UTF-8.
489 ///
490 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
491 /// associated with this error.
492 ///
493 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
494 Err(OwnedBytes),
495}
496
497impl From<Result<Vec<(merkle::Key, merkle::Value)>, api::Error>> for KeyValueBatchResult {
498 fn from(value: Result<Vec<(merkle::Key, merkle::Value)>, api::Error>) -> Self {
499 match value {
500 Ok(pairs) => {
501 let values: Vec<_> = pairs.into_iter().map(Into::into).collect();
502 KeyValueBatchResult::Some(values.into())
503 }
504 Err(err) => KeyValueBatchResult::Err(err.to_string().into_bytes().into()),
505 }
506 }
507}
508
509impl<'db> From<CreateIteratorResult<'db>> for IteratorResult<'db> {
510 fn from(value: CreateIteratorResult<'db>) -> Self {
511 IteratorResult::Ok {
512 handle: Box::new(value.0),
513 }
514 }
515}
516
517impl<'db, E: fmt::Display> From<Result<CreateIteratorResult<'db>, E>> for IteratorResult<'db> {
518 fn from(value: Result<CreateIteratorResult<'db>, E>) -> Self {
519 match value {
520 Ok(res) => res.into(),
521 Err(err) => IteratorResult::Err(err.to_string().into_bytes().into()),
522 }
523 }
524}
525
526/// A result type returned from FFI functions that get a revision
527#[derive(Debug)]
528#[repr(C, usize)]
529pub enum RevisionResult<'db> {
530 /// The caller provided a null pointer to a database handle.
531 NullHandlePointer,
532 /// The provided root was not found in the database.
533 RevisionNotFound(HashKey),
534 /// Getting the revision was successful and the revision handle and root
535 /// hash are returned.
536 Ok {
537 /// An opaque pointer to the [`RevisionHandle`].
538 /// The value should be freed with [`fwd_free_revision`]
539 ///
540 /// [`fwd_free_revision`]: crate::fwd_free_revision
541 handle: Box<RevisionHandle<'db>>,
542 /// The root hash of the revision.
543 root_hash: HashKey,
544 },
545 /// An error occurred and the message is returned as an [`OwnedBytes`]. The
546 /// value is guaranteed to contain only valid UTF-8.
547 ///
548 /// The caller must call [`fwd_free_owned_bytes`] to free the memory
549 /// associated with this error.
550 ///
551 /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes
552 Err(OwnedBytes),
553}
554
555/// A result type returned from FFI functions that create a reconstructed view.
556#[derive(Debug)]
557#[repr(C, usize)]
558pub enum ReconstructedResult<'db> {
559 /// The caller provided a null pointer to an input handle.
560 NullHandlePointer,
561 /// Building the reconstructed view was successful and the handle is returned.
562 Ok {
563 /// An opaque pointer to the [`ReconstructedHandle`].
564 /// The value should be freed with [`fwd_free_reconstructed`].
565 ///
566 /// [`fwd_free_reconstructed`]: crate::fwd_free_reconstructed
567 handle: Box<ReconstructedHandle<'db>>,
568 },
569 /// An error occurred and the message is returned as an [`OwnedBytes`].
570 Err(OwnedBytes),
571}
572
573impl<'db> From<GetRevisionResult<'db>> for RevisionResult<'db> {
574 fn from(value: GetRevisionResult<'db>) -> Self {
575 RevisionResult::Ok {
576 handle: Box::new(value.handle),
577 root_hash: HashKey::from(value.root_hash),
578 }
579 }
580}
581
582impl<'db> From<Result<GetRevisionResult<'db>, api::Error>> for RevisionResult<'db> {
583 fn from(value: Result<GetRevisionResult<'db>, api::Error>) -> Self {
584 match value {
585 Ok(res) => res.into(),
586 Err(api::Error::RevisionNotFound { provided }) => RevisionResult::RevisionNotFound(
587 HashKey::from(provided.unwrap_or_else(api::HashKey::empty)),
588 ),
589 Err(err) => RevisionResult::Err(err.to_string().into_bytes().into()),
590 }
591 }
592}
593
594impl<'db, E: fmt::Display> From<Result<CreateProposalResult<'db>, E>> for ProposalResult<'db> {
595 fn from(value: Result<CreateProposalResult<'db>, E>) -> Self {
596 match value {
597 Ok(CreateProposalResult { handle, .. }) => ProposalResult::Ok {
598 root_hash: handle.hash_key().unwrap_or_default(),
599 handle: Box::new(handle),
600 },
601 Err(err) => ProposalResult::Err(err.to_string().into_bytes().into()),
602 }
603 }
604}
605
606impl<'db, E: fmt::Display> From<Result<ReconstructedHandle<'db>, E>> for ReconstructedResult<'db> {
607 fn from(value: Result<ReconstructedHandle<'db>, E>) -> Self {
608 match value {
609 Ok(handle) => ReconstructedResult::Ok {
610 handle: Box::new(handle),
611 },
612 Err(err) => ReconstructedResult::Err(err.to_string().into_bytes().into()),
613 }
614 }
615}
616
617impl From<Result<api::FrozenChangeProof, api::Error>> for ChangeProofResult {
618 fn from(value: Result<api::FrozenChangeProof, api::Error>) -> Self {
619 match value {
620 Ok(proof) => ChangeProofResult::Ok(Box::new(proof.into())),
621 Err(api::Error::StartRevisionNotFound { provided }) => {
622 ChangeProofResult::StartRevisionNotFound(HashKey::from(
623 provided.unwrap_or_else(api::HashKey::empty),
624 ))
625 }
626 Err(api::Error::EndRevisionNotFound { provided }) => {
627 ChangeProofResult::EndRevisionNotFound(HashKey::from(
628 provided.unwrap_or_else(api::HashKey::empty),
629 ))
630 }
631 Err(err) => ChangeProofResult::Err(err.to_string().into_bytes().into()),
632 }
633 }
634}
635
636impl From<Result<VerifiedChangeProofContext, api::Error>> for VerifiedChangeProofResult {
637 fn from(value: Result<VerifiedChangeProofContext, api::Error>) -> Self {
638 match value {
639 Ok(context) => VerifiedChangeProofResult::Ok(Box::new(context)),
640 Err(err) => VerifiedChangeProofResult::Err(err.to_string().into_bytes().into()),
641 }
642 }
643}
644
645impl<'db> From<Result<ProposedChangeProofContext<'db>, api::Error>>
646 for ProposedChangeProofResult<'db>
647{
648 fn from(value: Result<ProposedChangeProofContext<'db>, api::Error>) -> Self {
649 match value {
650 Ok(context) => ProposedChangeProofResult::Ok(Box::new(context)),
651 Err(err) => ProposedChangeProofResult::Err(err.to_string().into_bytes().into()),
652 }
653 }
654}
655
656/// Helper trait to handle the different result types returned from FFI functions.
657///
658/// Once Try trait is stable, we can use that instead of this trait:
659///
660/// ```ignore
661/// impl std::ops::FromResidual<Option<std::convert::Infallible>> for VoidResult {
662/// #[inline]
663/// fn from_residual(residual: Option<std::convert::Infallible>) -> Self {
664/// match residual {
665/// None => VoidResult::NullHandlePointer,
666/// // no other branches are needed because `std::convert::Infallible` is uninhabited
667/// // this compiles without error because the compiler knows that Some(_) is impossible
668/// // see: https://github.com/rust-lang/rust/blob/3fb1b53a9dbfcdf37a4b67d35cde373316829930/library/core/src/option.rs#L2627-L2631
669/// // and: https://doc.rust-lang.org/nomicon/exotic-sizes.html#empty-types
670/// }
671/// }
672/// }
673/// ```
674pub(crate) trait NullHandleResult: CResult {
675 fn null_handle_pointer_error() -> Self;
676}
677
678pub(crate) trait CResult: Sized {
679 #[cfg(panic = "unwind")]
680 fn from_err(err: impl ToString) -> Self;
681
682 #[cfg(panic = "unwind")]
683 fn from_panic(panic: Box<dyn std::any::Any + Send>) -> Self
684 where
685 Self: Sized,
686 {
687 Self::from_err(Panic::from(panic))
688 }
689}
690
691macro_rules! impl_null_handle_result {
692 ($($Enum:ty),* $(,)?) => {
693 $(
694 impl NullHandleResult for $Enum {
695 fn null_handle_pointer_error() -> Self {
696 Self::NullHandlePointer
697 }
698 }
699 )*
700 };
701}
702
703macro_rules! impl_cresult {
704 ($($Enum:ty),* $(,)?) => {
705 $(
706 impl CResult for $Enum {
707 #[cfg(panic = "unwind")]
708 fn from_err(err: impl ToString) -> Self {
709 Self::Err(err.to_string().into_bytes().into())
710 }
711 }
712 )*
713 };
714}
715
716impl_null_handle_result!(
717 VoidResult,
718 ValueResult,
719 HashResult,
720 RangeProofResult<'_>,
721 ChangeProofResult,
722 VerifiedChangeProofResult,
723 ProposedChangeProofResult<'_>,
724 NextKeyRangeResult,
725 CodeIteratorResult<'_>,
726 ProposalResult<'_>,
727 ReconstructedResult<'_>,
728 IteratorResult<'_>,
729 RevisionResult<'_>,
730 KeyValueBatchResult,
731 KeyValueResult,
732);
733
734impl_cresult!(
735 VoidResult,
736 ValueResult,
737 HashResult,
738 HandleResult,
739 RangeProofResult<'_>,
740 ChangeProofResult,
741 VerifiedChangeProofResult,
742 ProposedChangeProofResult<'_>,
743 NextKeyRangeResult,
744 CodeIteratorResult<'_>,
745 ProposalResult<'_>,
746 ReconstructedResult<'_>,
747 IteratorResult<'_>,
748 RevisionResult<'_>,
749 KeyValueBatchResult,
750 KeyValueResult,
751);
752
753#[cfg(panic = "unwind")]
754enum Panic {
755 Static(&'static str),
756 Formatted(String),
757 SendSyncErr(Box<dyn std::error::Error + Send + Sync>),
758 SendErr(Box<dyn std::error::Error + Send>),
759 Unknown(#[expect(unused)] Box<dyn std::any::Any + Send>),
760 // TODO: add variant to capture backtrace with panic hook
761 // https://doc.rust-lang.org/stable/std/panic/fn.set_hook.html
762}
763
764#[cfg(panic = "unwind")]
765impl From<Box<dyn std::any::Any + Send>> for Panic {
766 fn from(panic: Box<dyn std::any::Any + Send>) -> Self {
767 macro_rules! downcast {
768 ($Variant:ident($panic:ident)) => {
769 let $panic = match $panic.downcast() {
770 Ok(panic) => return Panic::$Variant(*panic),
771 Err(panic) => panic,
772 };
773 };
774 }
775
776 downcast!(Static(panic));
777 downcast!(Formatted(panic));
778 downcast!(SendSyncErr(panic));
779 downcast!(SendErr(panic));
780
781 Self::Unknown(panic)
782 }
783}
784
785#[cfg(panic = "unwind")]
786impl fmt::Display for Panic {
787 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788 match self {
789 Panic::Static(msg) => f.pad(msg),
790 Panic::Formatted(msg) => f.pad(msg),
791 Panic::SendSyncErr(err) => err.fmt(f),
792 Panic::SendErr(err) => err.fmt(f),
793 Panic::Unknown(_) => f.pad("unknown panic type recovered"),
794 }
795 }
796}