#![allow(dead_code)]
use jsonrpsee::types::ErrorObjectOwned;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
pub mod codes {
pub const PARSE_ERROR: i32 = -32700;
pub const INVALID_REQUEST: i32 = -32600;
pub const METHOD_NOT_FOUND: i32 = -32601;
pub const INVALID_PARAMS: i32 = -32602;
pub const INTERNAL_ERROR: i32 = -32603;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TryRequest {
pub jsonrpc: String,
pub id: u64,
pub method: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub params: Vec<Value>,
}
impl TryRequest {
pub fn schema() -> Self {
Self {
jsonrpc: "2.0".to_string(),
id: 1,
method: "plexus.schema".to_string(),
params: vec![],
}
}
pub fn method(method: &str, params: Vec<Value>) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id: 1,
method: method.to_string(),
params,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GuidedErrorData {
#[serde(rename = "try")]
pub try_request: TryRequest,
#[serde(flatten)]
pub context: Value,
}
impl GuidedErrorData {
pub fn new(try_request: TryRequest) -> Self {
Self {
try_request,
context: json!({}),
}
}
pub fn with_context(try_request: TryRequest, context: Value) -> Self {
Self {
try_request,
context,
}
}
}
pub struct GuidedError;
impl GuidedError {
pub fn parse_error(message: &str) -> ErrorObjectOwned {
let data = GuidedErrorData::new(TryRequest::schema());
ErrorObjectOwned::owned(
codes::PARSE_ERROR,
format!("Parse error: {}. This server speaks JSON-RPC 2.0 over WebSocket", message),
Some(data),
)
}
pub fn invalid_request(message: &str) -> ErrorObjectOwned {
let data = GuidedErrorData::new(TryRequest::schema());
ErrorObjectOwned::owned(
codes::INVALID_REQUEST,
format!("Invalid request: {}", message),
Some(data),
)
}
pub fn activation_not_found(activation: &str, available: Vec<String>) -> ErrorObjectOwned {
let data = GuidedErrorData::with_context(
TryRequest::schema(),
json!({
"activation": activation,
"available_activations": available,
}),
);
ErrorObjectOwned::owned(
codes::METHOD_NOT_FOUND,
format!("Activation '{}' not found", activation),
Some(data),
)
}
pub fn method_not_found(
activation: &str,
method: &str,
available_methods: Vec<String>,
example_method: Option<(&str, Vec<Value>)>,
) -> ErrorObjectOwned {
let try_request = match example_method {
Some((method_name, params)) => TryRequest::method(method_name, params),
None => TryRequest::schema(),
};
let data = GuidedErrorData::with_context(
try_request,
json!({
"activation": activation,
"method": method,
"available_methods": available_methods,
}),
);
ErrorObjectOwned::owned(
codes::METHOD_NOT_FOUND,
format!("Method '{}' not found in activation '{}'", method, activation),
Some(data),
)
}
pub fn invalid_params(
method: &str,
message: &str,
usage: Option<&str>,
example: Option<TryRequest>,
) -> ErrorObjectOwned {
let try_request = example.unwrap_or_else(TryRequest::schema);
let mut context = json!({
"method": method,
});
if let Some(usage_str) = usage {
context["usage"] = json!(usage_str);
}
let data = GuidedErrorData::with_context(try_request, context);
ErrorObjectOwned::owned(
codes::INVALID_PARAMS,
format!("Invalid params for {}: {}", method, message),
Some(data),
)
}
pub fn internal_error(message: &str) -> ErrorObjectOwned {
let data = GuidedErrorData::new(TryRequest::schema());
ErrorObjectOwned::owned(
codes::INTERNAL_ERROR,
format!("Internal error: {}", message),
Some(data),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_error_includes_try() {
let error = GuidedError::parse_error("invalid JSON");
let data: GuidedErrorData = serde_json::from_str(
error.data().unwrap().get()
).unwrap();
assert_eq!(data.try_request.method, "plexus.schema");
}
#[test]
fn test_activation_not_found_includes_available() {
let error = GuidedError::activation_not_found(
"foo",
vec!["arbor".into(), "bash".into(), "health".into()],
);
let data: GuidedErrorData = serde_json::from_str(
error.data().unwrap().get()
).unwrap();
assert_eq!(data.try_request.method, "plexus.schema");
assert_eq!(data.context["available_activations"].as_array().unwrap().len(), 3);
}
#[test]
fn test_method_not_found_with_example() {
let error = GuidedError::method_not_found(
"bash",
"foo",
vec!["execute".into()],
Some(("bash_execute", vec![json!("echo hello")])),
);
let data: GuidedErrorData = serde_json::from_str(
error.data().unwrap().get()
).unwrap();
assert_eq!(data.try_request.method, "bash_execute");
assert_eq!(data.try_request.params[0], json!("echo hello"));
}
}