use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::io::Write;
use std::path::PathBuf;
use structopt::StructOpt;
use url::Url;
const JSON_RPC_VERSION: &str = "2.0";
#[allow(unused)]
#[derive(Debug)]
#[repr(i32)]
pub enum JsonRpcError {
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
ServerError = -32000,
}
impl Display for JsonRpcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl From<serde_json::Error> for JsonRpcError {
fn from(e: serde_json::Error) -> Self {
use serde_json::error::Category;
match e.classify() {
Category::Data => JsonRpcError::InvalidRequest,
Category::Eof | Category::Io | Category::Syntax => JsonRpcError::ParseError,
}
}
}
impl From<std::io::Error> for JsonRpcError {
fn from(_: std::io::Error) -> Self {
JsonRpcError::ServerError
}
}
impl From<anyhow::Error> for JsonRpcError {
fn from(_: anyhow::Error) -> Self {
JsonRpcError::ServerError
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct RpcMessage {
pub jsonrpc: String,
pub id: Option<RpcId>,
#[serde(flatten)]
pub body: RpcBody,
}
impl RpcMessage {
pub fn request(id: Option<&RpcId>, request: RpcRequest) -> Self {
RpcMessage {
jsonrpc: JSON_RPC_VERSION.to_string(),
id: id.cloned(),
body: RpcBody::Request { request },
}
}
pub fn response(id: Option<&RpcId>, result: RpcResult) -> Self {
RpcMessage {
jsonrpc: JSON_RPC_VERSION.to_string(),
id: id.cloned(),
body: RpcBody::Result { result },
}
}
pub fn file_response(id: Option<&RpcId>, file: PathBuf, url: Url) -> Self {
Self::response(id, RpcResult::File(RpcFileResult { file, url }))
}
pub fn files_response(id: Option<&RpcId>, items: Vec<(PathBuf, Url)>) -> Self {
let items = items
.into_iter()
.map(|(file, url)| RpcFileResult { file, url })
.collect();
Self::response(id, RpcResult::Files(items))
}
pub fn error<E: Into<JsonRpcError> + std::fmt::Display>(id: Option<&RpcId>, err: E) -> Self {
let message = format!("{err:#}");
RpcMessage {
jsonrpc: JSON_RPC_VERSION.to_string(),
id: id.cloned(),
body: RpcBody::Error {
error: RpcError {
message,
code: err.into() as i32,
},
},
}
}
pub fn request_error(id: Option<&RpcId>) -> Self {
Self::error(id, JsonRpcError::InvalidRequest)
}
pub fn validate(&self) -> Result<(), JsonRpcError> {
if self.jsonrpc.as_str() != JSON_RPC_VERSION {
return Err(JsonRpcError::InvalidRequest);
}
Ok(())
}
pub fn print(&self, verbose: bool) {
let mut stdout = std::io::stdout();
let json = match verbose {
true => serde_json::to_string(self).unwrap(),
false => serde_json::to_string(&self.body).unwrap(),
};
let _ = stdout.write_fmt(format_args!("{}\r\n", json));
let _ = stdout.flush();
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum RpcId {
Int(i64),
Float(f64),
String(String),
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
#[serde(rename_all = "snake_case")]
pub enum RpcBody {
Request {
#[serde(flatten)]
request: RpcRequest,
},
Result {
result: RpcResult,
},
Error {
error: RpcError,
},
}
#[derive(Serialize, Deserialize, StructOpt, Debug, Clone)]
#[serde(tag = "method", content = "params")]
#[serde(rename_all = "snake_case")]
pub enum RpcRequest {
Version {},
Publish { files: Vec<PathBuf> },
Close { urls: Vec<Url> },
Download {
url: Url,
output_file: PathBuf,
},
Receive {
output_file: PathBuf,
},
Upload {
url: Url,
file: PathBuf,
},
Shutdown {},
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum RpcResult {
String(String),
File(RpcFileResult),
Files(Vec<RpcFileResult>),
Status(RpcStatusResult),
Statuses(Vec<RpcStatusResult>),
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
#[serde(rename_all = "snake_case")]
pub enum RpcStatusResult {
Ok,
Error,
}
impl From<bool> for RpcStatusResult {
fn from(b: bool) -> Self {
match b {
true => RpcStatusResult::Ok,
false => RpcStatusResult::Error,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub struct RpcFileResult {
pub file: PathBuf,
pub url: Url,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub struct RpcError {
pub code: i32,
pub message: String,
}