From 2280240d0920d3bbfe25fd941b2ef72e3bfcb891 Mon Sep 17 00:00:00 2001 From: ghassmo Date: Sat, 26 Mar 2022 11:56:45 +0400 Subject: [PATCH] bin/taud: move rpc code to separate file --- bin/taud/src/jsonrpc.rs | 392 ++++++++++++++++++++++++++++++++++++++++ bin/taud/src/main.rs | 358 +----------------------------------- 2 files changed, 397 insertions(+), 353 deletions(-) create mode 100644 bin/taud/src/jsonrpc.rs diff --git a/bin/taud/src/jsonrpc.rs b/bin/taud/src/jsonrpc.rs new file mode 100644 index 000000000..3f69174c3 --- /dev/null +++ b/bin/taud/src/jsonrpc.rs @@ -0,0 +1,392 @@ +use std::sync::Arc; + +use async_executor::Executor; +use async_trait::async_trait; +use log::debug; +use serde_json::{json, Value}; + +use darkfi::{ + rpc::{ + jsonrpc::{error as jsonerr, response as jsonresp, ErrorCode, JsonRequest, JsonResult}, + rpcserver::RequestHandler, + }, + Result, +}; + +use crate::{ + month_tasks::MonthTasks, + task_info::{Comment, TaskInfo}, + util::{get_current_time, Settings, Timestamp}, +}; + +pub struct JsonRpcInterface { + pub settings: Settings, +} + +#[async_trait] +impl RequestHandler for JsonRpcInterface { + async fn handle_request(&self, req: JsonRequest, _executor: Arc>) -> JsonResult { + if req.params.as_array().is_none() { + return JsonResult::Err(jsonerr(ErrorCode::InvalidParams, None, req.id)) + } + + debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap()); + + match req.method.as_str() { + Some("add") => return self.add(req.id, req.params).await, + Some("list") => return self.list(req.id, req.params).await, + Some("update") => return self.update(req.id, req.params).await, + Some("get_state") => return self.get_state(req.id, req.params).await, + Some("set_state") => return self.set_state(req.id, req.params).await, + Some("set_comment") => return self.set_comment(req.id, req.params).await, + Some(_) | None => { + return JsonResult::Err(jsonerr(ErrorCode::MethodNotFound, None, req.id)) + } + } + } +} + +impl JsonRpcInterface { + // RPCAPI: + // Add new task and returns `true` upon success. + // --> {"jsonrpc": "2.0", "method": "add", "params": ["title", "desc", ["assign"], ["project"], "due", "rank"], "id": 1} + // <-- {"jsonrpc": "2.0", "result": true, "id": 1} + async fn add(&self, id: Value, params: Value) -> JsonResult { + let args = params.as_array().unwrap(); + + if args.len() != 6 { + return JsonResult::Err(jsonerr(ErrorCode::InvalidParams, None, id)) + } + + let mut task: TaskInfo; + + match (args[0].as_str(), args[1].as_str(), args[5].as_u64()) { + (Some(title), Some(desc), Some(rank)) => { + let due: Option = if args[4].is_i64() { + let timestamp = args[4].as_i64().unwrap(); + let timestamp = Timestamp(timestamp); + + if timestamp < get_current_time() { + return JsonResult::Err(jsonerr( + ErrorCode::InvalidParams, + Some("invalid due date".into()), + id, + )) + } + + Some(timestamp) + } else { + None + }; + + match TaskInfo::new(title, desc, due, rank as u32, &self.settings) { + Ok(t) => task = t, + Err(e) => { + return JsonResult::Err(jsonerr( + ErrorCode::InternalError, + Some(e.to_string()), + id, + )) + } + } + } + (None, _, _) => { + return JsonResult::Err(jsonerr( + ErrorCode::InvalidParams, + Some("invalid title".into()), + id, + )) + } + (_, None, _) => { + return JsonResult::Err(jsonerr( + ErrorCode::InvalidParams, + Some("invalid desc".into()), + id, + )) + } + (_, _, None) => { + return JsonResult::Err(jsonerr( + ErrorCode::InvalidParams, + Some("invalid rank".into()), + id, + )) + } + } + + let assign = args[2].as_array(); + if assign.is_some() && !assign.unwrap().is_empty() { + task.set_assign( + &assign + .unwrap() + .iter() + .filter(|a| a.as_str().is_some()) + .map(|a| a.as_str().unwrap().to_string()) + .collect(), + ); + } + + let project = args[3].as_array(); + if project.is_some() && !project.unwrap().is_empty() { + task.set_project( + &project + .unwrap() + .iter() + .filter(|p| p.as_str().is_some()) + .map(|p| p.as_str().unwrap().to_string()) + .collect(), + ); + } + + let result = || -> Result<()> { + task.save()?; + task.activate()?; + Ok(()) + }; + + match result() { + Ok(()) => JsonResult::Resp(jsonresp(json!(true), id)), + Err(e) => { + JsonResult::Err(jsonerr(ErrorCode::ServerError(-32603), Some(e.to_string()), id)) + } + } + } + + // RPCAPI: + // List tasks + // --> {"jsonrpc": "2.0", "method": "list", "params": [], "id": 1} + // <-- {"jsonrpc": "2.0", "result": [task, ...], "id": 1} + async fn list(&self, id: Value, _params: Value) -> JsonResult { + match MonthTasks::load_current_open_tasks(&self.settings) { + Ok(tks) => JsonResult::Resp(jsonresp(json!(tks), id)), + Err(e) => { + JsonResult::Err(jsonerr(ErrorCode::ServerError(-32603), Some(e.to_string()), id)) + } + } + } + + // 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, id: Value, params: Value) -> JsonResult { + let args = params.as_array().unwrap(); + + if args.len() != 2 { + return JsonResult::Err(jsonerr(ErrorCode::InvalidParams, None, id)) + } + + if !args[0].is_u64() { + return JsonResult::Err(jsonerr(ErrorCode::InvalidParams, Some("invalid id".into()), id)) + } + + if !args[1].is_object() { + return JsonResult::Err(jsonerr( + ErrorCode::InvalidParams, + Some("invalid update data".into()), + id, + )) + } + + let task_id = args[0].as_u64().unwrap(); + let data = args[1].as_object().unwrap(); + + let mut task: TaskInfo = match self.load_task_by_id(task_id) { + Ok(t) => t, + Err(e) => return JsonResult::Err(jsonerr(ErrorCode::InternalError, Some(e), id)), + }; + + let mut result = || -> std::result::Result<(), String> { + if data.contains_key("title") { + let title = data + .get("title") + .ok_or("error parsing title")? + .as_str() + .ok_or("invalid value for title")?; + task.set_title(title); + } + + if data.contains_key("description") { + let description = data + .get("description") + .ok_or("error parsing description")? + .as_str() + .ok_or("invalid value for description")?; + task.set_desc(description); + } + + if data.contains_key("rank") { + let rank = data + .get("rank") + .ok_or("error parsing rank")? + .as_u64() + .ok_or("invalid value for rank")?; + + task.set_rank(rank as u32); + } + + if data.contains_key("due") { + if let Some(due) = data.get("due").ok_or("error parsing due")?.as_i64() { + task.set_due(Some(Timestamp(due))); + } else { + task.set_due(None); + } + } + + if data.contains_key("assign") { + task.set_assign( + &data + .get("assign") + .ok_or("error parsing assign")? + .as_array() + .ok_or("invalid value for assign")? + .iter() + .filter(|a| a.as_str().is_some()) + .map(|a| a.as_str().unwrap().to_string()) + .collect(), + ); + } + + if data.contains_key("project") { + task.set_project( + &data + .get("project") + .ok_or("error parsing project")? + .as_array() + .ok_or("invalid value for project")? + .iter() + .filter(|p| p.as_str().is_some()) + .map(|p| p.as_str().unwrap().to_string()) + .collect(), + ); + } + + let save = task.save(); + + if let Err(e) = save { + return Err(format!("Unable to save the task: {}", e)) + } + + Ok(()) + }; + + match result() { + Ok(()) => JsonResult::Resp(jsonresp(json!(true), id)), + Err(e) => JsonResult::Err(jsonerr(ErrorCode::InvalidParams, Some(e), id)), + } + } + + // RPCAPI: + // Get task's state. + // --> {"jsonrpc": "2.0", "method": "get_state", "params": [task_id], "id": 1} + // <-- {"jsonrpc": "2.0", "result": "state", "id": 1} + async fn get_state(&self, id: Value, params: Value) -> JsonResult { + let args = params.as_array().unwrap(); + + if !args[0].is_u64() { + return JsonResult::Err(jsonerr(ErrorCode::InvalidParams, Some("invalid id".into()), id)) + } + + let task_id = args[0].as_u64().unwrap(); + + let task: TaskInfo = match self.load_task_by_id(task_id) { + Ok(t) => t, + Err(e) => return JsonResult::Err(jsonerr(ErrorCode::InternalError, Some(e), id)), + }; + + JsonResult::Resp(jsonresp(json!(task.get_state()), id)) + } + + // 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, id: Value, params: Value) -> JsonResult { + let args = params.as_array().unwrap(); + + if !args[0].is_u64() { + return JsonResult::Err(jsonerr(ErrorCode::InvalidParams, Some("invalid id".into()), id)) + } + + if !args[1].is_string() { + return JsonResult::Err(jsonerr( + ErrorCode::InvalidParams, + Some("invalid state".into()), + id, + )) + } + + let task_id = args[0].as_u64().unwrap(); + let state = args[1].as_str().unwrap(); + + let mut task: TaskInfo = match self.load_task_by_id(task_id) { + Ok(t) => t, + Err(e) => return JsonResult::Err(jsonerr(ErrorCode::InternalError, Some(e), id)), + }; + + task.set_state(state); + + match task.save() { + Ok(()) => JsonResult::Resp(jsonresp(json!(true), id)), + Err(e) => JsonResult::Err(jsonerr(ErrorCode::InternalError, Some(e.to_string()), id)), + } + } + + // RPCAPI: + // Set comment for a task and returns `true` upon success. + // --> {"jsonrpc": "2.0", "method": "set_comment", "params": [task_id, comment_author, comment_content], "id": 1} + // <-- {"jsonrpc": "2.0", "result": true, "id": 1} + async fn set_comment(&self, id: Value, params: Value) -> JsonResult { + let args = params.as_array().unwrap(); + + if !args[0].is_u64() { + return JsonResult::Err(jsonerr(ErrorCode::InvalidParams, Some("invalid id".into()), id)) + } + + if !args[1].is_string() { + return JsonResult::Err(jsonerr( + ErrorCode::InvalidParams, + Some("invalid comment author".into()), + id, + )) + } + + if !args[2].is_string() { + return JsonResult::Err(jsonerr( + ErrorCode::InvalidParams, + Some("invalid comment content".into()), + id, + )) + } + + let task_id = args[0].as_u64().unwrap(); + let comment_author = args[1].as_str().unwrap(); + let comment_content = args[2].as_str().unwrap(); + + let mut task: TaskInfo = match self.load_task_by_id(task_id) { + Ok(t) => t, + Err(e) => return JsonResult::Err(jsonerr(ErrorCode::InternalError, Some(e), id)), + }; + + task.set_comment(Comment::new(comment_content, comment_author)); + + match task.save() { + Ok(()) => JsonResult::Resp(jsonresp(json!(true), id)), + Err(e) => JsonResult::Err(jsonerr(ErrorCode::InternalError, Some(e.to_string()), id)), + } + } + + fn load_task_by_id(&self, task_id: u64) -> std::result::Result { + let tasks: Vec = match MonthTasks::load_current_open_tasks(&self.settings) { + Ok(v) => v, + Err(e) => return Err(e.to_string()), + }; + + let task = tasks.into_iter().find(|t| (t.get_id() as u64) == task_id); + + if task.is_none() { + return Err("Didn't find a task with the provided id".into()) + } + + Ok(task.unwrap()) + } +} diff --git a/bin/taud/src/main.rs b/bin/taud/src/main.rs index 4dfef33b4..031061e4e 100644 --- a/bin/taud/src/main.rs +++ b/bin/taud/src/main.rs @@ -1,17 +1,11 @@ use std::{fs::create_dir_all, sync::Arc}; use async_executor::Executor; -use async_trait::async_trait; use clap::{IntoApp, Parser}; -use log::debug; -use serde_json::{json, Value}; use simplelog::{ColorChoice, TermLogger, TerminalMode}; use darkfi::{ - rpc::{ - jsonrpc::{error as jsonerr, response as jsonresp, ErrorCode::*, JsonRequest, JsonResult}, - rpcserver::{listen_and_serve, RequestHandler, RpcServerConfig}, - }, + rpc::rpcserver::{listen_and_serve, RpcServerConfig}, util::{ cli::{log_config, spawn_config, Config}, expand_path, @@ -21,356 +15,14 @@ use darkfi::{ }; mod crdt; +mod jsonrpc; mod month_tasks; mod task_info; mod util; -use crate::{ - month_tasks::MonthTasks, - task_info::{Comment, TaskInfo}, - util::{get_current_time, CliTaud, Settings, TauConfig, Timestamp, CONFIG_FILE_CONTENTS}, -}; -struct JsonRpcInterface { - settings: Settings, -} +use jsonrpc::JsonRpcInterface; -#[async_trait] -impl RequestHandler for JsonRpcInterface { - async fn handle_request(&self, req: JsonRequest, _executor: Arc>) -> JsonResult { - if req.params.as_array().is_none() { - return JsonResult::Err(jsonerr(InvalidParams, None, req.id)) - } - - debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap()); - - match req.method.as_str() { - Some("add") => return self.add(req.id, req.params).await, - Some("list") => return self.list(req.id, req.params).await, - Some("update") => return self.update(req.id, req.params).await, - Some("get_state") => return self.get_state(req.id, req.params).await, - Some("set_state") => return self.set_state(req.id, req.params).await, - Some("set_comment") => return self.set_comment(req.id, req.params).await, - Some(_) | None => return JsonResult::Err(jsonerr(MethodNotFound, None, req.id)), - } - } -} - -impl JsonRpcInterface { - // RPCAPI: - // Add new task and returns `true` upon success. - // --> {"jsonrpc": "2.0", "method": "add", "params": ["title", "desc", ["assign"], ["project"], "due", "rank"], "id": 1} - // <-- {"jsonrpc": "2.0", "result": true, "id": 1} - async fn add(&self, id: Value, params: Value) -> JsonResult { - let args = params.as_array().unwrap(); - - if args.len() != 6 { - return JsonResult::Err(jsonerr(InvalidParams, None, id)) - } - - let mut task: TaskInfo; - - match (args[0].as_str(), args[1].as_str(), args[5].as_u64()) { - (Some(title), Some(desc), Some(rank)) => { - let due: Option = if args[4].is_i64() { - let timestamp = args[4].as_i64().unwrap(); - let timestamp = Timestamp(timestamp); - - if timestamp < get_current_time() { - return JsonResult::Err(jsonerr( - InvalidParams, - Some("invalid due date".into()), - id, - )) - } - - Some(timestamp) - } else { - None - }; - - match TaskInfo::new(title, desc, due, rank as u32, &self.settings) { - Ok(t) => task = t, - Err(e) => { - return JsonResult::Err(jsonerr(InternalError, Some(e.to_string()), id)) - } - } - } - (None, _, _) => { - return JsonResult::Err(jsonerr(InvalidParams, Some("invalid title".into()), id)) - } - (_, None, _) => { - return JsonResult::Err(jsonerr(InvalidParams, Some("invalid desc".into()), id)) - } - (_, _, None) => { - return JsonResult::Err(jsonerr(InvalidParams, Some("invalid rank".into()), id)) - } - } - - let assign = args[2].as_array(); - if assign.is_some() && !assign.unwrap().is_empty() { - task.set_assign( - &assign - .unwrap() - .iter() - .filter(|a| a.as_str().is_some()) - .map(|a| a.as_str().unwrap().to_string()) - .collect(), - ); - } - - let project = args[3].as_array(); - if project.is_some() && !project.unwrap().is_empty() { - task.set_project( - &project - .unwrap() - .iter() - .filter(|p| p.as_str().is_some()) - .map(|p| p.as_str().unwrap().to_string()) - .collect(), - ); - } - - let result = || -> Result<()> { - task.save()?; - task.activate()?; - Ok(()) - }; - - match result() { - Ok(()) => JsonResult::Resp(jsonresp(json!(true), id)), - Err(e) => JsonResult::Err(jsonerr(ServerError(-32603), Some(e.to_string()), id)), - } - } - - // RPCAPI: - // List tasks - // --> {"jsonrpc": "2.0", "method": "list", "params": [], "id": 1} - // <-- {"jsonrpc": "2.0", "result": [task, ...], "id": 1} - async fn list(&self, id: Value, _params: Value) -> JsonResult { - match MonthTasks::load_current_open_tasks(&self.settings) { - Ok(tks) => JsonResult::Resp(jsonresp(json!(tks), id)), - Err(e) => JsonResult::Err(jsonerr(ServerError(-32603), Some(e.to_string()), id)), - } - } - - // 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, id: Value, params: Value) -> JsonResult { - let args = params.as_array().unwrap(); - - if args.len() != 2 { - return JsonResult::Err(jsonerr(InvalidParams, None, id)) - } - - if !args[0].is_u64() { - return JsonResult::Err(jsonerr(InvalidParams, Some("invalid id".into()), id)) - } - - if !args[1].is_object() { - return JsonResult::Err(jsonerr(InvalidParams, Some("invalid update data".into()), id)) - } - - let task_id = args[0].as_u64().unwrap(); - let data = args[1].as_object().unwrap(); - - let mut task: TaskInfo = match self.load_task_by_id(task_id) { - Ok(t) => t, - Err(e) => return JsonResult::Err(jsonerr(InternalError, Some(e), id)), - }; - - let mut result = || -> std::result::Result<(), String> { - if data.contains_key("title") { - let title = data - .get("title") - .ok_or("error parsing title")? - .as_str() - .ok_or("invalid value for title")?; - task.set_title(title); - } - - if data.contains_key("description") { - let description = data - .get("description") - .ok_or("error parsing description")? - .as_str() - .ok_or("invalid value for description")?; - task.set_desc(description); - } - - if data.contains_key("rank") { - let rank = data - .get("rank") - .ok_or("error parsing rank")? - .as_u64() - .ok_or("invalid value for rank")?; - - task.set_rank(rank as u32); - } - - if data.contains_key("due") { - if let Some(due) = data.get("due").ok_or("error parsing due")?.as_i64() { - task.set_due(Some(Timestamp(due))); - } else { - task.set_due(None); - } - } - - if data.contains_key("assign") { - task.set_assign( - &data - .get("assign") - .ok_or("error parsing assign")? - .as_array() - .ok_or("invalid value for assign")? - .iter() - .filter(|a| a.as_str().is_some()) - .map(|a| a.as_str().unwrap().to_string()) - .collect(), - ); - } - - if data.contains_key("project") { - task.set_project( - &data - .get("project") - .ok_or("error parsing project")? - .as_array() - .ok_or("invalid value for project")? - .iter() - .filter(|p| p.as_str().is_some()) - .map(|p| p.as_str().unwrap().to_string()) - .collect(), - ); - } - - let save = task.save(); - - if let Err(e) = save { - return Err(format!("Unable to save the task: {}", e)) - } - - Ok(()) - }; - - match result() { - Ok(()) => JsonResult::Resp(jsonresp(json!(true), id)), - Err(e) => JsonResult::Err(jsonerr(InvalidParams, Some(e), id)), - } - } - - // RPCAPI: - // Get task's state. - // --> {"jsonrpc": "2.0", "method": "get_state", "params": [task_id], "id": 1} - // <-- {"jsonrpc": "2.0", "result": "state", "id": 1} - async fn get_state(&self, id: Value, params: Value) -> JsonResult { - let args = params.as_array().unwrap(); - - if !args[0].is_u64() { - return JsonResult::Err(jsonerr(InvalidParams, Some("invalid id".into()), id)) - } - - let task_id = args[0].as_u64().unwrap(); - - let task: TaskInfo = match self.load_task_by_id(task_id) { - Ok(t) => t, - Err(e) => return JsonResult::Err(jsonerr(InternalError, Some(e), id)), - }; - - JsonResult::Resp(jsonresp(json!(task.get_state()), id)) - } - - // 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, id: Value, params: Value) -> JsonResult { - let args = params.as_array().unwrap(); - - if !args[0].is_u64() { - return JsonResult::Err(jsonerr(InvalidParams, Some("invalid id".into()), id)) - } - - if !args[1].is_string() { - return JsonResult::Err(jsonerr(InvalidParams, Some("invalid state".into()), id)) - } - - let task_id = args[0].as_u64().unwrap(); - let state = args[1].as_str().unwrap(); - - let mut task: TaskInfo = match self.load_task_by_id(task_id) { - Ok(t) => t, - Err(e) => return JsonResult::Err(jsonerr(InternalError, Some(e), id)), - }; - - task.set_state(state); - - match task.save() { - Ok(()) => JsonResult::Resp(jsonresp(json!(true), id)), - Err(e) => JsonResult::Err(jsonerr(InternalError, Some(e.to_string()), id)), - } - } - - // RPCAPI: - // Set comment for a task and returns `true` upon success. - // --> {"jsonrpc": "2.0", "method": "set_comment", "params": [task_id, comment_author, comment_content], "id": 1} - // <-- {"jsonrpc": "2.0", "result": true, "id": 1} - async fn set_comment(&self, id: Value, params: Value) -> JsonResult { - let args = params.as_array().unwrap(); - - if !args[0].is_u64() { - return JsonResult::Err(jsonerr(InvalidParams, Some("invalid id".into()), id)) - } - - if !args[1].is_string() { - return JsonResult::Err(jsonerr( - InvalidParams, - Some("invalid comment author".into()), - id, - )) - } - - if !args[2].is_string() { - return JsonResult::Err(jsonerr( - InvalidParams, - Some("invalid comment content".into()), - id, - )) - } - - let task_id = args[0].as_u64().unwrap(); - let comment_author = args[1].as_str().unwrap(); - let comment_content = args[2].as_str().unwrap(); - - let mut task: TaskInfo = match self.load_task_by_id(task_id) { - Ok(t) => t, - Err(e) => return JsonResult::Err(jsonerr(InternalError, Some(e), id)), - }; - - task.set_comment(Comment::new(comment_content, comment_author)); - - match task.save() { - Ok(()) => JsonResult::Resp(jsonresp(json!(true), id)), - Err(e) => JsonResult::Err(jsonerr(InternalError, Some(e.to_string()), id)), - } - } - - fn load_task_by_id(&self, task_id: u64) -> std::result::Result { - let tasks: Vec = match MonthTasks::load_current_open_tasks(&self.settings) { - Ok(v) => v, - Err(e) => return Err(e.to_string()), - }; - - let task = tasks.into_iter().find(|t| (t.get_id() as u64) == task_id); - - if task.is_none() { - return Err("Didn't find a task with the provided id".into()) - } - - Ok(task.unwrap()) - } -} +use crate::util::{CliTaud, Settings, TauConfig, CONFIG_FILE_CONTENTS}; async fn start(config: TauConfig, executor: Arc>) -> Result<()> { if config.dataset_path.is_empty() { @@ -423,7 +75,7 @@ mod tests { path::PathBuf, }; - use crate::{month_tasks::MonthTasks, task_info::TaskInfo}; + use crate::{month_tasks::MonthTasks, task_info::TaskInfo, util::get_current_time}; use super::*;