From d9cee2be4038d8f0a1fc924ee3900bf368db281e Mon Sep 17 00:00:00 2001 From: David Rousselie Date: Thu, 27 Jan 2022 14:07:15 +0100 Subject: [PATCH] Return new task after adding a new one --- src/contextswitch/api.rs | 5 ++- src/contextswitch/taskwarrior.rs | 69 +++++++++++++++++++++++++++----- src/routes/tasks.rs | 13 +++--- tests/task.rs | 23 +++++------ 4 files changed, 79 insertions(+), 31 deletions(-) diff --git a/src/contextswitch/api.rs b/src/contextswitch/api.rs index 70a0bdc..267f0fe 100644 --- a/src/contextswitch/api.rs +++ b/src/contextswitch/api.rs @@ -12,6 +12,7 @@ pub fn list_tasks(filters: Vec<&str>) -> Result, Error> { } #[tracing::instrument(level = "debug")] -pub async fn add_task(add_args: Vec<&str>) -> Result { - taskwarrior::add_task(add_args).await +pub async fn add_task(add_args: Vec<&str>) -> Result { + let taskwarrior_task = taskwarrior::add_task(add_args).await?; + (&taskwarrior_task).try_into() } diff --git a/src/contextswitch/taskwarrior.rs b/src/contextswitch/taskwarrior.rs index 8bc359b..4678610 100644 --- a/src/contextswitch/taskwarrior.rs +++ b/src/contextswitch/taskwarrior.rs @@ -1,11 +1,11 @@ use chrono::{DateTime, Utc}; use configparser::ini::Ini; -use contextswitch_types::ContextSwitchMetadata; -use contextswitch_types::Task; +use contextswitch_types::{ContextSwitchMetadata, Task, TaskId}; use regex::Regex; use serde::{Deserialize, Serialize}; use serde_json; use std::env; +use std::fmt; use std::io::{Error, ErrorKind}; use std::path::Path; use std::process::Command; @@ -14,10 +14,34 @@ use tokio::sync::Mutex; use tracing::debug; use uuid::Uuid; +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] +pub struct TaskwarriorTaskLocalId(pub u64); + +impl fmt::Display for TaskwarriorTaskLocalId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] +pub struct TaskwarriorTaskId(pub Uuid); + +impl fmt::Display for TaskwarriorTaskId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From<&TaskwarriorTaskId> for TaskId { + fn from(task: &TaskwarriorTaskId) -> Self { + TaskId(task.0) + } +} + #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct TaskwarriorTask { - pub uuid: Uuid, - pub id: u64, + pub uuid: TaskwarriorTaskId, + pub id: TaskwarriorTaskLocalId, #[serde(with = "contextswitch_types::tw_date_format")] pub entry: DateTime, #[serde(with = "contextswitch_types::tw_date_format")] @@ -65,8 +89,7 @@ impl TryFrom<&TaskwarriorTask> for Task { )?; Ok(Task { - uuid: task.uuid, - id: task.id, + id: (&task.uuid).into(), entry: task.entry, modified: task.modified, status: task.status, @@ -146,7 +169,23 @@ pub fn list_tasks(filters: Vec<&str>) -> Result, Error> { } #[tracing::instrument(level = "debug")] -pub async fn add_task(add_args: Vec<&str>) -> Result { +pub fn get_task_by_local_id(id: &TaskwarriorTaskLocalId) -> Result, Error> { + let mut tasks: Vec = list_tasks(vec![&id.to_string()])?; + if tasks.len() > 1 { + return Err(Error::new( + ErrorKind::Other, + format!( + "Found more than 1 task when searching for task with local ID {}", + id + ), + )); + } + + Ok(tasks.pop()) +} + +#[tracing::instrument(level = "debug")] +pub async fn add_task(add_args: Vec<&str>) -> Result { lazy_static! { static ref RE: Regex = Regex::new(r"Created task (?P\d+).").unwrap(); static ref LOCK: Mutex = Mutex::new(0); @@ -173,7 +212,17 @@ pub async fn add_task(add_args: Vec<&str>) -> Result { })? .as_str(); - task_id_str - .parse::() - .map_err(|_| Error::new(ErrorKind::Other, "Cannot parse task ID value")) + let task_id = TaskwarriorTaskLocalId( + task_id_str + .parse::() + .map_err(|_| Error::new(ErrorKind::Other, "Cannot parse task ID value"))?, + ); + + let task = get_task_by_local_id(&task_id)?; + task.ok_or_else(|| { + Error::new( + ErrorKind::Other, + format!("Newly created task with ID {} was not found", task_id), + ) + }) } diff --git a/src/routes/tasks.rs b/src/routes/tasks.rs index f85b2f1..25216c0 100644 --- a/src/routes/tasks.rs +++ b/src/routes/tasks.rs @@ -1,8 +1,7 @@ use crate::contextswitch; use actix_web::{web, HttpResponse}; -use contextswitch_types::TaskDefinition; +use contextswitch_types::{NewTask, Task}; use serde::Deserialize; -use serde_json::json; use std::io::Error; #[derive(Deserialize)] @@ -16,20 +15,20 @@ pub async fn list_tasks(task_query: web::Query) -> Result = contextswitch::list_tasks(filter)?; Ok(HttpResponse::Ok() .content_type("application/json") .body(serde_json::to_string(&tasks)?)) } -#[tracing::instrument(level = "debug", skip_all, fields(definition = %task_definition.definition))] -pub async fn add_task(task_definition: web::Json) -> Result { - let task_id = contextswitch::add_task(task_definition.definition.split(' ').collect()).await?; +#[tracing::instrument(level = "debug", skip_all, fields(definition = %task.definition))] +pub async fn add_task(task: web::Json) -> Result { + let task: Task = contextswitch::add_task(task.definition.split(' ').collect()).await?; Ok(HttpResponse::Ok() .content_type("application/json") - .body(json!({ "id": task_id }).to_string())) + .body(serde_json::to_string(&task)?)) } #[tracing::instrument(level = "debug")] diff --git a/tests/task.rs b/tests/task.rs index b7ec35c..19b0db2 100644 --- a/tests/task.rs +++ b/tests/task.rs @@ -1,21 +1,20 @@ pub mod test_helper; -use contextswitch_api::contextswitch::taskwarrior; -use contextswitch_types::Task; -use contextswitch_types::TaskDefinition; +use contextswitch_api::contextswitch; +use contextswitch_types::{ContextSwitchMetadata, NewTask, Task, TaskId}; use rstest::*; use test_helper::app_address; +use uuid::Uuid; #[rstest] #[tokio::test] async fn list_tasks(app_address: &str) { - let task_id = - taskwarrior::add_task(vec!["test", "list_tasks", "contextswitch:'{\"test\": 1}'"]) - .await - .unwrap(); + let task = contextswitch::add_task(vec!["test", "list_tasks", "contextswitch:'{\"test\": 1}'"]) + .await + .unwrap(); let tasks: Vec = reqwest::Client::new() - .get(&format!("{}/tasks?filter={}", &app_address, task_id)) + .get(&format!("{}/tasks?filter={}", &app_address, task.id)) .send() .await .expect("Failed to execute request") @@ -34,7 +33,7 @@ async fn list_tasks(app_address: &str) { async fn add_task(app_address: &str) { let response: serde_json::Value = reqwest::Client::new() .post(&format!("{}/tasks", &app_address)) - .json(&TaskDefinition { + .json(&NewTask { definition: "test add_task contextswitch:{\"test\":1}".to_string(), }) .send() @@ -43,14 +42,14 @@ async fn add_task(app_address: &str) { .json() .await .expect("Cannot parse JSON result"); - let new_task_id = response["id"].as_u64().unwrap(); - let tasks = taskwarrior::list_tasks(vec![&new_task_id.to_string()]).unwrap(); + let new_task_id = TaskId(Uuid::parse_str(response["id"].as_str().unwrap()).unwrap()); + let tasks = contextswitch::list_tasks(vec![&new_task_id.to_string()]).unwrap(); assert_eq!(tasks.len(), 1); assert_eq!(tasks[0].id, new_task_id); assert_eq!(tasks[0].description, "test add_task"); assert_eq!( tasks[0].contextswitch.as_ref().unwrap(), - &"{\"test\":1}".to_string() + &ContextSwitchMetadata { test: 1 } ); }