From 2c359e7d7a25d6f49e13a5670d04c31b2b5c9db6 Mon Sep 17 00:00:00 2001 From: David Rousselie Date: Thu, 17 Feb 2022 13:06:48 +0100 Subject: [PATCH] Add bookmarks to Contextswitch data --- .pre-commit-config.yaml | 1 + Cargo.lock | 4 +- Cargo.toml | 7 +-- src/contextswitch/api.rs | 14 ++--- src/contextswitch/taskwarrior.rs | 47 ++++++++--------- src/routes/tasks.rs | 2 +- tests/task.rs | 89 +++++++++++++++++++++++++++++--- 7 files changed, 118 insertions(+), 46 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 34d0d19..4c9985f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,4 +4,5 @@ repos: hooks: - id: fmt - id: cargo-check + args: ['--tests'] - id: clippy diff --git a/Cargo.lock b/Cargo.lock index db3b3a1..dc70ee3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,6 +336,7 @@ dependencies = [ "configparser", "contextswitch-types", "dotenv", + "http", "lazy_static", "listenfd", "mktemp", @@ -357,9 +358,10 @@ dependencies = [ [[package]] name = "contextswitch-types" version = "0.1.0" -source = "git+https://github.com/dax/contextswitch-types.git#5f72fdda034db4eb7b22fd1c5d6936f39360880c" +source = "git+https://github.com/dax/contextswitch-types.git#d99bd6e6aebece04a41bdf62f00eaafcb73640ea" dependencies = [ "chrono", + "http", "serde", "serde_json", "uuid", diff --git a/Cargo.toml b/Cargo.toml index 5e6f574..5846aef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,9 +30,10 @@ tracing-log = "0.1.0" tracing-actix-web = "=0.5.0-beta.9" regex = "1.5.0" lazy_static = "1.4.0" -tracing-bunyan-formatter = "0.3.2" -thiserror = "1.0.30" -anyhow = "1.0.53" +tracing-bunyan-formatter = "0.3.0" +thiserror = "1.0" +anyhow = "1.0" +http = "0.2.0" [dev-dependencies] reqwest = { version = "0.11.0", features = ["json"] } diff --git a/src/contextswitch/api.rs b/src/contextswitch/api.rs index c80b6c5..86b3899 100644 --- a/src/contextswitch/api.rs +++ b/src/contextswitch/api.rs @@ -23,11 +23,11 @@ impl std::fmt::Debug for ContextswitchError { #[derive(thiserror::Error)] pub enum ContextswitchError { - #[error("Invalid Contextswitch metadata: {metadata}")] - InvalidMetadataError { + #[error("Invalid Contextswitch data: {data}")] + InvalidDataError { #[source] source: serde_json::Error, - metadata: String, + data: String, }, #[error(transparent)] UnexpectedError(#[from] anyhow::Error), @@ -35,12 +35,12 @@ pub enum ContextswitchError { #[tracing::instrument(level = "debug")] pub fn list_tasks(filters: Vec<&str>) -> Result, ContextswitchError> { - let tasks: Result, ContextswitchError> = taskwarrior::list_tasks(filters) + let tasks: Vec = taskwarrior::list_tasks(filters) .map_err(|e| ContextswitchError::UnexpectedError(e.into()))? .iter() - .map(Task::try_from) + .map(Task::from) .collect(); - tasks + Ok(tasks) } #[tracing::instrument(level = "debug")] @@ -48,5 +48,5 @@ pub async fn add_task(add_args: Vec<&str>) -> Result { let taskwarrior_task = taskwarrior::add_task(add_args) .await .map_err(|e| ContextswitchError::UnexpectedError(e.into()))?; - (&taskwarrior_task).try_into() + Ok((&taskwarrior_task).into()) } diff --git a/src/contextswitch/taskwarrior.rs b/src/contextswitch/taskwarrior.rs index 08172ce..83adaf8 100644 --- a/src/contextswitch/taskwarrior.rs +++ b/src/contextswitch/taskwarrior.rs @@ -1,8 +1,7 @@ -use crate::contextswitch::ContextswitchError; use anyhow::{anyhow, Context}; use chrono::{DateTime, Utc}; use configparser::ini::Ini; -use contextswitch_types::{ContextSwitchMetadata, Task, TaskId}; +use contextswitch_types::{ContextswitchData, Task, TaskId}; use regex::Regex; use serde::{Deserialize, Serialize}; use serde_json; @@ -12,7 +11,7 @@ use std::path::Path; use std::process::Command; use std::str; use tokio::sync::Mutex; -use tracing::debug; +use tracing::{debug, warn}; use uuid::Uuid; #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] @@ -88,27 +87,23 @@ pub struct TaskwarriorTask { pub contextswitch: Option, } -impl TryFrom<&TaskwarriorTask> for Task { - type Error = ContextswitchError; +impl From<&TaskwarriorTask> for Task { + fn from(task: &TaskwarriorTask) -> Self { + let cs_data = + task.contextswitch + .as_ref() + .and_then(|cs_string| -> Option { + let contextswitch_data_result = serde_json::from_str(cs_string); + if contextswitch_data_result.is_err() { + warn!( + "Invalid Contextswitch data found in {}: {}", + &task.uuid, cs_string + ); + } + contextswitch_data_result.ok() + }); - fn try_from(task: &TaskwarriorTask) -> Result { - let cs_metadata = task.contextswitch.as_ref().map_or( - Ok(None), - |cs_string| -> Result, ContextswitchError> { - if cs_string.is_empty() || cs_string == "{}" { - Ok(None) - } else { - Some(serde_json::from_str(cs_string)) - .transpose() - .map_err(|e| ContextswitchError::InvalidMetadataError { - source: e, - metadata: cs_string.to_string(), - }) - } - }, - )?; - - Ok(Task { + Task { id: (&task.uuid).into(), entry: task.entry, modified: task.modified, @@ -124,8 +119,8 @@ impl TryFrom<&TaskwarriorTask> for Task { priority: task.priority, recur: task.recur, tags: task.tags.clone(), - contextswitch: cs_metadata, - }) + contextswitch: cs_data, + } } } @@ -150,7 +145,7 @@ fn write_default_config(data_location: &str) -> String { taskrc.setstr( "default", "uda.contextswitch.label", - Some("Context Switch metadata"), + Some("Contextswitch data"), ); taskrc.setstr("default", "uda.contextswitch.default", Some("{}")); diff --git a/src/routes/tasks.rs b/src/routes/tasks.rs index d2da379..ad728f7 100644 --- a/src/routes/tasks.rs +++ b/src/routes/tasks.rs @@ -12,7 +12,7 @@ pub struct TaskQuery { impl ResponseError for contextswitch::ContextswitchError { fn status_code(&self) -> StatusCode { match self { - contextswitch::ContextswitchError::InvalidMetadataError { .. } => { + contextswitch::ContextswitchError::InvalidDataError { .. } => { StatusCode::INTERNAL_SERVER_ERROR } contextswitch::ContextswitchError::UnexpectedError(_) => { diff --git a/tests/task.rs b/tests/task.rs index 19b0db2..5dcd6c6 100644 --- a/tests/task.rs +++ b/tests/task.rs @@ -1,7 +1,8 @@ pub mod test_helper; use contextswitch_api::contextswitch; -use contextswitch_types::{ContextSwitchMetadata, NewTask, Task, TaskId}; +use contextswitch_types::{Bookmark, ContextswitchData, NewTask, Task, TaskId}; +use http::uri::Uri; use rstest::*; use test_helper::app_address; use uuid::Uuid; @@ -9,9 +10,13 @@ use uuid::Uuid; #[rstest] #[tokio::test] async fn list_tasks(app_address: &str) { - let task = contextswitch::add_task(vec!["test", "list_tasks", "contextswitch:'{\"test\": 1}'"]) - .await - .unwrap(); + let task = contextswitch::add_task(vec![ + "test", + "list_tasks", + "contextswitch:'{\"bookmarks\":[{\"uri\":\"https://example.com/path?filter=1\"}]}'", + ]) + .await + .unwrap(); let tasks: Vec = reqwest::Client::new() .get(&format!("{}/tasks?filter={}", &app_address, task.id)) @@ -24,8 +29,69 @@ async fn list_tasks(app_address: &str) { assert_eq!(tasks.len(), 1); assert_eq!(tasks[0].description, "test list_tasks"); - let cs_metadata = tasks[0].contextswitch.as_ref().unwrap(); - assert_eq!(cs_metadata.test, 1); + let cs_data = tasks[0].contextswitch.as_ref().unwrap(); + assert_eq!(cs_data.bookmarks.len(), 1); + assert_eq!(cs_data.bookmarks[0].content, None); + assert_eq!( + cs_data.bookmarks[0].uri, + "https://example.com/path?filter=1".parse::().unwrap() + ); +} + +#[rstest] +#[tokio::test] +async fn list_tasks_with_unknown_contextswitch_data(app_address: &str) { + let task = contextswitch::add_task(vec![ + "test", + "list_tasks_with_unknown_contextswitch_data", + "contextswitch:'{\"unknown\": 1}'", + ]) + .await + .unwrap(); + + let tasks: Vec = reqwest::Client::new() + .get(&format!("{}/tasks?filter={}", &app_address, task.id)) + .send() + .await + .expect("Failed to execute request") + .json() + .await + .expect("Cannot parse JSON result"); + + assert_eq!(tasks.len(), 1); + assert_eq!( + tasks[0].description, + "test list_tasks_with_unknown_contextswitch_data" + ); + assert!(tasks[0].contextswitch.is_none()); +} + +#[rstest] +#[tokio::test] +async fn list_tasks_with_invalid_contextswitch_data(app_address: &str) { + let task = contextswitch::add_task(vec![ + "test", + "list_tasks_with_invalid_contextswitch_data", + "contextswitch:'}'", + ]) + .await + .unwrap(); + + let tasks: Vec = reqwest::Client::new() + .get(&format!("{}/tasks?filter={}", &app_address, task.id)) + .send() + .await + .expect("Failed to execute request") + .json() + .await + .expect("Cannot parse JSON result"); + + assert_eq!(tasks.len(), 1); + assert_eq!( + tasks[0].description, + "test list_tasks_with_invalid_contextswitch_data" + ); + assert!(tasks[0].contextswitch.is_none()); } #[rstest] @@ -34,7 +100,9 @@ async fn add_task(app_address: &str) { let response: serde_json::Value = reqwest::Client::new() .post(&format!("{}/tasks", &app_address)) .json(&NewTask { - definition: "test add_task contextswitch:{\"test\":1}".to_string(), + definition: + "test add_task contextswitch:{\"bookmarks\":[{\"uri\":\"https://example.com/path?filter=1\"}]}" + .to_string(), }) .send() .await @@ -50,6 +118,11 @@ async fn add_task(app_address: &str) { assert_eq!(tasks[0].description, "test add_task"); assert_eq!( tasks[0].contextswitch.as_ref().unwrap(), - &ContextSwitchMetadata { test: 1 } + &ContextswitchData { + bookmarks: vec![Bookmark { + uri: "https://example.com/path?filter=1".parse::().unwrap(), + content: None + }] + } ); }