From 2eea9daffdbed7cedf0834bc2cdd5ecf4574b5f9 Mon Sep 17 00:00:00 2001 From: parazyd Date: Mon, 21 Aug 2023 13:31:36 +0200 Subject: [PATCH] taud: Finish new RPC port. --- Cargo.lock | 3 +- bin/tau/tau-cli/Cargo.toml | 5 +- bin/tau/tau-cli/src/filter.rs | 34 +-- bin/tau/tau-cli/src/main.rs | 4 +- bin/tau/tau-cli/src/primitives.rs | 115 +--------- bin/tau/tau-cli/src/rpc.rs | 141 ++++++++---- bin/tau/tau-cli/src/view.rs | 17 +- bin/tau/taud/src/error.rs | 2 +- bin/tau/taud/src/jsonrpc.rs | 356 ++++++++++++++++++------------ bin/tau/taud/src/main.rs | 17 +- bin/tau/taud/src/task_info.rs | 88 +++++++- src/rpc/jsonrpc.rs | 10 +- 12 files changed, 466 insertions(+), 326 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f465b7f6..6bc063c87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5952,10 +5952,11 @@ dependencies = [ "log", "prettytable-rs", "serde", - "serde_json", "simplelog", + "taud", "term_grid", "textwrap 0.16.0", + "tinyjson", "url", ] diff --git a/bin/tau/tau-cli/Cargo.toml b/bin/tau/tau-cli/Cargo.toml index 19eefe65a..7d28329af 100644 --- a/bin/tau/tau-cli/Cargo.toml +++ b/bin/tau/tau-cli/Cargo.toml @@ -10,7 +10,8 @@ repository = "https://github.com/darkrenaissance/darkfi" [dependencies] libc = "0.2.147" -darkfi = { path = "../../../", features = ["rpc"]} +darkfi = {path = "../../../", features = ["rpc"]} +taud = {path = "../taud"} # Async async-std = {version = "1.12.0", features = ["attributes"]} @@ -29,4 +30,4 @@ url = "2.4.0" # Encoding and parsing clap = {version = "4.3.22", features = ["derive"]} serde = {version = "1.0.183", features = ["derive"]} -serde_json = "1.0.105" +tinyjson = "2.5.1" diff --git a/bin/tau/tau-cli/src/filter.rs b/bin/tau/tau-cli/src/filter.rs index fefabd6c6..08331f92e 100644 --- a/bin/tau/tau-cli/src/filter.rs +++ b/bin/tau/tau-cli/src/filter.rs @@ -23,7 +23,6 @@ use std::{ use chrono::{Datelike, Local, TimeZone, Utc}; use log::error; -use serde_json::Value; use darkfi::Result; @@ -38,7 +37,8 @@ pub fn apply_filter(tasks: &mut Vec, filter: &str) { // Filter by state. _ if filter.contains("state:") => { let kv: Vec<&str> = filter.split(':').collect(); - if let Some(state) = Value::from(kv[1]).as_str() { + //if let Some(state) = Value::from(kv[1]).as_str() { + if let Some(state) = Some(kv[1]) { match state { "open" => tasks.retain(|task| task.state == State::Open.to_string()), "start" => tasks.retain(|task| task.state == State::Start.to_string()), @@ -59,7 +59,8 @@ pub fn apply_filter(tasks: &mut Vec, filter: &str) { _ if filter.contains("month:") => { let kv: Vec<&str> = filter.split(':').collect(); if kv.len() == 2 { - if let Some(value) = Value::from(kv[1]).as_str() { + //if let Some(value) = Value::from(kv[1]).as_str() { + if let Some(value) = Some(kv[1]) { if value.len() != 4 || value.parse::().is_err() { error!( "Please provide month date as \"MMYY\" (e.g. 0922 for September 2022)" @@ -71,7 +72,7 @@ pub fn apply_filter(tasks: &mut Vec, filter: &str) { let year = year + (Utc::now().year() / 100) * 100; tasks.retain(|task| { - let date = task.created_at; + let date = task.created_at.0; let task_date = Utc.timestamp_nanos(date.try_into().unwrap()).date_naive(); task_date.month() == month && task_date.year() == year }) @@ -89,7 +90,8 @@ pub fn apply_filter(tasks: &mut Vec, filter: &str) { _ if filter.contains("project:") => { let kv: Vec<&str> = filter.split(':').collect(); if kv.len() == 2 { - if let Some(value) = Value::from(kv[1]).as_str() { + // OLD: if let Some(value) = Value::from(kv[1]).as_str() { + if let Some(value) = Some(kv[1]) { if value.is_empty() { tasks.retain(|task| task.project.is_empty()) } else { @@ -128,7 +130,9 @@ pub fn apply_filter(tasks: &mut Vec, filter: &str) { }; if kv.len() == 2 { - if let Some(value) = Value::from(kv[1]).as_str() { + // OLD: let Value::from(kv[1]).as_str(); + let value = Some(kv[1]); + if let Some(value) = value { if value.is_empty() { tasks.retain(|task| task.due.is_none()) } else { @@ -140,7 +144,7 @@ pub fn apply_filter(tasks: &mut Vec, filter: &str) { }; tasks.retain(|task| { - let date = task.due.unwrap_or(0); + let date = if let Some(due) = task.due { due.0 } else { 0 }; let task_date = Utc.timestamp_nanos(date.try_into().unwrap()).date_naive(); @@ -181,25 +185,25 @@ pub fn no_filter_warn() { } } -pub fn get_ids(filters: &mut Vec) -> Result> { +pub fn get_ids(filters: &mut Vec) -> Result> { let mut vec_ids = vec![]; let mut matching_id = String::new(); if let Some(index) = filters.iter().position(|t| { - t.parse::().is_ok() || !t.contains(':') && (t.contains(',') || t.contains('-')) + t.parse::().is_ok() || !t.contains(':') && (t.contains(',') || t.contains('-')) }) { matching_id.push_str(&filters.remove(index)); } match matching_id { - _ if matching_id.parse::().is_ok() => { - let id = matching_id.parse::().unwrap(); + _ if matching_id.parse::().is_ok() => { + let id = matching_id.parse::().unwrap(); vec_ids.push(id) } _ if !matching_id.contains(':') && (matching_id.contains(',') || matching_id.contains('-')) => { let num = matching_id.replace(&[',', '-'][..], ""); - if num.parse::().is_err() { + if num.parse::().is_err() { error!("Invalid ID number"); exit(1) } @@ -209,17 +213,17 @@ pub fn get_ids(filters: &mut Vec) -> Result> { if id.contains('-') { let range: Vec<&str> = id.split('-').collect(); let range = - range[0].parse::().unwrap()..=range[1].parse::().unwrap(); + range[0].parse::().unwrap()..=range[1].parse::().unwrap(); for rid in range { vec_ids.push(rid) } } else { - vec_ids.push(id.parse::().unwrap()) + vec_ids.push(id.parse::().unwrap()) } } } else if matching_id.contains('-') { let range: Vec<&str> = matching_id.split('-').collect(); - let range = range[0].parse::().unwrap()..=range[1].parse::().unwrap(); + let range = range[0].parse::().unwrap()..=range[1].parse::().unwrap(); for rid in range { vec_ids.push(rid) } diff --git a/bin/tau/tau-cli/src/main.rs b/bin/tau/tau-cli/src/main.rs index 27e4505c5..e27882224 100644 --- a/bin/tau/tau-cli/src/main.rs +++ b/bin/tau/tau-cli/src/main.rs @@ -42,7 +42,7 @@ use primitives::{task_from_cli, State, TaskEvent}; use util::{due_as_timestamp, prompt_text}; use view::{print_task_info, print_task_list}; -use crate::primitives::TaskInfo; +use taud::task_info::TaskInfo; const DEFAULT_PATH: &str = "~/tau_exported_tasks"; @@ -375,7 +375,7 @@ async fn main() -> Result<()> { Some(date) => { let ts = to_naivedate(date.clone())?.and_hms_opt(12, 0, 0).unwrap().timestamp(); - let tasks = tau.get_stop_tasks(Some(ts)).await?; + let tasks = tau.get_stop_tasks(Some(ts.try_into().unwrap())).await?; drawdown(date, tasks, assignee)?; } None => { diff --git a/bin/tau/tau-cli/src/primitives.rs b/bin/tau/tau-cli/src/primitives.rs index 29a4c8808..d88dc07a6 100644 --- a/bin/tau/tau-cli/src/primitives.rs +++ b/bin/tau/tau-cli/src/primitives.rs @@ -16,58 +16,12 @@ * along with this program. If not, see . */ -use std::{fmt, str::FromStr}; - -use darkfi::{util::time::Timestamp, Error, Result}; +use darkfi::{util::time::Timestamp, Result}; use crate::due_as_timestamp; +pub(crate) use taud::task_info::{State, TaskEvent, TaskInfo}; -pub enum State { - Open, - Start, - Pause, - Stop, -} - -impl State { - pub const fn is_start(&self) -> bool { - matches!(*self, Self::Start) - } - pub const fn is_pause(&self) -> bool { - matches!(*self, Self::Pause) - } - pub const fn is_stop(&self) -> bool { - matches!(*self, Self::Stop) - } -} - -impl fmt::Display for State { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - State::Open => write!(f, "open"), - State::Start => write!(f, "start"), - State::Stop => write!(f, "stop"), - State::Pause => write!(f, "pause"), - } - } -} - -impl FromStr for State { - type Err = Error; - - fn from_str(s: &str) -> std::result::Result { - let result = match s.to_lowercase().as_str() { - "open" => State::Open, - "stop" => State::Stop, - "start" => State::Start, - "pause" => State::Pause, - _ => return Err(Error::ParseFailed("unable to parse state")), - }; - Ok(result) - } -} - -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug)] pub struct BaseTask { pub title: String, pub tags: Vec, @@ -78,27 +32,10 @@ pub struct BaseTask { pub rank: Option, } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct TaskInfo { - pub ref_id: String, - pub workspace: String, - pub id: u32, - pub title: String, - pub tags: Vec, - pub desc: String, - pub owner: String, - pub assign: Vec, - pub project: Vec, - pub due: Option, - pub rank: Option, - pub created_at: u64, - pub state: String, - pub events: Vec, - pub comments: Vec, -} - impl From for TaskInfo { fn from(value: BaseTask) -> Self { + let due = if let Some(vd) = value.due { Some(Timestamp(vd)) } else { None }; + Self { ref_id: String::default(), workspace: String::default(), @@ -109,9 +46,9 @@ impl From for TaskInfo { owner: String::default(), assign: value.assign, project: value.project, - due: value.due, + due, rank: value.rank, - created_at: u64::default(), + created_at: Timestamp(u64::default()), state: String::default(), events: vec![], comments: vec![], @@ -119,44 +56,6 @@ impl From for TaskInfo { } } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct TaskEvent { - pub action: String, - pub author: String, - pub content: String, - pub timestamp: Timestamp, -} - -impl std::fmt::Display for TaskEvent { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "action: {}, timestamp: {}", self.action, self.timestamp) - } -} - -impl Default for TaskEvent { - fn default() -> Self { - Self { - action: State::Open.to_string(), - author: "".to_string(), - content: "".to_string(), - timestamp: Timestamp::current_time(), - } - } -} - -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct Comment { - content: String, - author: String, - timestamp: Timestamp, -} - -impl std::fmt::Display for Comment { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{} author: {}, content: {} ", self.timestamp, self.author, self.content) - } -} - pub fn task_from_cli(values: Vec) -> Result { let mut title = String::new(); let mut tags = vec![]; diff --git a/bin/tau/tau-cli/src/rpc.rs b/bin/tau/tau-cli/src/rpc.rs index b22b3827b..d23b5f91e 100644 --- a/bin/tau/tau-cli/src/rpc.rs +++ b/bin/tau/tau-cli/src/rpc.rs @@ -16,10 +16,9 @@ * along with this program. If not, see . */ -use log::debug; -use serde_json::{from_value, json}; - use darkfi::{rpc::jsonrpc::JsonRequest, Result}; +use log::debug; +use tinyjson::JsonValue; use crate::{ primitives::{BaseTask, State, TaskInfo}, @@ -33,108 +32,172 @@ impl Tau { /// Add a new task. pub async fn add(&self, task: BaseTask) -> Result { - let req = JsonRequest::new("add", json!([task])); + let mut params = vec![]; + params.push(JsonValue::String(task.title.clone())); + params.push(JsonValue::Array( + task.tags.iter().map(|x| JsonValue::String(x.clone())).collect(), + )); + params.push(JsonValue::String(task.desc.unwrap_or("".to_string()))); + params.push(JsonValue::Array( + task.assign.iter().map(|x| JsonValue::String(x.clone())).collect(), + )); + params.push(JsonValue::Array( + task.project.iter().map(|x| JsonValue::String(x.clone())).collect(), + )); + + let due = if let Some(num) = task.due { + JsonValue::String(num.to_string()) + } else { + JsonValue::Null + }; + params.push(due); + + let rank = + if let Some(num) = task.rank { JsonValue::Number(num.into()) } else { JsonValue::Null }; + params.push(rank); + + let req = JsonRequest::new("add", params); let rep = self.rpc_client.request(req).await?; debug!("Got reply: {:?}", rep); - let reply: u32 = from_value(rep)?; - Ok(reply) + Ok(*rep.get::().unwrap() as u32) } /// Get current open tasks ids. - pub async fn get_ids(&self) -> Result> { - let req = JsonRequest::new("get_ids", json!([])); + pub async fn get_ids(&self) -> Result> { + let req = JsonRequest::new("get_ids", vec![]); let rep = self.rpc_client.request(req).await?; + debug!("Got reply: {:?}", rep); + let mut ret = vec![]; - for i in rep.as_array().unwrap() { - ret.push(i.as_u64().unwrap()); + for i in rep.get::>().unwrap() { + ret.push(*i.get::().unwrap() as u32) } Ok(ret) } /// Update existing task given it's ID and some params. - pub async fn update(&self, id: u64, task: BaseTask) -> Result { - let req = JsonRequest::new("update", json!([id, task])); + pub async fn update(&self, id: u32, task: BaseTask) -> Result { + let mut params = vec![]; + params.push(JsonValue::String(task.title.clone())); + params.push(JsonValue::Array( + task.tags.iter().map(|x| JsonValue::String(x.clone())).collect(), + )); + params.push(JsonValue::String(task.desc.unwrap_or("".to_string()))); + params.push(JsonValue::Array( + task.assign.iter().map(|x| JsonValue::String(x.clone())).collect(), + )); + params.push(JsonValue::Array( + task.project.iter().map(|x| JsonValue::String(x.clone())).collect(), + )); + + let due = if let Some(num) = task.due { + JsonValue::String(num.to_string()) + } else { + JsonValue::Null + }; + params.push(due); + + let rank = + if let Some(num) = task.rank { JsonValue::Number(num.into()) } else { JsonValue::Null }; + params.push(rank); + + let req = JsonRequest::new( + "update", + vec![JsonValue::Number(id.into()), JsonValue::Array(params)], + ); let rep = self.rpc_client.request(req).await?; debug!("Got reply: {:?}", rep); - let reply: bool = from_value(rep)?; - Ok(reply) + Ok(*rep.get::().unwrap()) } /// Set the state for a task. - pub async fn set_state(&self, id: u64, state: &State) -> Result { - let req = JsonRequest::new("set_state", json!([id, state.to_string()])); + pub async fn set_state(&self, id: u32, state: &State) -> Result { + let req = JsonRequest::new( + "set_state", + vec![JsonValue::Number(id.into()), JsonValue::String(state.to_string())], + ); let rep = self.rpc_client.request(req).await?; debug!("Got reply: {:?}", rep); - let reply: bool = from_value(rep)?; - Ok(reply) + Ok(*rep.get::().unwrap()) } /// Set a comment for a task. - pub async fn set_comment(&self, id: u64, content: &str) -> Result { - let req = JsonRequest::new("set_comment", json!([id, content])); + pub async fn set_comment(&self, id: u32, content: &str) -> Result { + let req = JsonRequest::new( + "set_comment", + vec![JsonValue::Number(id.into()), JsonValue::String(content.to_string())], + ); let rep = self.rpc_client.request(req).await?; debug!("Got reply: {:?}", rep); - let reply: bool = from_value(rep)?; - Ok(reply) + Ok(*rep.get::().unwrap()) } /// Get task data by its ID. - pub async fn get_task_by_id(&self, id: u64) -> Result { - let req = JsonRequest::new("get_task_by_id", json!([id])); + pub async fn get_task_by_id(&self, id: u32) -> Result { + let req = JsonRequest::new("get_task_by_id", vec![JsonValue::Number(id.into())]); let rep = self.rpc_client.request(req).await?; - Ok(serde_json::from_value(rep)?) + debug!("Got reply: {:?}", rep); + let rep = rep.into(); + Ok(rep) } /// Get month's stopped tasks. - pub async fn get_stop_tasks(&self, month: Option) -> Result> { - let req = JsonRequest::new("get_stop_tasks", json!([month])); + pub async fn get_stop_tasks(&self, month: Option) -> Result> { + let param = if let Some(month) = month { + JsonValue::String(month.to_string()) + } else { + JsonValue::Null + }; + + let req = JsonRequest::new("get_stop_tasks", vec![param]); let rep = self.rpc_client.request(req).await?; - Ok(serde_json::from_value(rep)?) + debug!("Got reply: {:?}", rep); + let rep = + rep.get::>().unwrap().iter().map(|x| (*x).clone().into()).collect(); + Ok(rep) } /// Switch workspace. pub async fn switch_ws(&self, workspace: String) -> Result { - let req = JsonRequest::new("switch_ws", json!([workspace])); + let req = JsonRequest::new("switch_ws", vec![JsonValue::String(workspace)]); let rep = self.rpc_client.request(req).await?; debug!("Got reply: {:?}", rep); - let reply: bool = from_value(rep)?; - Ok(reply) + Ok(*rep.get::().unwrap()) } /// Get current workspace. pub async fn get_ws(&self) -> Result { - let req = JsonRequest::new("get_ws", json!([])); + let req = JsonRequest::new("get_ws", vec![]); let rep = self.rpc_client.request(req).await?; - Ok(serde_json::from_value(rep)?) + debug!("Got reply: {:?}", rep); + Ok(rep.get::().unwrap().clone()) } /// Export tasks. pub async fn export_to(&self, path: String) -> Result { - let req = JsonRequest::new("export", json!([path])); + let req = JsonRequest::new("export", vec![JsonValue::String(path)]); let rep = self.rpc_client.request(req).await?; debug!("Got reply: {:?}", rep); - - Ok(serde_json::from_value(rep)?) + Ok(*rep.get::().unwrap()) } /// Import tasks. pub async fn import_from(&self, path: String) -> Result { - let req = JsonRequest::new("import", json!([path])); + let req = JsonRequest::new("import", vec![JsonValue::String(path)]); let rep = self.rpc_client.request(req).await?; debug!("Got reply: {:?}", rep); - - Ok(serde_json::from_value(rep)?) + Ok(*rep.get::().unwrap()) } } diff --git a/bin/tau/tau-cli/src/view.rs b/bin/tau/tau-cli/src/view.rs index 6caf47639..59898ce98 100644 --- a/bin/tau/tau-cli/src/view.rs +++ b/bin/tau/tau-cli/src/view.rs @@ -90,14 +90,18 @@ pub fn print_task_list(tasks: Vec, ws: String) -> Result<()> { print_tags.push(t) } + let due_ = match task.due { + Some(ts) => ts.0, + None => 0, + }; + table.add_row(Row::new(vec![ Cell::new(&task.id.to_string()).style_spec(gen_style), Cell::new(&task.title).style_spec(gen_style), Cell::new(&print_tags.join(", ")).style_spec(gen_style), Cell::new(&task.project.join(", ")).style_spec(gen_style), Cell::new(&task.assign.join(", ")).style_spec(gen_style), - Cell::new(×tamp_to_date(task.due.unwrap_or(0), DateFormat::Date)) - .style_spec(gen_style), + Cell::new(×tamp_to_date(due_, DateFormat::Date)).style_spec(gen_style), if task.rank == max_rank { Cell::new(&rank).style_spec(max_style) } else if task.rank == min_rank { @@ -133,8 +137,13 @@ pub fn print_task_list(tasks: Vec, ws: String) -> Result<()> { } pub fn taskinfo_table(taskinfo: TaskInfo) -> Result { - let due = timestamp_to_date(taskinfo.due.unwrap_or(0), DateFormat::Date); - let created_at = timestamp_to_date(taskinfo.created_at, DateFormat::DateTime); + let due_ = match taskinfo.due { + Some(ts) => ts.0, + None => 0, + }; + + let due = timestamp_to_date(due_, DateFormat::Date); + let created_at = timestamp_to_date(taskinfo.created_at.0, DateFormat::DateTime); let rank = if let Some(r) = taskinfo.rank { r.to_string() } else { "".to_string() }; let mut table = table!( diff --git a/bin/tau/taud/src/error.rs b/bin/tau/taud/src/error.rs index 568cd9f0f..1937eb874 100644 --- a/bin/tau/taud/src/error.rs +++ b/bin/tau/taud/src/error.rs @@ -60,7 +60,7 @@ pub fn to_json_result(res: TaudResult, id: u16) -> JsonResult { TaudError::InvalidId => { JsonError::new(ErrorCode::InvalidParams, Some("invalid task id".into()), id).into() } - TaudError::InvalidData(e) | TaudError::SerdeJsonError(e) => { + TaudError::InvalidData(e) | TaudError::JsonError(e) => { JsonError::new(ErrorCode::InvalidParams, Some(e), id).into() } TaudError::InvalidDueTime => { diff --git a/bin/tau/taud/src/jsonrpc.rs b/bin/tau/taud/src/jsonrpc.rs index d7da9f159..c126fcb8b 100644 --- a/bin/tau/taud/src/jsonrpc.rs +++ b/bin/tau/taud/src/jsonrpc.rs @@ -22,8 +22,7 @@ use async_std::sync::Mutex; use async_trait::async_trait; use crypto_box::ChaChaBox; use log::{debug, warn}; -use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +use tinyjson::JsonValue; use darkfi::{ net, @@ -51,43 +50,25 @@ pub struct JsonRpcInterface { p2p: net::P2pPtr, } -#[derive(Clone, Debug, Serialize, Deserialize)] -struct BaseTaskInfo { - title: String, - tags: Vec, - desc: String, - assign: Vec, - project: Vec, - due: Option, - rank: Option, -} - #[async_trait] impl RequestHandler for JsonRpcInterface { async fn handle_request(&self, req: JsonRequest) -> JsonResult { - if !req.params.is_array() { - return JsonError::new(ErrorCode::InvalidParams, None, req.id).into() - } - - let params = req.params.as_array().unwrap(); - let rep = match req.method.as_str() { - Some("add") => self.add(params).await, - Some("get_ids") => self.get_ids(params).await, - Some("update") => self.update(params).await, - Some("set_state") => self.set_state(params).await, - Some("set_comment") => self.set_comment(params).await, - Some("get_task_by_id") => self.get_task_by_id(params).await, - Some("switch_ws") => self.switch_ws(params).await, - Some("get_ws") => self.get_ws(params).await, - Some("export") => self.export_to(params).await, - Some("import") => self.import_from(params).await, - Some("get_stop_tasks") => self.get_stop_tasks(params).await, - Some("ping") => self.pong(params).await, + "add" => self.add(req.params).await, + "get_ids" => self.get_ids(req.params).await, + "update" => self.update(req.params).await, + "set_state" => self.set_state(req.params).await, + "set_comment" => self.set_comment(req.params).await, + "get_task_by_id" => self.get_task_by_id(req.params).await, + "switch_ws" => self.switch_ws(req.params).await, + "get_ws" => self.get_ws(req.params).await, + "export" => self.export_to(req.params).await, + "import" => self.import_from(req.params).await, + "get_stop_tasks" => self.get_stop_tasks(req.params).await, - Some("dnet_switch") => self.dnet_switch(params).await, - Some("dnet_info") => self.dnet_info(params).await, - Some(_) | None => return JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(), + "ping" => return self.pong(req.id, req.params).await, + "dnet_switch" => self.dnet_switch(req.params).await, + _ => return JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(), }; to_json_result(rep, req.id) @@ -106,15 +87,6 @@ impl JsonRpcInterface { Self { dataset_path, nickname, workspace, workspaces, notify_queue_sender, p2p } } - // RPCAPI: - // Replies to a ping method. - // - // --> {"jsonrpc": "2.0", "method": "ping", "params": [], "id": 42} - // <-- {"jsonrpc": "2.0", "result": "pong", "id": 42} - async fn pong(&self, _params: &[Value]) -> TaudResult { - Ok(json!("pong")) - } - // RPCAPI: // Activate or deactivate dnet in the P2P stack. // By sending `true`, dnet will be activated, and by sending `false` dnet will @@ -122,28 +94,21 @@ impl JsonRpcInterface { // // --> {"jsonrpc": "2.0", "method": "dnet_switch", "params": [true], "id": 42} // <-- {"jsonrpc": "2.0", "result": true, "id": 42} - async fn dnet_switch(&self, params: &[Value]) -> TaudResult { - if params.len() != 1 && params[0].as_bool().is_none() { + async fn dnet_switch(&self, params: JsonValue) -> TaudResult { + let params = params.get::>().unwrap(); + if params.len() != 1 || !params[0].is_bool() { return Err(TaudError::InvalidData("Invalid parameters".into())) } - if params[0].as_bool().unwrap() { + let switch = params[0].get::().unwrap(); + + if *switch { self.p2p.dnet_enable().await; } else { self.p2p.dnet_disable().await; } - Ok(json!(true)) - } - - // RPCAPI: - // Retrieves P2P network information. - // - // --> {"jsonrpc": "2.0", "method": "dnet_info", "params": [], "id": 42} - // <-- {"jsonrpc": "2.0", result": {"nodeID": [], "nodeinfo": [], "id": 42} - async fn dnet_info(&self, _params: &[Value]) -> TaudResult { - let dnet_info = self.p2p.dnet_info().await; - Ok(net::P2p::map_dnet_info(dnet_info)) + Ok(JsonValue::Boolean(true)) } // RPCAPI: @@ -161,79 +126,156 @@ impl JsonRpcInterface { // "id": 1 // } // <-- {"jsonrpc": "2.0", "result": true, "id": 1} - async fn add(&self, params: &[Value]) -> TaudResult { + async fn add(&self, params: JsonValue) -> TaudResult { + let params = params.get::>().unwrap(); debug!(target: "tau", "JsonRpc::add() params {:?}", params); - let task: BaseTaskInfo = serde_json::from_value(params[0].clone())?; + if params.len() != 7 || + !params[0].is_string() || + !params[1].is_array() || + !params[2].is_string() || + !params[3].is_array() || + !params[4].is_array() + { + return Err(TaudError::InvalidData("Invalid parameters".to_string())) + } + + let due = match ¶ms[5] { + JsonValue::Null => None, + JsonValue::String(u64_str) => match u64::from_str_radix(&u64_str, 10) { + Ok(v) => Some(Timestamp(v)), + Err(e) => return Err(TaudError::InvalidData(e.to_string())), + }, + _ => return Err(TaudError::InvalidData("Invalid parameters".to_string())), + }; + + let rank = match params[6] { + JsonValue::Null => None, + JsonValue::Number(numba) => Some(numba as f32), + _ => return Err(TaudError::InvalidData("Invalid parameters".to_string())), + }; + + let tags = { + let mut tags = vec![]; + + for val in params[1].get::>().unwrap().iter() { + if let Some(tag) = val.get::() { + tags.push(tag.clone()); + } else { + return Err(TaudError::InvalidData("Invalid parameters".to_string())) + } + } + + tags + }; + + let assigns = { + let mut assigns = vec![]; + + for val in params[3].get::>().unwrap().iter() { + if let Some(assign) = val.get::() { + assigns.push(assign.clone()); + } else { + return Err(TaudError::InvalidData("Invalid parameters".to_string())) + } + } + + assigns + }; + + let projects = { + let mut projects = vec![]; + + for val in params[4].get::>().unwrap().iter() { + if let Some(project) = val.get::() { + projects.push(project.clone()); + } else { + return Err(TaudError::InvalidData("Invalid parameters".to_string())) + } + } + + projects + }; + let mut new_task: TaskInfo = TaskInfo::new( self.workspace.lock().await.clone(), - &task.title, - &task.desc, + ¶ms[0].get::().unwrap(), + ¶ms[2].get::().unwrap(), &self.nickname, - task.due, - task.rank, + due, + rank, &self.dataset_path, )?; - new_task.set_project(&task.project); - new_task.set_assign(&task.assign); - new_task.set_tags(&task.tags); + new_task.set_project(&projects); + new_task.set_assign(&assigns); + new_task.set_tags(&tags); self.notify_queue_sender.send(new_task.clone()).await.map_err(Error::from)?; - Ok(json!(new_task.id)) + Ok(JsonValue::Number(new_task.id.into())) } // RPCAPI: // List tasks // --> {"jsonrpc": "2.0", "method": "get_ids", "params": [], "id": 1} // <-- {"jsonrpc": "2.0", "result": [task_id, ...], "id": 1} - async fn get_ids(&self, params: &[Value]) -> TaudResult { + async fn get_ids(&self, params: JsonValue) -> TaudResult { + let params = params.get::>().unwrap(); debug!(target: "tau", "JsonRpc::get_ids() params {:?}", params); let ws = self.workspace.lock().await.clone(); let tasks = MonthTasks::load_current_tasks(&self.dataset_path, ws, false)?; - let task_ids: Vec = tasks.iter().map(|task| task.get_id()).collect(); + let task_ids: Vec = + tasks.iter().map(|task| JsonValue::Number(task.get_id().into())).collect(); - Ok(json!(task_ids)) + Ok(JsonValue::Array(task_ids)) } // RPCAPI: // Update task and returns `true` upon success. // --> {"jsonrpc": "2.0", "method": "update", "params": [task_id, {"title": "new title"} ], "id": 1} // <-- {"jsonrpc": "2.0", "result": true, "id": 1} - async fn update(&self, params: &[Value]) -> TaudResult { + async fn update(&self, params: JsonValue) -> TaudResult { + let params = params.get::>().unwrap(); debug!(target: "tau", "JsonRpc::update() params {:?}", params); - if params.len() != 2 { + if params.len() != 2 || !params[0].is_number() || !params[1].is_object() { return Err(TaudError::InvalidData("len of params should be 2".into())) } let ws = self.workspace.lock().await.clone(); - let task = self.check_params_for_update(¶ms[0], ¶ms[1], ws)?; + + let task = self.check_params_for_update( + *params[0].get::().unwrap() as u32, + params[1].get::>().unwrap(), + ws, + )?; self.notify_queue_sender.send(task).await.map_err(Error::from)?; - Ok(json!(true)) + Ok(JsonValue::Boolean(true)) } // RPCAPI: // Set state for a task and returns `true` upon success. // --> {"jsonrpc": "2.0", "method": "set_state", "params": [task_id, state], "id": 1} // <-- {"jsonrpc": "2.0", "result": true, "id": 1} - async fn set_state(&self, params: &[Value]) -> TaudResult { + async fn set_state(&self, params: JsonValue) -> TaudResult { // Allowed states for a task let states = ["stop", "start", "open", "pause"]; + let params = params.get::>().unwrap(); debug!(target: "tau", "JsonRpc::set_state() params {:?}", params); - if params.len() != 2 { + if params.len() != 2 || !params[0].is_number() || !params[1].is_string() { return Err(TaudError::InvalidData("len of params should be 2".into())) } - let state: String = serde_json::from_value(params[1].clone())?; + let state = params[1].get::().unwrap(); let ws = self.workspace.lock().await.clone(); - let mut task: TaskInfo = self.load_task_by_id(¶ms[0], ws)?; + let mut task: TaskInfo = + self.load_task_by_id(*params[0].get::().unwrap() as u32, ws)?; if states.contains(&state.as_str()) { task.set_state(&state); @@ -242,73 +284,90 @@ impl JsonRpcInterface { self.notify_queue_sender.send(task).await.map_err(Error::from)?; - Ok(json!(true)) + Ok(JsonValue::Boolean(true)) } // RPCAPI: // Set comment for a task and returns `true` upon success. // --> {"jsonrpc": "2.0", "method": "set_comment", "params": [task_id, comment_content], "id": 1} // <-- {"jsonrpc": "2.0", "result": true, "id": 1} - async fn set_comment(&self, params: &[Value]) -> TaudResult { + async fn set_comment(&self, params: JsonValue) -> TaudResult { + let params = params.get::>().unwrap(); debug!(target: "tau", "JsonRpc::set_comment() params {:?}", params); - if params.len() != 2 { + if params.len() != 2 || !params[0].is_number() || !params[1].is_string() { return Err(TaudError::InvalidData("len of params should be 2".into())) } - let comment_content: String = serde_json::from_value(params[1].clone())?; + let id = *params[0].get::().unwrap() as u32; + let comment_content = params[1].get::().unwrap(); let ws = self.workspace.lock().await.clone(); - let mut task: TaskInfo = self.load_task_by_id(¶ms[0], ws)?; + let mut task: TaskInfo = self.load_task_by_id(id, ws)?; task.set_comment(Comment::new(&comment_content, &self.nickname)); set_event(&mut task, "comment", &self.nickname, &comment_content); self.notify_queue_sender.send(task).await.map_err(Error::from)?; - Ok(json!(true)) + Ok(JsonValue::Boolean(true)) } // RPCAPI: // Get a task by id. // --> {"jsonrpc": "2.0", "method": "get_task_by_id", "params": [task_id], "id": 1} // <-- {"jsonrpc": "2.0", "result": "task", "id": 1} - async fn get_task_by_id(&self, params: &[Value]) -> TaudResult { + async fn get_task_by_id(&self, params: JsonValue) -> TaudResult { + let params = params.get::>().unwrap(); debug!(target: "tau", "JsonRpc::get_task_by_id() params {:?}", params); - if params.len() != 1 { + if params.len() != 1 || !params[0].is_number() { return Err(TaudError::InvalidData("len of params should be 1".into())) } let ws = self.workspace.lock().await.clone(); - let task: TaskInfo = self.load_task_by_id(¶ms[0], ws)?; + let task: TaskInfo = self.load_task_by_id(*params[0].get::().unwrap() as u32, ws)?; + let task: JsonValue = (&task).into(); - Ok(json!(task)) + Ok(task) } // RPCAPI: // Get all tasks. // --> {"jsonrpc": "2.0", "method": "get_stop_tasks", "params": [task_id], "id": 1} // <-- {"jsonrpc": "2.0", "result": "task", "id": 1} - async fn get_stop_tasks(&self, params: &[Value]) -> TaudResult { + async fn get_stop_tasks(&self, params: JsonValue) -> TaudResult { + let params = params.get::>().unwrap(); debug!(target: "tau", "JsonRpc::get_stop_tasks() params {:?}", params); - if params.len() != 1 { + if params.len() != 1 || !params[0].is_string() { return Err(TaudError::InvalidData("len of params should be 1".into())) } - let month = params[0].as_u64().map(Timestamp); + + let month = match params[0].get::() { + Some(u64_str) => match u64::from_str_radix(u64_str, 10) { + Ok(v) => Some(Timestamp(v)), + //Err(e) => return Err(TaudError::InvalidData(e.to_string())), + Err(_) => None, + }, + + None => None, + }; + let ws = self.workspace.lock().await.clone(); let tasks = MonthTasks::load_stop_tasks(&self.dataset_path, ws, month.as_ref())?; + let tasks: Vec = tasks.iter().map(|x| x.into()).collect(); - Ok(json!(tasks)) + Ok(JsonValue::Array(tasks)) } // RPCAPI: // Switch tasks workspace. // --> {"jsonrpc": "2.0", "method": "switch_ws", "params": [workspace], "id": 1} // <-- {"jsonrpc": "2.0", "result": "true", "id": 1} - async fn switch_ws(&self, params: &[Value]) -> TaudResult { + async fn switch_ws(&self, params: JsonValue) -> TaudResult { + let params = params.get::>().unwrap(); debug!(target: "tau", "JsonRpc::switch_ws() params {:?}", params); if params.len() != 1 { @@ -319,34 +378,36 @@ impl JsonRpcInterface { return Err(TaudError::InvalidData("Invalid workspace".into())) } - let ws = params[0].as_str().unwrap().to_string(); + let ws = params[0].get::().unwrap(); let mut s = self.workspace.lock().await; - if self.workspaces.contains_key(&ws) { - *s = ws + if self.workspaces.contains_key(ws) { + *s = ws.to_string() } else { warn!("Workspace \"{}\" is not configured", ws); - return Ok(json!(false)) + return Ok(JsonValue::Boolean(false)) } - Ok(json!(true)) + Ok(JsonValue::Boolean(true)) } // RPCAPI: // Get workspace. // --> {"jsonrpc": "2.0", "method": "get_ws", "params": [], "id": 1} // <-- {"jsonrpc": "2.0", "result": "workspace", "id": 1} - async fn get_ws(&self, params: &[Value]) -> TaudResult { + async fn get_ws(&self, params: JsonValue) -> TaudResult { + let params = params.get::>().unwrap(); debug!(target: "tau", "JsonRpc::get_ws() params {:?}", params); let ws = self.workspace.lock().await.clone(); - Ok(json!(ws)) + Ok(JsonValue::String(ws)) } // RPCAPI: // Export tasks. // --> {"jsonrpc": "2.0", "method": "export_to", "params": [path], "id": 1} // <-- {"jsonrpc": "2.0", "result": "true", "id": 1} - async fn export_to(&self, params: &[Value]) -> TaudResult { + async fn export_to(&self, params: JsonValue) -> TaudResult { + let params = params.get::>().unwrap(); debug!(target: "tau", "JsonRpc::export_to() params {:?}", params); if params.len() != 1 { @@ -358,7 +419,8 @@ impl JsonRpcInterface { } // mkdir datastore_path if not exists - let path = expand_path(params[0].as_str().unwrap())?.join("exported_tasks"); + let path = params[0].get::().unwrap(); + let path = expand_path(path)?.join("exported_tasks"); create_dir_all(path.join("month")).map_err(Error::from)?; create_dir_all(path.join("task")).map_err(Error::from)?; @@ -369,14 +431,15 @@ impl JsonRpcInterface { task.save(&path)?; } - Ok(json!(true)) + Ok(JsonValue::Boolean(true)) } // RPCAPI: // Import tasks. // --> {"jsonrpc": "2.0", "method": "import_from", "params": [path], "id": 1} // <-- {"jsonrpc": "2.0", "result": "true", "id": 1} - async fn import_from(&self, params: &[Value]) -> TaudResult { + async fn import_from(&self, params: JsonValue) -> TaudResult { + let params = params.get::>().unwrap(); debug!(target: "tau", "JsonRpc::import_from() params {:?}", params); if params.len() != 1 { @@ -387,7 +450,8 @@ impl JsonRpcInterface { return Err(TaudError::InvalidData("Invalid path".into())) } - let path = expand_path(params[0].as_str().unwrap())?.join("exported_tasks"); + let path = params[0].get::().unwrap(); + let path = expand_path(path)?.join("exported_tasks"); let ws = self.workspace.lock().await.clone(); let mut task_ids: Vec = @@ -411,34 +475,26 @@ impl JsonRpcInterface { task_ids.push(task.id); self.notify_queue_sender.send(task).await.map_err(Error::from)?; } - Ok(json!(true)) + Ok(JsonValue::Boolean(true)) } - fn load_task_by_id(&self, task_id: &Value, ws: String) -> TaudResult { - let task_id: u64 = serde_json::from_value(task_id.clone())?; + fn load_task_by_id(&self, task_id: u32, ws: String) -> TaudResult { let tasks = MonthTasks::load_current_tasks(&self.dataset_path, ws, false)?; - let task = tasks.into_iter().find(|t| (t.get_id() as u64) == task_id); + let task = tasks.into_iter().find(|t| (t.get_id()) == task_id); task.ok_or(TaudError::InvalidId) } fn check_params_for_update( &self, - task_id: &Value, - fields: &Value, + task_id: u32, + fields: &HashMap, ws: String, ) -> TaudResult { let mut task: TaskInfo = self.load_task_by_id(task_id, ws)?; - if !fields.is_object() { - return Err(TaudError::InvalidData("Invalid task's data".into())) - } - - let fields = fields.as_object().unwrap(); - if fields.contains_key("title") { - let title = fields.get("title").unwrap().clone(); - let title: String = serde_json::from_value(title)?; + let title = fields["title"].get::().unwrap(); if !title.is_empty() { task.set_title(&title); set_event(&mut task, "title", &self.nickname, &title); @@ -446,19 +502,23 @@ impl JsonRpcInterface { } if fields.contains_key("desc") { - let description = fields.get("desc"); - if let Some(description) = description { - let description: Option = serde_json::from_value(description.clone())?; - if let Some(desc) = description { - task.set_desc(&desc); - set_event(&mut task, "desc", &self.nickname, &desc); - } + let desc = fields["desc"].get::().unwrap(); + if !desc.is_empty() { + task.set_desc(&desc); + set_event(&mut task, "desc", &self.nickname, &desc); } } if fields.contains_key("rank") { - let rank_opt = fields.get("rank").unwrap(); - let rank: Option> = serde_json::from_value(rank_opt.clone())?; + // TODO: Why is this a double Option? + let rank = { + match fields["rank"] { + JsonValue::Null => None, + JsonValue::Number(rank) => Some(Some(rank as f32)), + _ => unreachable!(), + } + }; + if let Some(rank) = rank { task.set_rank(rank); match rank { @@ -473,8 +533,17 @@ impl JsonRpcInterface { } if fields.contains_key("due") { - let due = fields.get("due").unwrap().clone(); - let due: Option> = serde_json::from_value(due)?; + // TODO: Why is this a double Option? + let due = { + match &fields["due"] { + JsonValue::Null => None, + JsonValue::String(ts_str) => { + Some(Some(Timestamp(u64::from_str_radix(&ts_str, 10).unwrap()))) + } + _ => unreachable!(), + } + }; + if let Some(d) = due { task.set_due(d); match d { @@ -489,8 +558,13 @@ impl JsonRpcInterface { } if fields.contains_key("assign") { - let assign = fields.get("assign").unwrap().clone(); - let assign: Vec = serde_json::from_value(assign)?; + let assign: Vec = fields["assign"] + .get::>() + .unwrap() + .iter() + .map(|x| x.get::().unwrap().clone()) + .collect(); + if !assign.is_empty() { task.set_assign(&assign); set_event(&mut task, "assign", &self.nickname, &assign.join(", ")); @@ -498,8 +572,13 @@ impl JsonRpcInterface { } if fields.contains_key("project") { - let project = fields.get("project").unwrap().clone(); - let project: Vec = serde_json::from_value(project)?; + let project: Vec = fields["project"] + .get::>() + .unwrap() + .iter() + .map(|x| x.get::().unwrap().clone()) + .collect(); + if !project.is_empty() { task.set_project(&project); set_event(&mut task, "project", &self.nickname, &project.join(", ")); @@ -507,8 +586,13 @@ impl JsonRpcInterface { } if fields.contains_key("tags") { - let tags = fields.get("tags").unwrap().clone(); - let tags: Vec = serde_json::from_value(tags)?; + let tags: Vec = fields["tags"] + .get::>() + .unwrap() + .iter() + .map(|x| x.get::().unwrap().clone()) + .collect(); + if !tags.is_empty() { task.set_tags(&tags); set_event(&mut task, "tags", &self.nickname, &tags.join(", ")); diff --git a/bin/tau/taud/src/main.rs b/bin/tau/taud/src/main.rs index 0a68ef0dc..b8070c54f 100644 --- a/bin/tau/taud/src/main.rs +++ b/bin/tau/taud/src/main.rs @@ -39,6 +39,7 @@ use futures::{select, FutureExt}; use log::{debug, error, info}; use rand::rngs::OsRng; use structopt_toml::StructOptToml; +use tinyjson::JsonValue; use darkfi::{ async_daemonize, @@ -218,31 +219,31 @@ async fn on_receive_task( // otherwise it's a modification. match TaskInfo::load(&task.ref_id, datastore_path) { Ok(loaded_task) => { - let loaded_events = loaded_task.events.0; - let mut events = task.events.0.clone(); + let loaded_events = loaded_task.events; + let mut events = task.events.clone(); events.retain(|ev| !loaded_events.contains(ev)); let file = "/tmp/tau_pipe"; let mut pipe_write = pipe_write(file)?; let mut task_clone = task.clone(); - task_clone.events.0 = events; + task_clone.events = events; - let json = serde_json::to_string(&task_clone).unwrap(); - pipe_write.write_all(json.as_bytes())?; + let json: JsonValue = (&task_clone).into(); + pipe_write.write_all(json.stringify().unwrap().as_bytes())?; } Err(_) => { let file = "/tmp/tau_pipe"; let mut pipe_write = pipe_write(file)?; let mut task_clone = task.clone(); - task_clone.events.0.push(TaskEvent::new( + task_clone.events.push(TaskEvent::new( "add_task".to_string(), task_clone.owner.clone(), "".to_string(), )); - let json = serde_json::to_string(&task_clone).unwrap(); - pipe_write.write_all(json.as_bytes())?; + let json: JsonValue = (&task_clone).into(); + pipe_write.write_all(json.stringify().unwrap().as_bytes())?; } } } diff --git a/bin/tau/taud/src/task_info.rs b/bin/tau/taud/src/task_info.rs index 802a441fb..eda51f989 100644 --- a/bin/tau/taud/src/task_info.rs +++ b/bin/tau/taud/src/task_info.rs @@ -16,7 +16,12 @@ * along with this program. If not, see . */ -use std::path::{Path, PathBuf}; +use std::{ + collections::HashMap, + fmt, + path::{Path, PathBuf}, + str::FromStr, +}; use darkfi_serial::{SerialDecodable, SerialEncodable}; use log::debug; @@ -28,6 +33,7 @@ use darkfi::{ file::{load_json_file, save_json_file}, time::Timestamp, }, + Error, }; use crate::{ @@ -36,6 +42,51 @@ use crate::{ util::find_free_id, }; +pub enum State { + Open, + Start, + Pause, + Stop, +} + +impl State { + pub const fn is_start(&self) -> bool { + matches!(*self, Self::Start) + } + pub const fn is_pause(&self) -> bool { + matches!(*self, Self::Pause) + } + pub const fn is_stop(&self) -> bool { + matches!(*self, Self::Stop) + } +} + +impl fmt::Display for State { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + State::Open => write!(f, "open"), + State::Start => write!(f, "start"), + State::Stop => write!(f, "stop"), + State::Pause => write!(f, "pause"), + } + } +} + +impl FromStr for State { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + let result = match s.to_lowercase().as_str() { + "open" => State::Open, + "stop" => State::Stop, + "start" => State::Start, + "pause" => State::Pause, + _ => return Err(Error::ParseFailed("unable to parse state")), + }; + Ok(result) + } +} + #[derive(Clone, Debug, SerialEncodable, SerialDecodable, PartialEq, Eq)] pub struct TaskEvent { pub action: String, @@ -44,6 +95,23 @@ pub struct TaskEvent { pub timestamp: Timestamp, } +impl std::fmt::Display for TaskEvent { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "action: {}, timestamp: {}", self.action, self.timestamp) + } +} + +impl Default for TaskEvent { + fn default() -> Self { + Self { + action: State::Open.to_string(), + author: "".to_string(), + content: "".to_string(), + timestamp: Timestamp::current_time(), + } + } +} + impl TaskEvent { pub fn new(action: String, author: String, content: String) -> Self { Self { action, author, content, timestamp: Timestamp::current_time() } @@ -65,9 +133,9 @@ impl From<&JsonValue> for TaskEvent { fn from(value: &JsonValue) -> TaskEvent { let map = value.get::>().unwrap(); TaskEvent { - action: map["action"].get().unwrap().clone(), - author: map["author"].get().unwrap().clone(), - content: map["content"].get().unwrap().clone(), + action: map["action"].get::().unwrap().clone(), + author: map["author"].get::().unwrap().clone(), + content: map["content"].get::().unwrap().clone(), timestamp: Timestamp( u64::from_str_radix(map["timestamp"].get::().unwrap(), 10).unwrap(), ), @@ -82,6 +150,12 @@ pub struct Comment { timestamp: Timestamp, } +impl std::fmt::Display for Comment { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{} author: {}, content: {} ", self.timestamp, self.author, self.content) + } +} + impl From for JsonValue { fn from(comment: Comment) -> JsonValue { JsonValue::Object(HashMap::from([ @@ -96,8 +170,8 @@ impl From for Comment { fn from(value: JsonValue) -> Comment { let map = value.get::>().unwrap(); Comment { - content: map["content"].get().unwrap().clone(), - author: map["author"].get().unwrap().clone(), + content: map["content"].get::().unwrap().clone(), + author: map["author"].get::().unwrap().clone(), timestamp: Timestamp( u64::from_str_radix(map["timestamp"].get::().unwrap(), 10).unwrap(), ), @@ -218,7 +292,7 @@ impl From for TaskInfo { }; let events: Vec = events.iter().map(|x| x.into()).collect(); - let comments: Vec = comments.iter().map(|x| (*x).into()).collect(); + let comments: Vec = comments.iter().map(|x| (*x).clone().into()).collect(); TaskInfo { ref_id: value["ref_id"].get::().unwrap().clone(), diff --git a/src/rpc/jsonrpc.rs b/src/rpc/jsonrpc.rs index 8a5cd0752..1ba769947 100644 --- a/src/rpc/jsonrpc.rs +++ b/src/rpc/jsonrpc.rs @@ -155,9 +155,13 @@ pub struct JsonRequest { impl JsonRequest { /// Create a new [`JsonRequest`] object with the given method and parameters. /// The request ID is chosen randomly. - pub fn new(method: &str, params: JsonValue) -> Self { - assert!(params.is_array()); - Self { jsonrpc: "2.0", id: OsRng::gen(&mut OsRng), method: method.to_string(), params } + pub fn new(method: &str, params: Vec) -> Self { + Self { + jsonrpc: "2.0", + id: OsRng::gen(&mut OsRng), + method: method.to_string(), + params: JsonValue::Array(params), + } } /// Convert the object into a JSON string