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}