From af51e0382aa1a9c77d84f0afedac2a751b495409 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 11 Feb 2022 23:59:22 +0000 Subject: [PATCH] implement lists in the same files as singular data --- lib/src/lib.rs | 12 ++----- lib/src/string.rs | 65 +++++++++++++++++++++++++++++++++++++ lib/src/stringlist.rs | 72 ----------------------------------------- lib/src/task.rs | 69 ++++++++++++++++++++++++++++++++++++++++ lib/src/tasklist.rs | 72 ----------------------------------------- lib/src/uuid.rs | 74 +++++++++++++++++++++++++++++++++++++++++-- lib/src/uuidlist.rs | 70 ---------------------------------------- 7 files changed, 208 insertions(+), 226 deletions(-) delete mode 100644 lib/src/stringlist.rs delete mode 100644 lib/src/tasklist.rs delete mode 100644 lib/src/uuidlist.rs diff --git a/lib/src/lib.rs b/lib/src/lib.rs index f5c43439c..41fa16005 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -11,21 +11,15 @@ pub mod replica; pub mod result; pub mod status; pub mod string; -pub mod stringlist; pub mod task; -pub mod tasklist; pub mod uuid; -pub mod uuidlist; pub(crate) mod types { pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList}; pub(crate) use crate::replica::TCReplica; pub(crate) use crate::result::TCResult; pub(crate) use crate::status::TCStatus; - pub(crate) use crate::string::TCString; - pub(crate) use crate::stringlist::TCStringList; - pub(crate) use crate::task::TCTask; - pub(crate) use crate::tasklist::TCTaskList; - pub(crate) use crate::uuid::TCUuid; - pub(crate) use crate::uuidlist::TCUuidList; + pub(crate) use crate::string::{TCString, TCStringList}; + pub(crate) use crate::task::{TCTask, TCTaskList}; + pub(crate) use crate::uuid::{TCUuid, TCUuidList}; } diff --git a/lib/src/string.rs b/lib/src/string.rs index 257d105eb..d9fc90507 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -2,6 +2,7 @@ use crate::traits::*; use std::ffi::{CStr, CString, OsStr}; use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; +use std::ptr::NonNull; use std::str::Utf8Error; /// TCString supports passing strings into and out of the TaskChampion API. @@ -136,6 +137,39 @@ impl<'a> From<&str> for TCString<'static> { } } +/// TCStringList represents a list of strings. +/// +/// The content of this struct must be treated as read-only. +#[repr(C)] +pub struct TCStringList { + /// number of strings in items + len: libc::size_t, + + /// total size of items (internal use only) + _capacity: libc::size_t, + + /// TCStringList representing each string. these remain owned by the TCStringList instance and will + /// be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the + /// *TCStringList at indexes 0..len-1 are not NULL. + items: *const NonNull>, +} + +impl CArray for TCStringList { + type Element = NonNull>; + + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + TCStringList { + len, + _capacity: cap, + items, + } + } + + fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + (self.items, self.len, self._capacity) + } +} + /// Create a new TCString referencing the given C string. The C string must remain valid and /// unchanged until after the TCString is freed. It's typically easiest to ensure this by using a /// static string. @@ -281,11 +315,42 @@ pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) { drop(unsafe { TCString::take_from_arg(tcstring) }); } +/// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after +/// this call. +/// +/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList. +#[no_mangle] +pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) { + debug_assert!(!tcstrings.is_null()); + // SAFETY: + // - *tcstrings is a valid TCStringList (caller promises to treat it as read-only) + let strings = unsafe { TCStringList::take_from_arg(tcstrings, TCStringList::null_value()) }; + TCStringList::drop_vector(strings); +} + #[cfg(test)] mod test { use super::*; use pretty_assertions::assert_eq; + #[test] + fn empty_array_has_non_null_pointer() { + let tcstrings = TCStringList::return_val(Vec::new()); + assert!(!tcstrings.items.is_null()); + assert_eq!(tcstrings.len, 0); + assert_eq!(tcstrings._capacity, 0); + } + + #[test] + fn free_sets_null_pointer() { + let mut tcstrings = TCStringList::return_val(Vec::new()); + // SAFETY: testing expected behavior + unsafe { tc_string_list_free(&mut tcstrings) }; + assert!(tcstrings.items.is_null()); + assert_eq!(tcstrings.len, 0); + assert_eq!(tcstrings._capacity, 0); + } + const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28"; fn make_cstring() -> TCString<'static> { diff --git a/lib/src/stringlist.rs b/lib/src/stringlist.rs deleted file mode 100644 index c4fe8d0f7..000000000 --- a/lib/src/stringlist.rs +++ /dev/null @@ -1,72 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use std::ptr::NonNull; - -/// TCStringList represents a list of strings. -/// -/// The content of this struct must be treated as read-only. -#[repr(C)] -pub struct TCStringList { - /// number of strings in items - len: libc::size_t, - - /// total size of items (internal use only) - _capacity: libc::size_t, - - /// TCStringList representing each string. these remain owned by the TCStringList instance and will - /// be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the - /// *TCStringList at indexes 0..len-1 are not NULL. - items: *const NonNull>, -} - -impl CArray for TCStringList { - type Element = NonNull>; - - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { - TCStringList { - len, - _capacity: cap, - items, - } - } - - fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { - (self.items, self.len, self._capacity) - } -} - -/// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList. -#[no_mangle] -pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) { - debug_assert!(!tcstrings.is_null()); - // SAFETY: - // - *tcstrings is a valid TCStringList (caller promises to treat it as read-only) - let strings = unsafe { TCStringList::take_from_arg(tcstrings, TCStringList::null_value()) }; - TCStringList::drop_vector(strings); -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_array_has_non_null_pointer() { - let tcstrings = TCStringList::return_val(Vec::new()); - assert!(!tcstrings.items.is_null()); - assert_eq!(tcstrings.len, 0); - assert_eq!(tcstrings._capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tcstrings = TCStringList::return_val(Vec::new()); - // SAFETY: testing expected behavior - unsafe { tc_string_list_free(&mut tcstrings) }; - assert!(tcstrings.items.is_null()); - assert_eq!(tcstrings.len, 0); - assert_eq!(tcstrings._capacity, 0); - } -} diff --git a/lib/src/task.rs b/lib/src/task.rs index 2f7da32ef..b56a71498 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -171,6 +171,39 @@ fn to_datetime(time: libc::time_t) -> Option> { } } +/// TCTaskList represents a list of tasks. +/// +/// The content of this struct must be treated as read-only. +#[repr(C)] +pub struct TCTaskList { + /// number of tasks in items + len: libc::size_t, + + /// total size of items (internal use only) + _capacity: libc::size_t, + + /// array of pointers representing each task. these remain owned by the TCTaskList instance and + /// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList, + /// and the *TCTaskList at indexes 0..len-1 are not NULL. + items: *const NonNull, +} + +impl CArray for TCTaskList { + type Element = NonNull; + + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + TCTaskList { + len, + _capacity: cap, + items, + } + } + + fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + (self.items, self.len, self._capacity) + } +} + /// Convert an immutable task into a mutable task. /// /// The task must not be NULL. It is modified in-place, and becomes mutable. @@ -553,3 +586,39 @@ pub unsafe extern "C" fn tc_task_free<'a>(task: *mut TCTask) { drop(tctask); } + +/// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after +/// this call. +/// +/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. +#[no_mangle] +pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) { + debug_assert!(!tctasks.is_null()); + // SAFETY: + // - *tctasks is a valid TCTaskList (caller promises to treat it as read-only) + let tasks = unsafe { TCTaskList::take_from_arg(tctasks, TCTaskList::null_value()) }; + TCTaskList::drop_vector(tasks); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn empty_array_has_non_null_pointer() { + let tctasks = TCTaskList::return_val(Vec::new()); + assert!(!tctasks.items.is_null()); + assert_eq!(tctasks.len, 0); + assert_eq!(tctasks._capacity, 0); + } + + #[test] + fn free_sets_null_pointer() { + let mut tctasks = TCTaskList::return_val(Vec::new()); + // SAFETY: testing expected behavior + unsafe { tc_task_list_free(&mut tctasks) }; + assert!(tctasks.items.is_null()); + assert_eq!(tctasks.len, 0); + assert_eq!(tctasks._capacity, 0); + } +} diff --git a/lib/src/tasklist.rs b/lib/src/tasklist.rs deleted file mode 100644 index 8905384aa..000000000 --- a/lib/src/tasklist.rs +++ /dev/null @@ -1,72 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use std::ptr::NonNull; - -/// TCTaskList represents a list of tasks. -/// -/// The content of this struct must be treated as read-only. -#[repr(C)] -pub struct TCTaskList { - /// number of tasks in items - len: libc::size_t, - - /// total size of items (internal use only) - _capacity: libc::size_t, - - /// array of pointers representing each task. these remain owned by the TCTaskList instance and - /// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList, - /// and the *TCTaskList at indexes 0..len-1 are not NULL. - items: *const NonNull, -} - -impl CArray for TCTaskList { - type Element = NonNull; - - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { - TCTaskList { - len, - _capacity: cap, - items, - } - } - - fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { - (self.items, self.len, self._capacity) - } -} - -/// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. -#[no_mangle] -pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) { - debug_assert!(!tctasks.is_null()); - // SAFETY: - // - *tctasks is a valid TCTaskList (caller promises to treat it as read-only) - let tasks = unsafe { TCTaskList::take_from_arg(tctasks, TCTaskList::null_value()) }; - TCTaskList::drop_vector(tasks); -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_array_has_non_null_pointer() { - let tctasks = TCTaskList::return_val(Vec::new()); - assert!(!tctasks.items.is_null()); - assert_eq!(tctasks.len, 0); - assert_eq!(tctasks._capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tctasks = TCTaskList::return_val(Vec::new()); - // SAFETY: testing expected behavior - unsafe { tc_task_list_free(&mut tctasks) }; - assert!(tctasks.items.is_null()); - assert_eq!(tctasks.len, 0); - assert_eq!(tctasks._capacity, 0); - } -} diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 63be3823c..d140a9eed 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -3,6 +3,10 @@ use crate::types::*; use libc; use taskchampion::Uuid; +// NOTE: this must be a simple constant so that cbindgen can evaluate it +/// Length, in bytes, of the string representation of a UUID (without NUL terminator) +pub const TC_UUID_STRING_BYTES: usize = 36; + /// TCUuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. /// Uuids are typically treated as opaque, but the bytes are available in big-endian format. /// @@ -36,9 +40,37 @@ pub unsafe extern "C" fn tc_uuid_nil() -> TCUuid { TCUuid::return_val(Uuid::nil()) } -// NOTE: this must be a simple constant so that cbindgen can evaluate it -/// Length, in bytes, of the string representation of a UUID (without NUL terminator) -pub const TC_UUID_STRING_BYTES: usize = 36; +/// TCUuidList represents a list of uuids. +/// +/// The content of this struct must be treated as read-only. +#[repr(C)] +pub struct TCUuidList { + /// number of uuids in items + len: libc::size_t, + + /// total size of items (internal use only) + _capacity: libc::size_t, + + /// array of uuids. these remain owned by the TCUuidList instance and will be freed by + /// tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList. + items: *const TCUuid, +} + +impl CArray for TCUuidList { + type Element = TCUuid; + + unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { + TCUuidList { + len, + _capacity: cap, + items, + } + } + + fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { + (self.items, self.len, self._capacity) + } +} /// Write the string representation of a TCUuid into the given buffer, which must be /// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. @@ -91,3 +123,39 @@ pub unsafe extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut T } TCResult::Error } + +/// Free a TCUuidList instance. The instance, and all TCUuids it contains, must not be used after +/// this call. +/// +/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList. +#[no_mangle] +pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) { + debug_assert!(!tcuuids.is_null()); + // SAFETY: + // - *tcuuids is a valid TCUuidList (caller promises to treat it as read-only) + let uuids = unsafe { TCUuidList::take_from_arg(tcuuids, TCUuidList::null_value()) }; + TCUuidList::drop_vector(uuids); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn empty_array_has_non_null_pointer() { + let tcuuids = TCUuidList::return_val(Vec::new()); + assert!(!tcuuids.items.is_null()); + assert_eq!(tcuuids.len, 0); + assert_eq!(tcuuids._capacity, 0); + } + + #[test] + fn free_sets_null_pointer() { + let mut tcuuids = TCUuidList::return_val(Vec::new()); + // SAFETY: testing expected behavior + unsafe { tc_uuid_list_free(&mut tcuuids) }; + assert!(tcuuids.items.is_null()); + assert_eq!(tcuuids.len, 0); + assert_eq!(tcuuids._capacity, 0); + } +} diff --git a/lib/src/uuidlist.rs b/lib/src/uuidlist.rs deleted file mode 100644 index 139bb8bd3..000000000 --- a/lib/src/uuidlist.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::traits::*; -use crate::types::*; - -/// TCUuidList represents a list of uuids. -/// -/// The content of this struct must be treated as read-only. -#[repr(C)] -pub struct TCUuidList { - /// number of uuids in items - len: libc::size_t, - - /// total size of items (internal use only) - _capacity: libc::size_t, - - /// array of uuids. these remain owned by the TCUuidList instance and will be freed by - /// tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList. - items: *const TCUuid, -} - -impl CArray for TCUuidList { - type Element = TCUuid; - - unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { - TCUuidList { - len, - _capacity: cap, - items, - } - } - - fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { - (self.items, self.len, self._capacity) - } -} - -/// Free a TCUuidList instance. The instance, and all TCUuids it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList. -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) { - debug_assert!(!tcuuids.is_null()); - // SAFETY: - // - *tcuuids is a valid TCUuidList (caller promises to treat it as read-only) - let uuids = unsafe { TCUuidList::take_from_arg(tcuuids, TCUuidList::null_value()) }; - TCUuidList::drop_vector(uuids); -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_array_has_non_null_pointer() { - let tcuuids = TCUuidList::return_val(Vec::new()); - assert!(!tcuuids.items.is_null()); - assert_eq!(tcuuids.len, 0); - assert_eq!(tcuuids._capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tcuuids = TCUuidList::return_val(Vec::new()); - // SAFETY: testing expected behavior - unsafe { tc_uuid_list_free(&mut tcuuids) }; - assert!(tcuuids.items.is_null()); - assert_eq!(tcuuids.len, 0); - assert_eq!(tcuuids._capacity, 0); - } -}