add UDA support

This commit is contained in:
Dustin J. Mitchell 2022-02-12 16:22:45 +00:00
parent f81c4eec90
commit ad560fdb79
5 changed files with 560 additions and 8 deletions

View file

@ -386,6 +386,140 @@ static void test_task_annotations(void) {
tc_replica_free(rep);
}
// UDA manipulation (add, remove, list, free)
static void test_task_udas(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep));
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
tc_task_to_mut(task, rep);
TCString *value;
TCUDAList udas;
TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")));
TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1")));
udas = tc_task_get_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(0, udas.len);
tc_uda_list_free(&udas);
udas = tc_task_get_legacy_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(0, udas.len);
tc_uda_list_free(&udas);
TEST_ASSERT_EQUAL(TC_RESULT_OK,
tc_task_set_uda(task,
tc_string_borrow("ns"),
tc_string_borrow("u1"),
tc_string_borrow("vvv")));
value = tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1"));
TEST_ASSERT_NOT_NULL(value);
TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(value));
tc_string_free(value);
TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1")));
udas = tc_task_get_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(1, udas.len);
TEST_ASSERT_EQUAL_STRING("ns", tc_string_content(udas.items[0].ns));
TEST_ASSERT_EQUAL_STRING("u1", tc_string_content(udas.items[0].key));
TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(udas.items[0].value));
tc_uda_list_free(&udas);
udas = tc_task_get_legacy_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(1, udas.len);
TEST_ASSERT_NULL(udas.items[0].ns);
TEST_ASSERT_EQUAL_STRING("ns.u1", tc_string_content(udas.items[0].key));
TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(udas.items[0].value));
tc_uda_list_free(&udas);
TEST_ASSERT_EQUAL(TC_RESULT_OK,
tc_task_set_legacy_uda(task,
tc_string_borrow("leg1"),
tc_string_borrow("legv")));
value = tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1"));
TEST_ASSERT_NOT_NULL(value);
TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(value));
tc_string_free(value);
value = tc_task_get_legacy_uda(task, tc_string_borrow("leg1"));
TEST_ASSERT_NOT_NULL(value);
TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(value));
tc_string_free(value);
udas = tc_task_get_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(2, udas.len);
tc_uda_list_free(&udas);
udas = tc_task_get_legacy_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(2, udas.len);
tc_uda_list_free(&udas);
TEST_ASSERT_EQUAL(TC_RESULT_OK,
tc_task_remove_uda(task,
tc_string_borrow("ns"),
tc_string_borrow("u1")));
TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")));
TEST_ASSERT_EQUAL(TC_RESULT_OK,
tc_task_remove_uda(task,
tc_string_borrow("ns"),
tc_string_borrow("u1")));
udas = tc_task_get_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(1, udas.len);
TEST_ASSERT_EQUAL_STRING("", tc_string_content(udas.items[0].ns));
TEST_ASSERT_EQUAL_STRING("leg1", tc_string_content(udas.items[0].key));
TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(udas.items[0].value));
tc_uda_list_free(&udas);
udas = tc_task_get_legacy_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(1, udas.len);
TEST_ASSERT_NULL(udas.items[0].ns);
TEST_ASSERT_EQUAL_STRING("leg1", tc_string_content(udas.items[0].key));
TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(udas.items[0].value));
tc_uda_list_free(&udas);
TEST_ASSERT_EQUAL(TC_RESULT_OK,
tc_task_remove_legacy_uda(task,
tc_string_borrow("leg1")));
TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1")));
TEST_ASSERT_EQUAL(TC_RESULT_OK,
tc_task_remove_legacy_uda(task,
tc_string_borrow("leg1")));
udas = tc_task_get_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(0, udas.len);
tc_uda_list_free(&udas);
udas = tc_task_get_legacy_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(0, udas.len);
tc_uda_list_free(&udas);
tc_task_free(task);
tc_replica_free(rep);
}
int task_tests(void) {
UNITY_BEGIN();
// each test case above should be named here, in order.
@ -401,5 +535,6 @@ int task_tests(void) {
RUN_TEST(test_task_add_remove_has_tag);
RUN_TEST(test_task_get_tags);
RUN_TEST(test_task_annotations);
RUN_TEST(test_task_udas);
return UNITY_END();
}

View file

@ -12,6 +12,7 @@ pub mod result;
pub mod status;
pub mod string;
pub mod task;
pub mod uda;
pub mod uuid;
pub(crate) mod types {
@ -21,5 +22,6 @@ pub(crate) mod types {
pub(crate) use crate::status::TCStatus;
pub(crate) use crate::string::{TCString, TCStringList};
pub(crate) use crate::task::{TCTask, TCTaskList};
pub(crate) use crate::uda::{TCUDAList, TCUDA, UDA};
pub(crate) use crate::uuid::{TCUuid, TCUuidList};
}

View file

@ -340,10 +340,89 @@ pub unsafe extern "C" fn tc_task_get_annotations<'a>(task: *mut TCTask) -> TCAnn
})
}
// TODO: tc_task_get_uda
// TODO: tc_task_get_udas
// TODO: tc_task_get_legacy_uda
// TODO: tc_task_get_legacy_udas
/// Get the named UDA from the task.
///
/// Returns NULL if the UDA does not exist.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_uda<'a>(
task: *mut TCTask,
ns: *mut TCString<'a>,
key: *mut TCString<'a>,
) -> *mut TCString<'static> {
wrap(task, |task| {
if let Ok(ns) = unsafe { TCString::take_from_arg(ns) }.as_str() {
if let Ok(key) = unsafe { TCString::take_from_arg(key) }.as_str() {
if let Some(value) = task.get_uda(ns, key) {
// SAFETY:
// - caller will free this string (caller promises)
return unsafe { TCString::return_val(value.into()) };
}
}
}
std::ptr::null_mut()
})
}
/// Get the named legacy UDA from the task.
///
/// Returns NULL if the UDA does not exist.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_legacy_uda<'a>(
task: *mut TCTask,
key: *mut TCString<'a>,
) -> *mut TCString<'static> {
wrap(task, |task| {
if let Ok(key) = unsafe { TCString::take_from_arg(key) }.as_str() {
if let Some(value) = task.get_legacy_uda(key) {
// SAFETY:
// - caller will free this string (caller promises)
return unsafe { TCString::return_val(value.into()) };
}
}
std::ptr::null_mut()
})
}
/// Get all UDAs for this task.
///
/// Legacy UDAs are represented with an empty string in the ns field.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_udas<'a>(task: *mut TCTask) -> TCUDAList {
wrap(task, |task| {
let vec: Vec<TCUDA> = task
.get_udas()
.map(|((ns, key), value)| {
TCUDA::return_val(UDA {
ns: Some(ns.into()),
key: key.into(),
value: value.into(),
})
})
.collect();
TCUDAList::return_val(vec)
})
}
/// Get all UDAs for this task.
///
/// All TCUDAs in this list have a NULL ns field. The entire UDA key is
/// included in the key field.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_legacy_udas<'a>(task: *mut TCTask) -> TCUDAList {
wrap(task, |task| {
let vec: Vec<TCUDA> = task
.get_legacy_udas()
.map(|(key, value)| {
TCUDA::return_val(UDA {
ns: None,
key: key.into(),
value: value.into(),
})
})
.collect();
TCUDAList::return_val(vec)
})
}
/// Set a mutable task's status.
#[no_mangle]
@ -542,10 +621,93 @@ pub unsafe extern "C" fn tc_task_remove_annotation(task: *mut TCTask, entry: i64
)
}
// TODO: tc_task_set_uda
// TODO: tc_task_remove_uda
// TODO: tc_task_set_legacy_uda
// TODO: tc_task_remove_legacy_uda
/// Set a UDA on a mutable task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_set_uda<'a>(
task: *mut TCTask,
ns: *mut TCString,
key: *mut TCString,
value: *mut TCString,
) -> TCResult {
// SAFETY: see TCString docstring
let ns = unsafe { TCString::take_from_arg(ns) };
// SAFETY: see TCString docstring
let key = unsafe { TCString::take_from_arg(key) };
// SAFETY: see TCString docstring
let value = unsafe { TCString::take_from_arg(value) };
wrap_mut(
task,
|task| {
task.set_uda(
ns.as_str()?.to_string(),
key.as_str()?.to_string(),
value.as_str()?.to_string(),
)?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Remove a UDA fraom a mutable task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_remove_uda<'a>(
task: *mut TCTask,
ns: *mut TCString,
key: *mut TCString,
) -> TCResult {
// SAFETY: see TCString docstring
let ns = unsafe { TCString::take_from_arg(ns) };
// SAFETY: see TCString docstring
let key = unsafe { TCString::take_from_arg(key) };
wrap_mut(
task,
|task| {
task.remove_uda(ns.as_str()?.to_string(), key.as_str()?.to_string())?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Set a legacy UDA on a mutable task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_set_legacy_uda<'a>(
task: *mut TCTask,
key: *mut TCString,
value: *mut TCString,
) -> TCResult {
// SAFETY: see TCString docstring
let key = unsafe { TCString::take_from_arg(key) };
// SAFETY: see TCString docstring
let value = unsafe { TCString::take_from_arg(value) };
wrap_mut(
task,
|task| {
task.set_legacy_uda(key.as_str()?.to_string(), value.as_str()?.to_string())?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Remove a UDA fraom a mutable task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_remove_legacy_uda<'a>(
task: *mut TCTask,
key: *mut TCString,
) -> TCResult {
// SAFETY: see TCString docstring
let key = unsafe { TCString::take_from_arg(key) };
wrap_mut(
task,
|task| {
task.remove_legacy_uda(key.as_str()?.to_string())?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Get the latest error for a task, or NULL if the last operation succeeded. Subsequent calls
/// to this function will return NULL. The task pointer must not be NULL. The caller must free the

148
lib/src/uda.rs Normal file
View file

@ -0,0 +1,148 @@
use crate::traits::*;
use crate::types::*;
/// TCUDA contains the details of a UDA.
#[repr(C)]
pub struct TCUDA {
/// Namespace of the UDA. For legacy UDAs, this is NULL.
pub ns: *mut TCString<'static>,
/// UDA key. Must not be NULL.
pub key: *mut TCString<'static>,
/// Content of the UDA. Must not be NULL.
pub value: *mut TCString<'static>,
}
pub(crate) struct UDA {
pub ns: Option<TCString<'static>>,
pub key: TCString<'static>,
pub value: TCString<'static>,
}
impl PassByValue for TCUDA {
type RustType = UDA;
unsafe fn from_ctype(self) -> Self::RustType {
UDA {
ns: if self.ns.is_null() {
None
} else {
// SAFETY:
// - self is owned, so we can take ownership of this TCString
// - self.ns is a valid, non-null TCString (NULL just checked)
Some(unsafe { TCString::take_from_arg(self.ns) })
},
// SAFETY:
// - self is owned, so we can take ownership of this TCString
// - self.key is a valid, non-null TCString (see type docstring)
key: unsafe { TCString::take_from_arg(self.key) },
// SAFETY:
// - self is owned, so we can take ownership of this TCString
// - self.value is a valid, non-null TCString (see type docstring)
value: unsafe { TCString::take_from_arg(self.value) },
}
}
fn as_ctype(uda: UDA) -> Self {
TCUDA {
// SAFETY: caller assumes ownership of this value
ns: if let Some(ns) = uda.ns {
unsafe { ns.return_val() }
} else {
std::ptr::null_mut()
},
// SAFETY: caller assumes ownership of this value
key: unsafe { uda.key.return_val() },
// SAFETY: caller assumes ownership of this value
value: unsafe { uda.value.return_val() },
}
}
}
impl Default for TCUDA {
fn default() -> Self {
TCUDA {
ns: std::ptr::null_mut(),
key: std::ptr::null_mut(),
value: std::ptr::null_mut(),
}
}
}
/// TCUDAList represents a list of UDAs.
///
/// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCUDAList {
/// number of UDAs in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// array of UDAs. These remain owned by the TCUDAList instance and will be freed by
/// tc_uda_list_free. This pointer is never NULL for a valid TCUDAList.
items: *const TCUDA,
}
impl CList for TCUDAList {
type Element = TCUDA;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCUDAList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// Free a TCUDA instance. The instance, and the TCStrings it contains, must not be used
/// after this call.
#[no_mangle]
pub unsafe extern "C" fn tc_uda_free(tcuda: *mut TCUDA) {
debug_assert!(!tcuda.is_null());
// SAFETY:
// - *tcuda is a valid TCUDA (caller promises to treat it as read-only)
let uda = unsafe { TCUDA::take_from_arg(tcuda, TCUDA::default()) };
drop(uda);
}
/// Free a TCUDAList instance. The instance, and all TCUDAs it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUDAList.
#[no_mangle]
pub unsafe extern "C" fn tc_uda_list_free(tcudas: *mut TCUDAList) {
// SAFETY:
// - tcudas is not NULL and points to a valid TCUDAList (caller is not allowed to
// modify the list)
// - caller promises not to use the value after return
unsafe { drop_value_list(tcudas) }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn empty_list_has_non_null_pointer() {
let tcudas = TCUDAList::return_val(Vec::new());
assert!(!tcudas.items.is_null());
assert_eq!(tcudas.len, 0);
assert_eq!(tcudas._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tcudas = TCUDAList::return_val(Vec::new());
// SAFETY: testing expected behavior
unsafe { tc_uda_list_free(&mut tcudas) };
assert!(tcudas.items.is_null());
assert_eq!(tcudas.len, 0);
assert_eq!(tcudas._capacity, 0);
}
}

View file

@ -226,6 +226,45 @@ typedef struct TCStringList {
struct TCString *const *items;
} TCStringList;
/**
* TCUDA contains the details of a UDA.
*/
typedef struct TCUDA {
/**
* Namespace of the UDA. For legacy UDAs, this is NULL.
*/
struct TCString *ns;
/**
* UDA key. Must not be NULL.
*/
struct TCString *key;
/**
* Content of the UDA. Must not be NULL.
*/
struct TCString *value;
} TCUDA;
/**
* TCUDAList represents a list of UDAs.
*
* The content of this struct must be treated as read-only.
*/
typedef struct TCUDAList {
/**
* number of UDAs in items
*/
size_t len;
/**
* total size of items (internal use only)
*/
size_t _capacity;
/**
* array of UDAs. These remain owned by the TCUDAList instance and will be freed by
* tc_uda_list_free. This pointer is never NULL for a valid TCUDAList.
*/
const struct TCUDA *items;
} TCUDAList;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
@ -491,6 +530,35 @@ struct TCStringList tc_task_get_tags(struct TCTask *task);
*/
struct TCAnnotationList tc_task_get_annotations(struct TCTask *task);
/**
* Get the named UDA from the task.
*
* Returns NULL if the UDA does not exist.
*/
struct TCString *tc_task_get_uda(struct TCTask *task, struct TCString *ns, struct TCString *key);
/**
* Get the named legacy UDA from the task.
*
* Returns NULL if the UDA does not exist.
*/
struct TCString *tc_task_get_legacy_uda(struct TCTask *task, struct TCString *key);
/**
* Get all UDAs for this task.
*
* Legacy UDAs are represented with an empty string in the ns field.
*/
struct TCUDAList tc_task_get_udas(struct TCTask *task);
/**
* Get all UDAs for this task.
*
* All TCUDAs in this list have a NULL ns field. The entire UDA key is
* included in the key field.
*/
struct TCUDAList tc_task_get_legacy_udas(struct TCTask *task);
/**
* Set a mutable task's status.
*/
@ -557,6 +625,29 @@ TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annota
*/
TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry);
/**
* Set a UDA on a mutable task.
*/
TCResult tc_task_set_uda(struct TCTask *task,
struct TCString *ns,
struct TCString *key,
struct TCString *value);
/**
* Remove a UDA fraom a mutable task.
*/
TCResult tc_task_remove_uda(struct TCTask *task, struct TCString *ns, struct TCString *key);
/**
* Set a legacy UDA on a mutable task.
*/
TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString *key, struct TCString *value);
/**
* Remove a UDA fraom a mutable task.
*/
TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString *key);
/**
* Get the latest error for a task, or NULL if the last operation succeeded. Subsequent calls
* to this function will return NULL. The task pointer must not be NULL. The caller must free the
@ -580,6 +671,20 @@ void tc_task_free(struct TCTask *task);
*/
void tc_task_list_free(struct TCTaskList *tctasks);
/**
* Free a TCUDA instance. The instance, and the TCStrings it contains, must not be used
* after this call.
*/
void tc_uda_free(struct TCUDA *tcuda);
/**
* Free a TCUDAList instance. The instance, and all TCUDAs it contains, must not be used after
* this call.
*
* When this call returns, the `items` pointer will be NULL, signalling an invalid TCUDAList.
*/
void tc_uda_list_free(struct TCUDAList *tcudas);
/**
* Create a new, randomly-generated UUID.
*/