taud: Finish new RPC port.

This commit is contained in:
parazyd
2023-08-21 13:31:36 +02:00
parent 805d465410
commit 2eea9daffd
12 changed files with 466 additions and 326 deletions

3
Cargo.lock generated
View File

@@ -5952,10 +5952,11 @@ dependencies = [
"log",
"prettytable-rs",
"serde",
"serde_json",
"simplelog",
"taud",
"term_grid",
"textwrap 0.16.0",
"tinyjson",
"url",
]

View File

@@ -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"

View File

@@ -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<TaskInfo>, 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<TaskInfo>, 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::<u32>().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<TaskInfo>, 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<TaskInfo>, 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<TaskInfo>, 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<TaskInfo>, 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<String>) -> Result<Vec<u64>> {
pub fn get_ids(filters: &mut Vec<String>) -> Result<Vec<u32>> {
let mut vec_ids = vec![];
let mut matching_id = String::new();
if let Some(index) = filters.iter().position(|t| {
t.parse::<u64>().is_ok() || !t.contains(':') && (t.contains(',') || t.contains('-'))
t.parse::<u32>().is_ok() || !t.contains(':') && (t.contains(',') || t.contains('-'))
}) {
matching_id.push_str(&filters.remove(index));
}
match matching_id {
_ if matching_id.parse::<u64>().is_ok() => {
let id = matching_id.parse::<u64>().unwrap();
_ if matching_id.parse::<u32>().is_ok() => {
let id = matching_id.parse::<u32>().unwrap();
vec_ids.push(id)
}
_ if !matching_id.contains(':') &&
(matching_id.contains(',') || matching_id.contains('-')) =>
{
let num = matching_id.replace(&[',', '-'][..], "");
if num.parse::<u64>().is_err() {
if num.parse::<u32>().is_err() {
error!("Invalid ID number");
exit(1)
}
@@ -209,17 +213,17 @@ pub fn get_ids(filters: &mut Vec<String>) -> Result<Vec<u64>> {
if id.contains('-') {
let range: Vec<&str> = id.split('-').collect();
let range =
range[0].parse::<u64>().unwrap()..=range[1].parse::<u64>().unwrap();
range[0].parse::<u32>().unwrap()..=range[1].parse::<u32>().unwrap();
for rid in range {
vec_ids.push(rid)
}
} else {
vec_ids.push(id.parse::<u64>().unwrap())
vec_ids.push(id.parse::<u32>().unwrap())
}
}
} else if matching_id.contains('-') {
let range: Vec<&str> = matching_id.split('-').collect();
let range = range[0].parse::<u64>().unwrap()..=range[1].parse::<u64>().unwrap();
let range = range[0].parse::<u32>().unwrap()..=range[1].parse::<u32>().unwrap();
for rid in range {
vec_ids.push(rid)
}

View File

@@ -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 => {

View File

@@ -16,58 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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<Self, Self::Err> {
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<String>,
@@ -78,27 +32,10 @@ pub struct BaseTask {
pub rank: Option<f32>,
}
#[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<String>,
pub desc: String,
pub owner: String,
pub assign: Vec<String>,
pub project: Vec<String>,
pub due: Option<u64>,
pub rank: Option<f32>,
pub created_at: u64,
pub state: String,
pub events: Vec<TaskEvent>,
pub comments: Vec<Comment>,
}
impl From<BaseTask> 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<BaseTask> 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<BaseTask> 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<String>) -> Result<BaseTask> {
let mut title = String::new();
let mut tags = vec![];

View File

@@ -16,10 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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<u32> {
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::<f64>().unwrap() as u32)
}
/// Get current open tasks ids.
pub async fn get_ids(&self) -> Result<Vec<u64>> {
let req = JsonRequest::new("get_ids", json!([]));
pub async fn get_ids(&self) -> Result<Vec<u32>> {
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::<Vec<JsonValue>>().unwrap() {
ret.push(*i.get::<f64>().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<bool> {
let req = JsonRequest::new("update", json!([id, task]));
pub async fn update(&self, id: u32, task: BaseTask) -> Result<bool> {
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::<bool>().unwrap())
}
/// Set the state for a task.
pub async fn set_state(&self, id: u64, state: &State) -> Result<bool> {
let req = JsonRequest::new("set_state", json!([id, state.to_string()]));
pub async fn set_state(&self, id: u32, state: &State) -> Result<bool> {
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::<bool>().unwrap())
}
/// Set a comment for a task.
pub async fn set_comment(&self, id: u64, content: &str) -> Result<bool> {
let req = JsonRequest::new("set_comment", json!([id, content]));
pub async fn set_comment(&self, id: u32, content: &str) -> Result<bool> {
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::<bool>().unwrap())
}
/// Get task data by its ID.
pub async fn get_task_by_id(&self, id: u64) -> Result<TaskInfo> {
let req = JsonRequest::new("get_task_by_id", json!([id]));
pub async fn get_task_by_id(&self, id: u32) -> Result<TaskInfo> {
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<i64>) -> Result<Vec<TaskInfo>> {
let req = JsonRequest::new("get_stop_tasks", json!([month]));
pub async fn get_stop_tasks(&self, month: Option<u64>) -> Result<Vec<TaskInfo>> {
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::<Vec<JsonValue>>().unwrap().iter().map(|x| (*x).clone().into()).collect();
Ok(rep)
}
/// Switch workspace.
pub async fn switch_ws(&self, workspace: String) -> Result<bool> {
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::<bool>().unwrap())
}
/// Get current workspace.
pub async fn get_ws(&self) -> Result<String> {
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::<String>().unwrap().clone())
}
/// Export tasks.
pub async fn export_to(&self, path: String) -> Result<bool> {
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::<bool>().unwrap())
}
/// Import tasks.
pub async fn import_from(&self, path: String) -> Result<bool> {
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::<bool>().unwrap())
}
}

View File

@@ -90,14 +90,18 @@ pub fn print_task_list(tasks: Vec<TaskInfo>, 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(&timestamp_to_date(task.due.unwrap_or(0), DateFormat::Date))
.style_spec(gen_style),
Cell::new(&timestamp_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<TaskInfo>, ws: String) -> Result<()> {
}
pub fn taskinfo_table(taskinfo: TaskInfo) -> Result<Table> {
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!(

View File

@@ -60,7 +60,7 @@ pub fn to_json_result(res: TaudResult<JsonValue>, 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 => {

View File

@@ -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<String>,
desc: String,
assign: Vec<String>,
project: Vec<String>,
due: Option<Timestamp>,
rank: Option<f32>,
}
#[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<Value> {
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<Value> {
if params.len() != 1 && params[0].as_bool().is_none() {
async fn dnet_switch(&self, params: JsonValue) -> TaudResult<JsonValue> {
let params = params.get::<Vec<JsonValue>>().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::<bool>().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<Value> {
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<Value> {
async fn add(&self, params: JsonValue) -> TaudResult<JsonValue> {
let params = params.get::<Vec<JsonValue>>().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 &params[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::<Vec<JsonValue>>().unwrap().iter() {
if let Some(tag) = val.get::<String>() {
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::<Vec<JsonValue>>().unwrap().iter() {
if let Some(assign) = val.get::<String>() {
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::<Vec<JsonValue>>().unwrap().iter() {
if let Some(project) = val.get::<String>() {
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,
&params[0].get::<String>().unwrap(),
&params[2].get::<String>().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<Value> {
async fn get_ids(&self, params: JsonValue) -> TaudResult<JsonValue> {
let params = params.get::<Vec<JsonValue>>().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<u32> = tasks.iter().map(|task| task.get_id()).collect();
let task_ids: Vec<JsonValue> =
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<Value> {
async fn update(&self, params: JsonValue) -> TaudResult<JsonValue> {
let params = params.get::<Vec<JsonValue>>().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(&params[0], &params[1], ws)?;
let task = self.check_params_for_update(
*params[0].get::<f64>().unwrap() as u32,
params[1].get::<HashMap<String, JsonValue>>().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<Value> {
async fn set_state(&self, params: JsonValue) -> TaudResult<JsonValue> {
// Allowed states for a task
let states = ["stop", "start", "open", "pause"];
let params = params.get::<Vec<JsonValue>>().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::<String>().unwrap();
let ws = self.workspace.lock().await.clone();
let mut task: TaskInfo = self.load_task_by_id(&params[0], ws)?;
let mut task: TaskInfo =
self.load_task_by_id(*params[0].get::<f64>().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<Value> {
async fn set_comment(&self, params: JsonValue) -> TaudResult<JsonValue> {
let params = params.get::<Vec<JsonValue>>().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::<f64>().unwrap() as u32;
let comment_content = params[1].get::<String>().unwrap();
let ws = self.workspace.lock().await.clone();
let mut task: TaskInfo = self.load_task_by_id(&params[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<Value> {
async fn get_task_by_id(&self, params: JsonValue) -> TaudResult<JsonValue> {
let params = params.get::<Vec<JsonValue>>().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(&params[0], ws)?;
let task: TaskInfo = self.load_task_by_id(*params[0].get::<f64>().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<Value> {
async fn get_stop_tasks(&self, params: JsonValue) -> TaudResult<JsonValue> {
let params = params.get::<Vec<JsonValue>>().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::<String>() {
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<JsonValue> = 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<Value> {
async fn switch_ws(&self, params: JsonValue) -> TaudResult<JsonValue> {
let params = params.get::<Vec<JsonValue>>().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::<String>().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<Value> {
async fn get_ws(&self, params: JsonValue) -> TaudResult<JsonValue> {
let params = params.get::<Vec<JsonValue>>().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<Value> {
async fn export_to(&self, params: JsonValue) -> TaudResult<JsonValue> {
let params = params.get::<Vec<JsonValue>>().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::<String>().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<Value> {
async fn import_from(&self, params: JsonValue) -> TaudResult<JsonValue> {
let params = params.get::<Vec<JsonValue>>().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::<String>().unwrap();
let path = expand_path(path)?.join("exported_tasks");
let ws = self.workspace.lock().await.clone();
let mut task_ids: Vec<u32> =
@@ -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<TaskInfo> {
let task_id: u64 = serde_json::from_value(task_id.clone())?;
fn load_task_by_id(&self, task_id: u32, ws: String) -> TaudResult<TaskInfo> {
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<String, JsonValue>,
ws: String,
) -> TaudResult<TaskInfo> {
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::<String>().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<String> = 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::<String>().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<Option<f32>> = 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<Option<Timestamp>> = 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<String> = serde_json::from_value(assign)?;
let assign: Vec<String> = fields["assign"]
.get::<Vec<JsonValue>>()
.unwrap()
.iter()
.map(|x| x.get::<String>().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<String> = serde_json::from_value(project)?;
let project: Vec<String> = fields["project"]
.get::<Vec<JsonValue>>()
.unwrap()
.iter()
.map(|x| x.get::<String>().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<String> = serde_json::from_value(tags)?;
let tags: Vec<String> = fields["tags"]
.get::<Vec<JsonValue>>()
.unwrap()
.iter()
.map(|x| x.get::<String>().unwrap().clone())
.collect();
if !tags.is_empty() {
task.set_tags(&tags);
set_event(&mut task, "tags", &self.nickname, &tags.join(", "));

View File

@@ -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())?;
}
}
}

View File

@@ -16,7 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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<Self, Self::Err> {
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::<HashMap<String, JsonValue>>().unwrap();
TaskEvent {
action: map["action"].get().unwrap().clone(),
author: map["author"].get().unwrap().clone(),
content: map["content"].get().unwrap().clone(),
action: map["action"].get::<String>().unwrap().clone(),
author: map["author"].get::<String>().unwrap().clone(),
content: map["content"].get::<String>().unwrap().clone(),
timestamp: Timestamp(
u64::from_str_radix(map["timestamp"].get::<String>().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<Comment> for JsonValue {
fn from(comment: Comment) -> JsonValue {
JsonValue::Object(HashMap::from([
@@ -96,8 +170,8 @@ impl From<JsonValue> for Comment {
fn from(value: JsonValue) -> Comment {
let map = value.get::<HashMap<String, JsonValue>>().unwrap();
Comment {
content: map["content"].get().unwrap().clone(),
author: map["author"].get().unwrap().clone(),
content: map["content"].get::<String>().unwrap().clone(),
author: map["author"].get::<String>().unwrap().clone(),
timestamp: Timestamp(
u64::from_str_radix(map["timestamp"].get::<String>().unwrap(), 10).unwrap(),
),
@@ -218,7 +292,7 @@ impl From<JsonValue> for TaskInfo {
};
let events: Vec<TaskEvent> = events.iter().map(|x| x.into()).collect();
let comments: Vec<Comment> = comments.iter().map(|x| (*x).into()).collect();
let comments: Vec<Comment> = comments.iter().map(|x| (*x).clone().into()).collect();
TaskInfo {
ref_id: value["ref_id"].get::<String>().unwrap().clone(),

View File

@@ -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<JsonValue>) -> 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