Return new task after adding a new one

This commit is contained in:
2022-01-27 14:07:15 +01:00
parent 7a3b5aa6a3
commit ffdabe0b6c
5 changed files with 80 additions and 32 deletions

2
Cargo.lock generated
View File

@@ -349,7 +349,7 @@ dependencies = [
[[package]] [[package]]
name = "contextswitch-types" name = "contextswitch-types"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/dax/contextswitch-types.git#e26c93cdf8c3ef4c4c82b3d37c4646dcee616b53" source = "git+https://github.com/dax/contextswitch-types.git#cc6db5cc18ab9d67998065004e912756d0a81e28"
dependencies = [ dependencies = [
"chrono", "chrono",
"serde", "serde",

View File

@@ -12,6 +12,7 @@ pub fn list_tasks(filters: Vec<&str>) -> Result<Vec<Task>, Error> {
} }
#[tracing::instrument(level = "debug")] #[tracing::instrument(level = "debug")]
pub async fn add_task(add_args: Vec<&str>) -> Result<u64, Error> { pub async fn add_task(add_args: Vec<&str>) -> Result<Task, Error> {
taskwarrior::add_task(add_args).await let taskwarrior_task = taskwarrior::add_task(add_args).await?;
(&taskwarrior_task).try_into()
} }

View File

@@ -1,11 +1,11 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use configparser::ini::Ini; use configparser::ini::Ini;
use contextswitch_types::ContextSwitchMetadata; use contextswitch_types::{ContextSwitchMetadata, Task, TaskId};
use contextswitch_types::Task;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json; use serde_json;
use std::env; use std::env;
use std::fmt;
use std::io::{Error, ErrorKind}; use std::io::{Error, ErrorKind};
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
@@ -14,10 +14,34 @@ use tokio::sync::Mutex;
use tracing::debug; use tracing::debug;
use uuid::Uuid; 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)] #[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct TaskwarriorTask { pub struct TaskwarriorTask {
pub uuid: Uuid, pub uuid: TaskwarriorTaskId,
pub id: u64, pub id: TaskwarriorTaskLocalId,
#[serde(with = "contextswitch_types::tw_date_format")] #[serde(with = "contextswitch_types::tw_date_format")]
pub entry: DateTime<Utc>, pub entry: DateTime<Utc>,
#[serde(with = "contextswitch_types::tw_date_format")] #[serde(with = "contextswitch_types::tw_date_format")]
@@ -65,8 +89,7 @@ impl TryFrom<&TaskwarriorTask> for Task {
)?; )?;
Ok(Task { Ok(Task {
uuid: task.uuid, id: (&task.uuid).into(),
id: task.id,
entry: task.entry, entry: task.entry,
modified: task.modified, modified: task.modified,
status: task.status, status: task.status,
@@ -146,7 +169,23 @@ pub fn list_tasks(filters: Vec<&str>) -> Result<Vec<TaskwarriorTask>, Error> {
} }
#[tracing::instrument(level = "debug")] #[tracing::instrument(level = "debug")]
pub async fn add_task(add_args: Vec<&str>) -> Result<u64, Error> { pub fn get_task_by_local_id(id: &TaskwarriorTaskLocalId) -> Result<Option<TaskwarriorTask>, Error> {
let mut tasks: Vec<TaskwarriorTask> = 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<TaskwarriorTask, Error> {
lazy_static! { lazy_static! {
static ref RE: Regex = Regex::new(r"Created task (?P<id>\d+).").unwrap(); static ref RE: Regex = Regex::new(r"Created task (?P<id>\d+).").unwrap();
static ref LOCK: Mutex<u32> = Mutex::new(0); static ref LOCK: Mutex<u32> = Mutex::new(0);
@@ -173,7 +212,17 @@ pub async fn add_task(add_args: Vec<&str>) -> Result<u64, Error> {
})? })?
.as_str(); .as_str();
task_id_str let task_id = TaskwarriorTaskLocalId(
.parse::<u64>() task_id_str
.map_err(|_| Error::new(ErrorKind::Other, "Cannot parse task ID value")) .parse::<u64>()
.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),
)
})
} }

View File

@@ -1,8 +1,7 @@
use crate::contextswitch; use crate::contextswitch;
use actix_web::{web, HttpResponse}; use actix_web::{web, HttpResponse};
use contextswitch_types::TaskDefinition; use contextswitch_types::{NewTask, Task};
use serde::Deserialize; use serde::Deserialize;
use serde_json::json;
use std::io::Error; use std::io::Error;
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -16,20 +15,20 @@ pub async fn list_tasks(task_query: web::Query<TaskQuery>) -> Result<HttpRespons
.filter .filter
.as_ref() .as_ref()
.map_or(vec![], |filter| filter.split(' ').collect()); .map_or(vec![], |filter| filter.split(' ').collect());
let tasks = contextswitch::list_tasks(filter)?; let tasks: Vec<Task> = contextswitch::list_tasks(filter)?;
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(serde_json::to_string(&tasks)?)) .body(serde_json::to_string(&tasks)?))
} }
#[tracing::instrument(level = "debug", skip_all, fields(definition = %task_definition.definition))] #[tracing::instrument(level = "debug", skip_all, fields(definition = %task.definition))]
pub async fn add_task(task_definition: web::Json<TaskDefinition>) -> Result<HttpResponse, Error> { pub async fn add_task(task: web::Json<NewTask>) -> Result<HttpResponse, Error> {
let task_id = contextswitch::add_task(task_definition.definition.split(' ').collect()).await?; let task: Task = contextswitch::add_task(task.definition.split(' ').collect()).await?;
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(json!({ "id": task_id }).to_string())) .body(serde_json::to_string(&task)?))
} }
#[tracing::instrument(level = "debug")] #[tracing::instrument(level = "debug")]

View File

@@ -1,21 +1,20 @@
pub mod test_helper; pub mod test_helper;
use contextswitch_api::contextswitch::taskwarrior; use contextswitch_api::contextswitch;
use contextswitch_types::Task; use contextswitch_types::{ContextSwitchMetadata, NewTask, Task, TaskId};
use contextswitch_types::TaskDefinition;
use rstest::*; use rstest::*;
use test_helper::app_address; use test_helper::app_address;
use uuid::Uuid;
#[rstest] #[rstest]
#[tokio::test] #[tokio::test]
async fn list_tasks(app_address: &str) { async fn list_tasks(app_address: &str) {
let task_id = let task = contextswitch::add_task(vec!["test", "list_tasks", "contextswitch:'{\"test\": 1}'"])
taskwarrior::add_task(vec!["test", "list_tasks", "contextswitch:'{\"test\": 1}'"]) .await
.await .unwrap();
.unwrap();
let tasks: Vec<Task> = reqwest::Client::new() let tasks: Vec<Task> = reqwest::Client::new()
.get(&format!("{}/tasks?filter={}", &app_address, task_id)) .get(&format!("{}/tasks?filter={}", &app_address, task.id))
.send() .send()
.await .await
.expect("Failed to execute request") .expect("Failed to execute request")
@@ -34,7 +33,7 @@ async fn list_tasks(app_address: &str) {
async fn add_task(app_address: &str) { async fn add_task(app_address: &str) {
let response: serde_json::Value = reqwest::Client::new() let response: serde_json::Value = reqwest::Client::new()
.post(&format!("{}/tasks", &app_address)) .post(&format!("{}/tasks", &app_address))
.json(&TaskDefinition { .json(&NewTask {
definition: "test add_task contextswitch:{\"test\":1}".to_string(), definition: "test add_task contextswitch:{\"test\":1}".to_string(),
}) })
.send() .send()
@@ -43,14 +42,14 @@ async fn add_task(app_address: &str) {
.json() .json()
.await .await
.expect("Cannot parse JSON result"); .expect("Cannot parse JSON result");
let new_task_id = response["id"].as_u64().unwrap(); let new_task_id = TaskId(Uuid::parse_str(response["id"].as_str().unwrap()).unwrap());
let tasks = taskwarrior::list_tasks(vec![&new_task_id.to_string()]).unwrap(); let tasks = contextswitch::list_tasks(vec![&new_task_id.to_string()]).unwrap();
assert_eq!(tasks.len(), 1); assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].id, new_task_id); assert_eq!(tasks[0].id, new_task_id);
assert_eq!(tasks[0].description, "test add_task"); assert_eq!(tasks[0].description, "test add_task");
assert_eq!( assert_eq!(
tasks[0].contextswitch.as_ref().unwrap(), tasks[0].contextswitch.as_ref().unwrap(),
&"{\"test\":1}".to_string() &ContextSwitchMetadata { test: 1 }
); );
} }