taskchampion_lib/replica.rs
1use crate::traits::*;
2use crate::types::*;
3use crate::util::err_to_ruststring;
4use std::ptr::NonNull;
5use taskchampion::storage::ReplicaOp;
6use taskchampion::{Replica, StorageConfig};
7
8#[ffizz_header::item]
9#[ffizz(order = 900)]
10/// ***** TCReplica *****
11///
12/// A replica represents an instance of a user's task data, providing an easy interface
13/// for querying and modifying that data.
14///
15/// # Error Handling
16///
17/// When a `tc_replica_..` function that returns a TCResult returns TC_RESULT_ERROR, then
18/// `tc_replica_error` will return the error message.
19///
20/// # Safety
21///
22/// The `*TCReplica` returned from `tc_replica_new…` functions is owned by the caller and
23/// must later be freed to avoid a memory leak.
24///
25/// Any function taking a `*TCReplica` requires:
26/// - the pointer must not be NUL;
27/// - the pointer must be one previously returned from a tc_… function;
28/// - the memory referenced by the pointer must never be modified by C code; and
29/// - except for `tc_replica_free`, ownership of a `*TCReplica` remains with the caller.
30///
31/// Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again.
32///
33/// TCReplicas are not threadsafe.
34///
35/// ```c
36/// typedef struct TCReplica TCReplica;
37/// ```
38pub struct TCReplica {
39 /// The wrapped Replica
40 inner: Replica,
41
42 /// If true, this replica has an outstanding &mut (for a TaskMut)
43 mut_borrowed: bool,
44
45 /// The error from the most recent operation, if any
46 error: Option<RustString<'static>>,
47}
48
49impl PassByPointer for TCReplica {}
50
51impl TCReplica {
52 /// Mutably borrow the inner Replica
53 pub(crate) fn borrow_mut(&mut self) -> &mut Replica {
54 if self.mut_borrowed {
55 panic!("replica is already borrowed");
56 }
57 self.mut_borrowed = true;
58 &mut self.inner
59 }
60
61 /// Release the borrow made by [`borrow_mut`]
62 pub(crate) fn release_borrow(&mut self) {
63 if !self.mut_borrowed {
64 panic!("replica is not borrowed");
65 }
66 self.mut_borrowed = false;
67 }
68}
69
70impl From<Replica> for TCReplica {
71 fn from(rep: Replica) -> TCReplica {
72 TCReplica {
73 inner: rep,
74 mut_borrowed: false,
75 error: None,
76 }
77 }
78}
79
80/// Utility function to allow using `?` notation to return an error value. This makes
81/// a mutable borrow, because most Replica methods require a `&mut`.
82fn wrap<T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T
83where
84 F: FnOnce(&mut Replica) -> anyhow::Result<T>,
85{
86 debug_assert!(!rep.is_null());
87 // SAFETY:
88 // - rep is not NULL (promised by caller)
89 // - *rep is a valid TCReplica (promised by caller)
90 // - rep is valid for the duration of this function
91 // - rep is not modified by anything else (not threadsafe)
92 let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) };
93 if rep.mut_borrowed {
94 panic!("replica is borrowed and cannot be used");
95 }
96 rep.error = None;
97 match f(&mut rep.inner) {
98 Ok(v) => v,
99 Err(e) => {
100 rep.error = Some(err_to_ruststring(e));
101 err_value
102 }
103 }
104}
105
106/// Utility function to allow using `?` notation to return an error value in the constructor.
107fn wrap_constructor<T, F>(f: F, error_out: *mut TCString, err_value: T) -> T
108where
109 F: FnOnce() -> anyhow::Result<T>,
110{
111 if !error_out.is_null() {
112 // SAFETY:
113 // - error_out is not NULL (just checked)
114 // - properly aligned and valid (promised by caller)
115 unsafe { *error_out = TCString::default() };
116 }
117
118 match f() {
119 Ok(v) => v,
120 Err(e) => {
121 if !error_out.is_null() {
122 // SAFETY:
123 // - error_out is not NULL (just checked)
124 // - properly aligned and valid (promised by caller)
125 unsafe {
126 TCString::val_to_arg_out(err_to_ruststring(e), error_out);
127 }
128 }
129 err_value
130 }
131 }
132}
133
134#[ffizz_header::item]
135#[ffizz(order = 900)]
136/// ***** TCReplicaOpType *****
137///
138/// ```c
139/// enum TCReplicaOpType
140/// #ifdef __cplusplus
141/// : uint32_t
142/// #endif // __cplusplus
143/// {
144/// Create = 0,
145/// Delete = 1,
146/// Update = 2,
147/// UndoPoint = 3,
148/// };
149/// #ifndef __cplusplus
150/// typedef uint32_t TCReplicaOpType;
151/// #endif // __cplusplus
152/// ```
153#[derive(Debug, Default)]
154#[repr(u32)]
155pub enum TCReplicaOpType {
156 Create = 0,
157 Delete = 1,
158 Update = 2,
159 UndoPoint = 3,
160 #[default]
161 Error = 4,
162}
163
164#[ffizz_header::item]
165#[ffizz(order = 901)]
166/// Create a new TCReplica with an in-memory database. The contents of the database will be
167/// lost when it is freed with tc_replica_free.
168///
169/// ```c
170/// EXTERN_C struct TCReplica *tc_replica_new_in_memory(void);
171/// ```
172#[no_mangle]
173pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica {
174 let storage = StorageConfig::InMemory
175 .into_storage()
176 .expect("in-memory always succeeds");
177 // SAFETY:
178 // - caller promises to free this value
179 unsafe { TCReplica::from(Replica::new(storage)).return_ptr() }
180}
181
182#[ffizz_header::item]
183#[ffizz(order = 901)]
184/// Create a new TCReplica with an on-disk database having the given filename. On error, a string
185/// is written to the error_out parameter (if it is not NULL) and NULL is returned. The caller
186/// must free this string.
187///
188/// ```c
189/// EXTERN_C struct TCReplica *tc_replica_new_on_disk(struct TCString path,
190/// bool create_if_missing,
191/// struct TCString *error_out);
192/// ```
193#[no_mangle]
194pub unsafe extern "C" fn tc_replica_new_on_disk(
195 path: TCString,
196 create_if_missing: bool,
197 error_out: *mut TCString,
198) -> *mut TCReplica {
199 wrap_constructor(
200 || {
201 // SAFETY:
202 // - path is valid (promised by caller)
203 // - caller will not use path after this call (convention)
204 let mut path = unsafe { TCString::val_from_arg(path) };
205 let storage = StorageConfig::OnDisk {
206 taskdb_dir: path.to_path_buf_mut()?,
207 create_if_missing,
208 }
209 .into_storage()?;
210
211 // SAFETY:
212 // - caller promises to free this value
213 Ok(unsafe { TCReplica::from(Replica::new(storage)).return_ptr() })
214 },
215 error_out,
216 std::ptr::null_mut(),
217 )
218}
219
220#[ffizz_header::item]
221#[ffizz(order = 901)]
222/// ***** TCReplicaOp *****
223///
224/// ```c
225/// struct TCReplicaOp {
226/// TCReplicaOpType operation_type;
227/// void* inner;
228/// };
229///
230/// typedef struct TCReplicaOp TCReplicaOp;
231/// ```
232#[derive(Debug)]
233#[repr(C)]
234pub struct TCReplicaOp {
235 operation_type: TCReplicaOpType,
236 inner: Box<ReplicaOp>,
237}
238
239impl From<ReplicaOp> for TCReplicaOp {
240 fn from(replica_op: ReplicaOp) -> TCReplicaOp {
241 match replica_op {
242 ReplicaOp::Create { .. } => TCReplicaOp {
243 operation_type: TCReplicaOpType::Create,
244 inner: Box::new(replica_op),
245 },
246 ReplicaOp::Delete { .. } => TCReplicaOp {
247 operation_type: TCReplicaOpType::Delete,
248 inner: Box::new(replica_op),
249 },
250 ReplicaOp::Update { .. } => TCReplicaOp {
251 operation_type: TCReplicaOpType::Update,
252 inner: Box::new(replica_op),
253 },
254 ReplicaOp::UndoPoint => TCReplicaOp {
255 operation_type: TCReplicaOpType::UndoPoint,
256 inner: Box::new(replica_op),
257 },
258 }
259 }
260}
261
262#[ffizz_header::item]
263#[ffizz(order = 901)]
264/// ***** TCReplicaOpList *****
265///
266/// ```c
267/// struct TCReplicaOpList {
268/// struct TCReplicaOp *items;
269/// size_t len;
270/// size_t capacity;
271/// };
272///
273/// typedef struct TCReplicaOpList TCReplicaOpList;
274/// ```
275#[repr(C)]
276#[derive(Debug)]
277pub struct TCReplicaOpList {
278 items: *mut TCReplicaOp,
279 len: usize,
280 capacity: usize,
281}
282
283impl Default for TCReplicaOpList {
284 fn default() -> Self {
285 // SAFETY:
286 // - caller will free this value
287 unsafe { TCReplicaOpList::return_val(Vec::new()) }
288 }
289}
290
291impl CList for TCReplicaOpList {
292 type Element = TCReplicaOp;
293
294 unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self {
295 TCReplicaOpList {
296 len,
297 capacity: cap,
298 items,
299 }
300 }
301
302 fn slice(&mut self) -> &mut [Self::Element] {
303 // SAFETY:
304 // - because we have &mut self, we have read/write access to items[0..len]
305 // - all items are properly initialized Element's
306 // - return value lifetime is equal to &mmut self's, so access is exclusive
307 // - items and len came from Vec, so total size is < isize::MAX
308 unsafe { std::slice::from_raw_parts_mut(self.items, self.len) }
309 }
310
311 fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) {
312 (self.items, self.len, self.capacity)
313 }
314}
315
316#[ffizz_header::item]
317#[ffizz(order = 902)]
318/// Get a list of all tasks in the replica.
319///
320/// Returns a TCTaskList with a NULL items field on error.
321///
322/// ```c
323/// EXTERN_C struct TCTaskList tc_replica_all_tasks(struct TCReplica *rep);
324/// ```
325#[no_mangle]
326pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList {
327 wrap(
328 rep,
329 |rep| {
330 // note that the Replica API returns a hashmap here, but we discard
331 // the keys and return a simple list. The task UUIDs are available
332 // from task.get_uuid(), so information is not lost.
333 let tasks: Vec<_> = rep
334 .all_tasks()?
335 .drain()
336 .map(|(_uuid, t)| {
337 Some(
338 NonNull::new(
339 // SAFETY:
340 // - caller promises to free this value (via freeing the list)
341 unsafe { TCTask::from(t).return_ptr() },
342 )
343 .expect("TCTask::return_ptr returned NULL"),
344 )
345 })
346 .collect();
347 // SAFETY:
348 // - value is not allocated and need not be freed
349 Ok(unsafe { TCTaskList::return_val(tasks) })
350 },
351 TCTaskList::null_value(),
352 )
353}
354
355#[ffizz_header::item]
356#[ffizz(order = 902)]
357/// Get a list of all uuids for tasks in the replica.
358///
359/// Returns a TCUuidList with a NULL items field on error.
360///
361/// The caller must free the UUID list with `tc_uuid_list_free`.
362///
363/// ```c
364/// EXTERN_C struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep);
365/// ```
366#[no_mangle]
367pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUuidList {
368 wrap(
369 rep,
370 |rep| {
371 let uuids: Vec<_> = rep
372 .all_task_uuids()?
373 .drain(..)
374 // SAFETY:
375 // - value is not allocated and need not be freed
376 .map(|uuid| unsafe { TCUuid::return_val(uuid) })
377 .collect();
378 // SAFETY:
379 // - value will be freed (promised by caller)
380 Ok(unsafe { TCUuidList::return_val(uuids) })
381 },
382 TCUuidList::null_value(),
383 )
384}
385
386#[ffizz_header::item]
387#[ffizz(order = 902)]
388/// Get the current working set for this replica. The resulting value must be freed
389/// with tc_working_set_free.
390///
391/// Returns NULL on error.
392///
393/// ```c
394/// EXTERN_C struct TCWorkingSet *tc_replica_working_set(struct TCReplica *rep);
395/// ```
396#[no_mangle]
397pub unsafe extern "C" fn tc_replica_working_set(rep: *mut TCReplica) -> *mut TCWorkingSet {
398 wrap(
399 rep,
400 |rep| {
401 let ws = rep.working_set()?;
402 // SAFETY:
403 // - caller promises to free this value
404 Ok(unsafe { TCWorkingSet::return_ptr(ws.into()) })
405 },
406 std::ptr::null_mut(),
407 )
408}
409
410#[ffizz_header::item]
411#[ffizz(order = 902)]
412/// Get an existing task by its UUID.
413///
414/// Returns NULL when the task does not exist, and on error. Consult tc_replica_error
415/// to distinguish the two conditions.
416///
417/// ```c
418/// EXTERN_C struct TCTask *tc_replica_get_task(struct TCReplica *rep, struct TCUuid tcuuid);
419/// ```
420#[no_mangle]
421pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *mut TCTask {
422 wrap(
423 rep,
424 |rep| {
425 // SAFETY:
426 // - tcuuid is a valid TCUuid (all bytes are valid)
427 // - tcuuid is Copy so ownership doesn't matter
428 let uuid = unsafe { TCUuid::val_from_arg(tcuuid) };
429 if let Some(task) = rep.get_task(uuid)? {
430 // SAFETY:
431 // - caller promises to free this task
432 Ok(unsafe { TCTask::from(task).return_ptr() })
433 } else {
434 Ok(std::ptr::null_mut())
435 }
436 },
437 std::ptr::null_mut(),
438 )
439}
440
441#[ffizz_header::item]
442#[ffizz(order = 902)]
443/// Create a new task. The task must not already exist.
444///
445/// Returns the task, or NULL on error.
446///
447/// ```c
448/// EXTERN_C struct TCTask *tc_replica_new_task(struct TCReplica *rep,
449/// enum TCStatus status,
450/// struct TCString description);
451/// ```
452#[no_mangle]
453pub unsafe extern "C" fn tc_replica_new_task(
454 rep: *mut TCReplica,
455 status: TCStatus,
456 description: TCString,
457) -> *mut TCTask {
458 // SAFETY:
459 // - description is valid (promised by caller)
460 // - caller will not use description after this call (convention)
461 let mut description = unsafe { TCString::val_from_arg(description) };
462 wrap(
463 rep,
464 |rep| {
465 let task = rep.new_task(status.into(), description.as_str()?.to_string())?;
466 // SAFETY:
467 // - caller promises to free this task
468 Ok(unsafe { TCTask::from(task).return_ptr() })
469 },
470 std::ptr::null_mut(),
471 )
472}
473
474#[ffizz_header::item]
475#[ffizz(order = 902)]
476/// Create a new task. The task must not already exist.
477///
478/// Returns the task, or NULL on error.
479///
480/// ```c
481/// EXTERN_C struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid);
482/// ```
483#[no_mangle]
484pub unsafe extern "C" fn tc_replica_import_task_with_uuid(
485 rep: *mut TCReplica,
486 tcuuid: TCUuid,
487) -> *mut TCTask {
488 wrap(
489 rep,
490 |rep| {
491 // SAFETY:
492 // - tcuuid is a valid TCUuid (all bytes are valid)
493 // - tcuuid is Copy so ownership doesn't matter
494 let uuid = unsafe { TCUuid::val_from_arg(tcuuid) };
495 let task = rep.import_task_with_uuid(uuid)?;
496 // SAFETY:
497 // - caller promises to free this task
498 Ok(unsafe { TCTask::from(task).return_ptr() })
499 },
500 std::ptr::null_mut(),
501 )
502}
503
504#[ffizz_header::item]
505#[ffizz(order = 902)]
506/// Delete a task. The task must exist. Note that this is different from setting status to
507/// Deleted; this is the final purge of the task.
508///
509/// ```c
510/// EXTERN_C struct TCTask *tc_replica_delete_task(struct TCReplica *rep, struct TCUuid tcuuid);
511/// ```
512#[no_mangle]
513pub unsafe extern "C" fn tc_replica_delete_task(rep: *mut TCReplica, tcuuid: TCUuid) -> TCResult {
514 wrap(
515 rep,
516 |rep| {
517 // SAFETY:
518 // - tcuuid is a valid TCUuid (all bytes are valid)
519 // - tcuuid is Copy so ownership doesn't matter
520 let uuid = unsafe { TCUuid::val_from_arg(tcuuid) };
521 rep.delete_task(uuid)?;
522 Ok(TCResult::Ok)
523 },
524 TCResult::Error,
525 )
526}
527
528#[ffizz_header::item]
529#[ffizz(order = 902)]
530/// Synchronize this replica with a server.
531///
532/// The `server` argument remains owned by the caller, and must be freed explicitly.
533///
534/// ```c
535/// EXTERN_C TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool avoid_snapshots);
536/// ```
537#[no_mangle]
538pub unsafe extern "C" fn tc_replica_sync(
539 rep: *mut TCReplica,
540 server: *mut TCServer,
541 avoid_snapshots: bool,
542) -> TCResult {
543 wrap(
544 rep,
545 |rep| {
546 debug_assert!(!server.is_null());
547 // SAFETY:
548 // - server is not NULL
549 // - *server is a valid TCServer (promised by caller)
550 // - server is valid for the lifetime of tc_replica_sync (not threadsafe)
551 // - server will not be accessed simultaneously (not threadsafe)
552 let server = unsafe { TCServer::from_ptr_arg_ref_mut(server) };
553 rep.sync(server.as_mut(), avoid_snapshots)?;
554 Ok(TCResult::Ok)
555 },
556 TCResult::Error,
557 )
558}
559
560#[ffizz_header::item]
561#[ffizz(order = 902)]
562/// Return undo local operations until the most recent UndoPoint.
563///
564/// ```c
565/// EXTERN_C TCReplicaOpList tc_replica_get_undo_ops(struct TCReplica *rep);
566/// ```
567#[no_mangle]
568pub unsafe extern "C" fn tc_replica_get_undo_ops(rep: *mut TCReplica) -> TCReplicaOpList {
569 wrap(
570 rep,
571 |rep| {
572 // SAFETY:
573 // - caller will free this value, either with tc_replica_commit_undo_ops or
574 // tc_replica_op_list_free.
575 Ok(unsafe {
576 TCReplicaOpList::return_val(
577 rep.get_undo_ops()?
578 .into_iter()
579 .map(TCReplicaOp::from)
580 .collect(),
581 )
582 })
583 },
584 TCReplicaOpList::default(),
585 )
586}
587
588#[ffizz_header::item]
589#[ffizz(order = 902)]
590/// Undo local operations in storage.
591///
592/// If undone_out is not NULL, then on success it is set to 1 if operations were undone, or 0 if
593/// there are no operations that can be done.
594///
595/// ```c
596/// EXTERN_C TCResult tc_replica_commit_undo_ops(struct TCReplica *rep, TCReplicaOpList tc_undo_ops, int32_t *undone_out);
597/// ```
598#[no_mangle]
599pub unsafe extern "C" fn tc_replica_commit_undo_ops(
600 rep: *mut TCReplica,
601 tc_undo_ops: TCReplicaOpList,
602 undone_out: *mut i32,
603) -> TCResult {
604 wrap(
605 rep,
606 |rep| {
607 // SAFETY:
608 // - `tc_undo_ops` is a valid value, as it was acquired from `tc_replica_get_undo_ops`.
609 let undo_ops: Vec<ReplicaOp> = unsafe { TCReplicaOpList::val_from_arg(tc_undo_ops) }
610 .into_iter()
611 .map(|op| *op.inner)
612 .collect();
613 let undone = i32::from(rep.commit_undo_ops(undo_ops)?);
614 if !undone_out.is_null() {
615 // SAFETY:
616 // - undone_out is not NULL (just checked)
617 // - undone_out is properly aligned (implicitly promised by caller)
618 unsafe { *undone_out = undone };
619 }
620 Ok(TCResult::Ok)
621 },
622 TCResult::Error,
623 )
624}
625
626#[ffizz_header::item]
627#[ffizz(order = 902)]
628/// Get the number of local, un-synchronized operations (not including undo points), or -1 on
629/// error.
630///
631/// ```c
632/// EXTERN_C int64_t tc_replica_num_local_operations(struct TCReplica *rep);
633/// ```
634#[no_mangle]
635pub unsafe extern "C" fn tc_replica_num_local_operations(rep: *mut TCReplica) -> i64 {
636 wrap(
637 rep,
638 |rep| {
639 let count = rep.num_local_operations()? as i64;
640 Ok(count)
641 },
642 -1,
643 )
644}
645
646#[ffizz_header::item]
647#[ffizz(order = 902)]
648/// Get the number of undo points (number of undo calls possible), or -1 on error.
649///
650/// ```c
651/// EXTERN_C int64_t tc_replica_num_undo_points(struct TCReplica *rep);
652/// ```
653#[no_mangle]
654pub unsafe extern "C" fn tc_replica_num_undo_points(rep: *mut TCReplica) -> i64 {
655 wrap(
656 rep,
657 |rep| {
658 let count = rep.num_undo_points()? as i64;
659 Ok(count)
660 },
661 -1,
662 )
663}
664
665#[ffizz_header::item]
666#[ffizz(order = 902)]
667/// Add an UndoPoint, if one has not already been added by this Replica. This occurs automatically
668/// when a change is made. The `force` flag allows forcing a new UndoPoint even if one has already
669/// been created by this Replica, and may be useful when a Replica instance is held for a long time
670/// and used to apply more than one user-visible change.
671///
672/// ```c
673/// EXTERN_C TCResult tc_replica_add_undo_point(struct TCReplica *rep, bool force);
674/// ```
675#[no_mangle]
676pub unsafe extern "C" fn tc_replica_add_undo_point(rep: *mut TCReplica, force: bool) -> TCResult {
677 wrap(
678 rep,
679 |rep| {
680 rep.add_undo_point(force)?;
681 Ok(TCResult::Ok)
682 },
683 TCResult::Error,
684 )
685}
686
687#[ffizz_header::item]
688#[ffizz(order = 902)]
689/// Rebuild this replica's working set, based on whether tasks are pending or not. If `renumber`
690/// is true, then existing tasks may be moved to new working-set indices; in any case, on
691/// completion all pending tasks are in the working set and all non- pending tasks are not.
692///
693/// ```c
694/// EXTERN_C TCResult tc_replica_rebuild_working_set(struct TCReplica *rep, bool renumber);
695/// ```
696#[no_mangle]
697pub unsafe extern "C" fn tc_replica_rebuild_working_set(
698 rep: *mut TCReplica,
699 renumber: bool,
700) -> TCResult {
701 wrap(
702 rep,
703 |rep| {
704 rep.rebuild_working_set(renumber)?;
705 Ok(TCResult::Ok)
706 },
707 TCResult::Error,
708 )
709}
710
711#[ffizz_header::item]
712#[ffizz(order = 902)]
713/// Get the latest error for a replica, or a string with NULL ptr if no error exists. Subsequent
714/// calls to this function will return NULL. The rep pointer must not be NULL. The caller must
715/// free the returned string.
716///
717/// ```c
718/// EXTERN_C struct TCString tc_replica_error(struct TCReplica *rep);
719/// ```
720#[no_mangle]
721pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> TCString {
722 // SAFETY:
723 // - rep is not NULL (promised by caller)
724 // - *rep is a valid TCReplica (promised by caller)
725 // - rep is valid for the duration of this function
726 // - rep is not modified by anything else (not threadsafe)
727 let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) };
728 if let Some(rstring) = rep.error.take() {
729 // SAFETY:
730 // - caller promises to free this string
731 unsafe { TCString::return_val(rstring) }
732 } else {
733 TCString::default()
734 }
735}
736
737#[ffizz_header::item]
738#[ffizz(order = 903)]
739/// Free a replica. The replica may not be used after this function returns and must not be freed
740/// more than once.
741///
742/// ```c
743/// EXTERN_C void tc_replica_free(struct TCReplica *rep);
744/// ```
745#[no_mangle]
746pub unsafe extern "C" fn tc_replica_free(rep: *mut TCReplica) {
747 // SAFETY:
748 // - replica is not NULL (promised by caller)
749 // - replica is valid (promised by caller)
750 // - caller will not use description after this call (promised by caller)
751 let replica = unsafe { TCReplica::take_from_ptr_arg(rep) };
752 if replica.mut_borrowed {
753 panic!("replica is borrowed and cannot be freed");
754 }
755 drop(replica);
756}
757
758#[ffizz_header::item]
759#[ffizz(order = 903)]
760/// Free a vector of ReplicaOp. The vector may not be used after this function returns and must not be freed
761/// more than once.
762///
763/// ```c
764/// EXTERN_C void tc_replica_op_list_free(struct TCReplicaOpList *oplist);
765/// ```
766#[no_mangle]
767pub unsafe extern "C" fn tc_replica_op_list_free(oplist: *mut TCReplicaOpList) {
768 debug_assert!(!oplist.is_null());
769 // SAFETY:
770 // - arg is not NULL (just checked)
771 // - `*oplist` is valid (guaranteed by caller not double-freeing this value)
772 unsafe {
773 TCReplicaOpList::take_val_from_arg(
774 oplist,
775 // SAFETY:
776 // - value is empty, so the caller need not free it.
777 TCReplicaOpList::return_val(Vec::new()),
778 )
779 };
780}
781
782#[ffizz_header::item]
783#[ffizz(order = 903)]
784/// Return uuid field of ReplicaOp.
785///
786/// ```c
787/// EXTERN_C struct TCString tc_replica_op_get_uuid(struct TCReplicaOp *op);
788/// ```
789#[no_mangle]
790pub unsafe extern "C" fn tc_replica_op_get_uuid(op: *const TCReplicaOp) -> TCString {
791 // SAFETY:
792 // - inner is not null
793 // - inner is a living object
794 let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() };
795
796 if let ReplicaOp::Create { uuid }
797 | ReplicaOp::Delete { uuid, .. }
798 | ReplicaOp::Update { uuid, .. } = rop
799 {
800 let uuid_rstr: RustString = uuid.to_string().into();
801 // SAFETY:
802 // - caller promises to free this string
803 unsafe { TCString::return_val(uuid_rstr) }
804 } else {
805 panic!("Operation has no uuid: {:#?}", rop);
806 }
807}
808
809#[ffizz_header::item]
810#[ffizz(order = 903)]
811/// Return property field of ReplicaOp.
812///
813/// ```c
814/// EXTERN_C struct TCString tc_replica_op_get_property(struct TCReplicaOp *op);
815/// ```
816#[no_mangle]
817pub unsafe extern "C" fn tc_replica_op_get_property(op: *const TCReplicaOp) -> TCString {
818 // SAFETY:
819 // - inner is not null
820 // - inner is a living object
821 let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() };
822
823 if let ReplicaOp::Update { property, .. } = rop {
824 // SAFETY:
825 // - caller promises to free this string
826 unsafe { TCString::return_val(property.clone().into()) }
827 } else {
828 panic!("Operation has no property: {:#?}", rop);
829 }
830}
831
832#[ffizz_header::item]
833#[ffizz(order = 903)]
834/// Return value field of ReplicaOp.
835///
836/// ```c
837/// EXTERN_C struct TCString tc_replica_op_get_value(struct TCReplicaOp *op);
838/// ```
839#[no_mangle]
840pub unsafe extern "C" fn tc_replica_op_get_value(op: *const TCReplicaOp) -> TCString {
841 // SAFETY:
842 // - inner is not null
843 // - inner is a living object
844 let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() };
845
846 if let ReplicaOp::Update { value, .. } = rop {
847 let value_rstr: RustString = value.clone().unwrap_or(String::new()).into();
848 // SAFETY:
849 // - caller promises to free this string
850 unsafe { TCString::return_val(value_rstr) }
851 } else {
852 panic!("Operation has no value: {:#?}", rop);
853 }
854}
855
856#[ffizz_header::item]
857#[ffizz(order = 903)]
858/// Return old value field of ReplicaOp.
859///
860/// ```c
861/// EXTERN_C struct TCString tc_replica_op_get_old_value(struct TCReplicaOp *op);
862/// ```
863#[no_mangle]
864pub unsafe extern "C" fn tc_replica_op_get_old_value(op: *const TCReplicaOp) -> TCString {
865 // SAFETY:
866 // - inner is not null
867 // - inner is a living object
868 let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() };
869
870 if let ReplicaOp::Update { old_value, .. } = rop {
871 let old_value_rstr: RustString = old_value.clone().unwrap_or(String::new()).into();
872 // SAFETY:
873 // - caller promises to free this string
874 unsafe { TCString::return_val(old_value_rstr) }
875 } else {
876 panic!("Operation has no old value: {:#?}", rop);
877 }
878}
879
880#[ffizz_header::item]
881#[ffizz(order = 903)]
882/// Return timestamp field of ReplicaOp.
883///
884/// ```c
885/// EXTERN_C struct TCString tc_replica_op_get_timestamp(struct TCReplicaOp *op);
886/// ```
887#[no_mangle]
888pub unsafe extern "C" fn tc_replica_op_get_timestamp(op: *const TCReplicaOp) -> TCString {
889 // SAFETY:
890 // - inner is not null
891 // - inner is a living object
892 let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() };
893
894 if let ReplicaOp::Update { timestamp, .. } = rop {
895 let timestamp_rstr: RustString = timestamp.to_string().into();
896 // SAFETY:
897 // - caller promises to free this string
898 unsafe { TCString::return_val(timestamp_rstr) }
899 } else {
900 panic!("Operation has no timestamp: {:#?}", rop);
901 }
902}
903
904#[ffizz_header::item]
905#[ffizz(order = 903)]
906/// Return description field of old task field of ReplicaOp.
907///
908/// ```c
909/// EXTERN_C struct TCString tc_replica_op_get_old_task_description(struct TCReplicaOp *op);
910/// ```
911#[no_mangle]
912pub unsafe extern "C" fn tc_replica_op_get_old_task_description(
913 op: *const TCReplicaOp,
914) -> TCString {
915 // SAFETY:
916 // - inner is not null
917 // - inner is a living object
918 let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() };
919
920 if let ReplicaOp::Delete { old_task, .. } = rop {
921 let description_rstr: RustString = old_task["description"].clone().into();
922 // SAFETY:
923 // - caller promises to free this string
924 unsafe { TCString::return_val(description_rstr) }
925 } else {
926 panic!("Operation has no timestamp: {:#?}", rop);
927 }
928}