diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index 9ca537760..7fcb9a683 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -149,9 +149,13 @@ static void task_task_add_tag(void) { tc_task_to_mut(task, rep); + TEST_ASSERT_FALSE(tc_task_has_tag(task, tc_string_borrow("next"))); + TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next"))); TEST_ASSERT_NULL(tc_task_error(task)); + TEST_ASSERT_TRUE(tc_task_has_tag(task, tc_string_borrow("next"))); + // invalid - synthetic tag TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("PENDING"))); TCString *err = tc_task_error(task); @@ -170,7 +174,7 @@ static void task_task_add_tag(void) { TEST_ASSERT_NOT_NULL(err); tc_string_free(err); - // TODO: test getting the tag + TEST_ASSERT_TRUE(tc_task_has_tag(task, tc_string_borrow("next"))); tc_task_free(task); tc_replica_free(rep); diff --git a/lib/src/task.rs b/lib/src/task.rs index 841ae83f5..3558ccf20 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -2,6 +2,7 @@ use crate::util::err_to_tcstring; use crate::{ replica::TCReplica, result::TCResult, status::TCStatus, string::TCString, uuid::TCUuid, }; +use std::convert::TryFrom; use std::ops::Deref; use std::str::FromStr; use taskchampion::{Tag, Task, TaskMut}; @@ -168,6 +169,15 @@ where } } +impl TryFrom> for Tag { + type Error = anyhow::Error; + + fn try_from(tcstring: TCString) -> Result { + let tagstr = tcstring.as_str()?; + Tag::from_str(tagstr) + } +} + /// Convert an immutable task into a mutable task. /// /// The task must not be NULL. It is modified in-place, and becomes mutable. @@ -246,7 +256,23 @@ pub extern "C" fn tc_task_is_active<'a>(task: *mut TCTask) -> bool { wrap(task, |task| task.is_active()) } -// TODO: tc_task_has_tag +/// Check if a task has the given tag. If the tag is invalid, this function will simply return +/// false with no error from `tc_task_error`. The given tag must not be NULL. +#[no_mangle] +pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> bool { + // SAFETY: + // - tcstring is not NULL (promised by caller) + // - caller is exclusive owner of tcstring (implicitly promised by caller) + let tcstring = unsafe { TCString::from_arg(tag) }; + wrap(task, |task| { + if let Ok(tag) = Tag::try_from(tcstring) { + task.has_tag(&tag) + } else { + false + } + }) +} + // TODO: tc_task_get_tags // TODO: tc_task_get_annotations // TODO: tc_task_get_uda @@ -331,8 +357,7 @@ pub extern "C" fn tc_task_add_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> wrap_mut( task, |task| { - let tagstr = tcstring.as_str()?; - let tag = Tag::from_str(tagstr)?; + let tag = Tag::try_from(tcstring)?; task.add_tag(&tag)?; Ok(TCResult::Ok) }, diff --git a/lib/taskchampion.h b/lib/taskchampion.h index e2c3cef79..a0b9e46d7 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -269,6 +269,12 @@ struct TCString *tc_task_get_description(struct TCTask *task); */ bool tc_task_is_active(struct TCTask *task); +/** + * Check if a task has the given tag. If the tag is invalid, this function will simply return + * false with no error from `tc_task_error`. The given tag must not be NULL. + */ +bool tc_task_has_tag(struct TCTask *task, struct TCString *tag); + /** * Set a mutable task's status. */