chore: initial sanitized public snapshot
This commit is contained in:
@@ -0,0 +1,641 @@
|
||||
//! Method validation and compatibility-oriented RPC handler stubs.
|
||||
#![expect(
|
||||
clippy::needless_pass_by_value,
|
||||
reason = "handler signatures intentionally mirror transport payloads and centralized compat wording"
|
||||
)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use aria2_rust_pro_compat::version_line;
|
||||
|
||||
use crate::{
|
||||
jsonrpc::{
|
||||
JsonRpcNotification, JsonRpcRequest, SYNTHETIC_INVALID_PARAMS_METHOD,
|
||||
SYNTHETIC_INVALID_REQUEST_METHOD,
|
||||
},
|
||||
methods::{
|
||||
RpcMethod, is_required_rpc_method, rpc_method, rpc_method_names, rpc_notification_names,
|
||||
},
|
||||
model::{RpcError, RpcErrorCode, RpcErrorKind, RpcMeta, RpcResultEnvelope, RpcValue},
|
||||
xmlrpc::{
|
||||
XmlRpcFault, XmlRpcMethodCall, XmlRpcMethodResponse, rpc_value_to_xmlrpc,
|
||||
xmlrpc_value_to_rpc,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
/// Context passed into handler execution.
|
||||
pub struct RpcHandlerContext {
|
||||
/// Authentication state derived from the active transport.
|
||||
pub auth: crate::model::RpcAuthContext,
|
||||
/// Per-request metadata propagated through the stack.
|
||||
pub meta: RpcMeta,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
/// Registry of compatibility-oriented RPC handlers.
|
||||
pub struct RpcHandlerRegistry;
|
||||
|
||||
impl RpcHandlerRegistry {
|
||||
#[must_use]
|
||||
/// Handles a JSON-RPC request and returns a normalized envelope.
|
||||
pub fn handle_json(self, request: JsonRpcRequest, ctx: RpcHandlerContext) -> RpcResultEnvelope {
|
||||
let _ = self;
|
||||
let _ = ctx;
|
||||
let synthetic_error_message = request
|
||||
.params
|
||||
.first()
|
||||
.and_then(|value| match value {
|
||||
RpcValue::String(message) => Some(message.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or("Invalid Request.");
|
||||
match request.method.as_str() {
|
||||
SYNTHETIC_INVALID_REQUEST_METHOD => RpcResultEnvelope {
|
||||
result: None,
|
||||
error: Some(RpcError {
|
||||
code: RpcErrorCode::InvalidRequest,
|
||||
kind: RpcErrorKind::InvalidParams,
|
||||
message: synthetic_error_message.to_owned(),
|
||||
}),
|
||||
},
|
||||
SYNTHETIC_INVALID_PARAMS_METHOD => RpcResultEnvelope {
|
||||
result: None,
|
||||
error: Some(RpcError::invalid_params(synthetic_error_message)),
|
||||
},
|
||||
_ => Self::handle_method_request(request),
|
||||
}
|
||||
}
|
||||
|
||||
/// Routes a resolved method name through the compatibility stub table.
|
||||
fn handle_method_request(request: JsonRpcRequest) -> RpcResultEnvelope {
|
||||
let Some(method) = rpc_method(&request.method) else {
|
||||
return unknown_or_stubbed_method(&request.method);
|
||||
};
|
||||
|
||||
if let Some(error) = validate_method_params(method, &request.params) {
|
||||
return error_envelope(error);
|
||||
}
|
||||
|
||||
compatibility_response(method)
|
||||
}
|
||||
|
||||
/// Handles a JSON-RPC notification.
|
||||
pub fn handle_notification(self, _notification: JsonRpcNotification, _ctx: RpcHandlerContext) {
|
||||
let _ = self;
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Handles an XML-RPC method call by reusing the JSON handler path.
|
||||
pub fn handle_xml(
|
||||
self,
|
||||
request: XmlRpcMethodCall,
|
||||
ctx: RpcHandlerContext,
|
||||
) -> XmlRpcMethodResponse {
|
||||
let meta = ctx.meta.clone();
|
||||
let envelope = self.handle_json(
|
||||
JsonRpcRequest {
|
||||
jsonrpc: Some("2.0".to_owned()),
|
||||
id: None,
|
||||
method: request.method_name,
|
||||
params: request
|
||||
.params
|
||||
.into_iter()
|
||||
.map(|p| xmlrpc_value_to_rpc(p.value))
|
||||
.collect(),
|
||||
meta,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
|
||||
match envelope.error {
|
||||
Some(error) => XmlRpcMethodResponse {
|
||||
value: None,
|
||||
fault: Some(XmlRpcFault {
|
||||
code: 1,
|
||||
message: error.message.clone(),
|
||||
error: Some(error),
|
||||
}),
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
None => XmlRpcMethodResponse {
|
||||
value: envelope.result.map(rpc_value_to_xmlrpc),
|
||||
fault: None,
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces the canned compatibility response for a resolved RPC method.
|
||||
fn compatibility_response(method: RpcMethod) -> RpcResultEnvelope {
|
||||
match method {
|
||||
RpcMethod::Aria2AddUri
|
||||
| RpcMethod::Aria2AddTorrent
|
||||
| RpcMethod::Aria2AddMetalink
|
||||
| RpcMethod::Aria2ChangeGlobalOption
|
||||
| RpcMethod::Aria2ChangeOption
|
||||
| RpcMethod::Aria2SaveSession
|
||||
| RpcMethod::Aria2Shutdown
|
||||
| RpcMethod::Aria2ForceShutdown => success_envelope(RpcValue::Null),
|
||||
RpcMethod::Aria2Remove
|
||||
| RpcMethod::Aria2ForceRemove
|
||||
| RpcMethod::Aria2Pause
|
||||
| RpcMethod::Aria2PauseAll
|
||||
| RpcMethod::Aria2ForcePause
|
||||
| RpcMethod::Aria2ForcePauseAll
|
||||
| RpcMethod::Aria2Unpause
|
||||
| RpcMethod::Aria2UnpauseAll => success_envelope(RpcValue::Bool(true)),
|
||||
RpcMethod::Aria2TellStatus
|
||||
| RpcMethod::Aria2TellGlobalStat
|
||||
| RpcMethod::Aria2GetGlobalOption
|
||||
| RpcMethod::Aria2GetOption
|
||||
| RpcMethod::Aria2GetSessionInfo => success_envelope(RpcValue::Object(BTreeMap::new())),
|
||||
RpcMethod::Aria2TellActive
|
||||
| RpcMethod::Aria2TellWaiting
|
||||
| RpcMethod::Aria2TellStopped
|
||||
| RpcMethod::Aria2GetUris
|
||||
| RpcMethod::Aria2GetFiles
|
||||
| RpcMethod::Aria2GetPeers
|
||||
| RpcMethod::Aria2GetServers
|
||||
| RpcMethod::SystemMulticall => success_envelope(RpcValue::Array(Vec::new())),
|
||||
RpcMethod::Aria2ChangeUri => success_envelope(RpcValue::Array(vec![
|
||||
RpcValue::Number(0),
|
||||
RpcValue::Number(0),
|
||||
])),
|
||||
RpcMethod::Aria2GetVersion => success_envelope(RpcValue::Object(BTreeMap::from([
|
||||
("version".to_owned(), RpcValue::String(version_line())),
|
||||
("rpcVersion".to_owned(), RpcValue::String("2.0".to_owned())),
|
||||
]))),
|
||||
RpcMethod::SystemListMethods => success_envelope(RpcValue::Array(
|
||||
rpc_method_names()
|
||||
.into_iter()
|
||||
.map(|name| RpcValue::String(name.to_owned()))
|
||||
.collect(),
|
||||
)),
|
||||
RpcMethod::SystemListNotifications => success_envelope(RpcValue::Array(
|
||||
rpc_notification_names()
|
||||
.into_iter()
|
||||
.map(|name| RpcValue::String(name.to_owned()))
|
||||
.collect(),
|
||||
)),
|
||||
_ => unknown_or_stubbed_method(method.as_str()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a successful result payload in a normalized handler envelope.
|
||||
const fn success_envelope(result: RpcValue) -> RpcResultEnvelope {
|
||||
RpcResultEnvelope {
|
||||
result: Some(result),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps an error payload in a normalized handler envelope.
|
||||
const fn error_envelope(error: RpcError) -> RpcResultEnvelope {
|
||||
RpcResultEnvelope {
|
||||
result: None,
|
||||
error: Some(error),
|
||||
}
|
||||
}
|
||||
|
||||
/// Distinguishes required-but-stubbed methods from truly unknown compatibility names.
|
||||
fn unknown_or_stubbed_method(method: &str) -> RpcResultEnvelope {
|
||||
if is_required_rpc_method(method) {
|
||||
error_envelope(RpcError::unsupported("rpc method stubbed"))
|
||||
} else {
|
||||
error_envelope(RpcError::unknown_method(method))
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates the documented positional parameter contract for compatibility methods.
|
||||
fn validate_method_params(method: RpcMethod, params: &[RpcValue]) -> Option<RpcError> {
|
||||
match method {
|
||||
RpcMethod::SystemListMethods if !params.is_empty() => Some(RpcError::invalid_params(
|
||||
"system.listMethods takes no parameters",
|
||||
)),
|
||||
RpcMethod::SystemListNotifications if !params.is_empty() => Some(RpcError::invalid_params(
|
||||
"system.listNotifications takes no parameters",
|
||||
)),
|
||||
RpcMethod::Aria2GetVersion if !params.is_empty() => Some(RpcError::invalid_params(
|
||||
"aria2.getVersion takes no parameters",
|
||||
)),
|
||||
RpcMethod::Aria2GetSessionInfo if !params.is_empty() => Some(RpcError::invalid_params(
|
||||
"aria2.getSessionInfo takes no parameters",
|
||||
)),
|
||||
RpcMethod::Aria2TellGlobalStat if !params.is_empty() => Some(RpcError::invalid_params(
|
||||
"aria2.getGlobalStat takes no parameters",
|
||||
)),
|
||||
RpcMethod::SystemMulticall if params.is_empty() => Some(RpcError::invalid_params(
|
||||
"system.multicall requires method specs",
|
||||
)),
|
||||
RpcMethod::SystemMulticall if !matches!(params.first(), Some(RpcValue::Array(_))) => Some(
|
||||
RpcError::invalid_params("system.multicall expected array of method specs"),
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Unit tests for transport-neutral RPC handler behavior.
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
model::RpcAuthContext,
|
||||
xmlrpc::{XmlRpcMember, XmlRpcParam, XmlRpcValue, xmlrpc_value_to_rpc},
|
||||
};
|
||||
|
||||
/// Builds a default handler context for unit tests.
|
||||
fn ctx() -> RpcHandlerContext {
|
||||
RpcHandlerContext {
|
||||
auth: RpcAuthContext::default(),
|
||||
meta: RpcMeta::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that unknown `XML-RPC` methods return a shared fault payload.
|
||||
fn handle_xml_unknown_method_returns_fault_with_error_payload() {
|
||||
let registry = RpcHandlerRegistry;
|
||||
let response = registry.handle_xml(
|
||||
XmlRpcMethodCall {
|
||||
method_name: "aria2.notFound".to_owned(),
|
||||
params: Vec::new(),
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
ctx(),
|
||||
);
|
||||
|
||||
assert!(response.value.is_none());
|
||||
let fault = response.fault.expect("expected fault");
|
||||
assert_eq!(fault.code, 1);
|
||||
let expected = RpcError::unknown_method("aria2.notFound");
|
||||
assert_eq!(fault.message, expected.message);
|
||||
assert_eq!(fault.error, Some(expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that nested `XML-RPC` values convert with the shared compatibility rules.
|
||||
fn xml_param_conversion_handles_nested_values_with_shared_rules() {
|
||||
let nested = XmlRpcValue::Struct(vec![XmlRpcMember {
|
||||
name: "outer".to_owned(),
|
||||
value: XmlRpcValue::Array(vec![
|
||||
XmlRpcValue::Double(3.25),
|
||||
XmlRpcValue::Base64(vec![0x41, 0x42]),
|
||||
XmlRpcValue::Struct(vec![XmlRpcMember {
|
||||
name: "inner".to_owned(),
|
||||
value: XmlRpcValue::Bool(true),
|
||||
}]),
|
||||
]),
|
||||
}]);
|
||||
|
||||
let expected = RpcValue::Object(BTreeMap::from([(
|
||||
"outer".to_owned(),
|
||||
RpcValue::Array(vec![
|
||||
RpcValue::String("3.25".to_owned()),
|
||||
RpcValue::String("QUI=".to_owned()),
|
||||
RpcValue::Object(BTreeMap::from([("inner".to_owned(), RpcValue::Bool(true))])),
|
||||
]),
|
||||
)]));
|
||||
|
||||
assert_eq!(xmlrpc_value_to_rpc(nested), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that nested array and struct parameters are accepted for `XML-RPC` addUri.
|
||||
fn handle_xml_accepts_nested_struct_and_array_params() {
|
||||
let registry = RpcHandlerRegistry;
|
||||
let response = registry.handle_xml(
|
||||
XmlRpcMethodCall {
|
||||
method_name: "aria2.addUri".to_owned(),
|
||||
params: vec![XmlRpcParam {
|
||||
value: XmlRpcValue::Array(vec![XmlRpcValue::Struct(vec![XmlRpcMember {
|
||||
name: "k".to_owned(),
|
||||
value: XmlRpcValue::Array(vec![XmlRpcValue::Int(1), XmlRpcValue::Nil]),
|
||||
}])]),
|
||||
}],
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
ctx(),
|
||||
);
|
||||
|
||||
assert!(response.fault.is_none());
|
||||
assert!(matches!(response.value, Some(XmlRpcValue::Nil)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that `system.listMethods` keeps the upstream public method ordering.
|
||||
fn handle_json_list_methods_matches_upstream_order() {
|
||||
let registry = RpcHandlerRegistry;
|
||||
let response = registry.handle_json(
|
||||
JsonRpcRequest {
|
||||
jsonrpc: Some("2.0".to_owned()),
|
||||
id: None,
|
||||
method: "system.listMethods".to_owned(),
|
||||
params: Vec::new(),
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
ctx(),
|
||||
);
|
||||
|
||||
match response.result {
|
||||
Some(RpcValue::Array(items)) => {
|
||||
let names = items
|
||||
.into_iter()
|
||||
.map(|item| match item {
|
||||
RpcValue::String(name) => name,
|
||||
other => panic!("unexpected method item: {other:?}"),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
names,
|
||||
vec![
|
||||
"aria2.addUri".to_owned(),
|
||||
"aria2.addTorrent".to_owned(),
|
||||
"aria2.getPeers".to_owned(),
|
||||
"aria2.addMetalink".to_owned(),
|
||||
"aria2.remove".to_owned(),
|
||||
"aria2.pause".to_owned(),
|
||||
"aria2.forcePause".to_owned(),
|
||||
"aria2.pauseAll".to_owned(),
|
||||
"aria2.forcePauseAll".to_owned(),
|
||||
"aria2.unpause".to_owned(),
|
||||
"aria2.unpauseAll".to_owned(),
|
||||
"aria2.forceRemove".to_owned(),
|
||||
"aria2.changePosition".to_owned(),
|
||||
"aria2.tellStatus".to_owned(),
|
||||
"aria2.getUris".to_owned(),
|
||||
"aria2.getFiles".to_owned(),
|
||||
"aria2.getServers".to_owned(),
|
||||
"aria2.tellActive".to_owned(),
|
||||
"aria2.tellWaiting".to_owned(),
|
||||
"aria2.tellStopped".to_owned(),
|
||||
"aria2.getOption".to_owned(),
|
||||
"aria2.changeUri".to_owned(),
|
||||
"aria2.changeOption".to_owned(),
|
||||
"aria2.getGlobalOption".to_owned(),
|
||||
"aria2.changeGlobalOption".to_owned(),
|
||||
"aria2.purgeDownloadResult".to_owned(),
|
||||
"aria2.removeDownloadResult".to_owned(),
|
||||
"aria2.getVersion".to_owned(),
|
||||
"aria2.getSessionInfo".to_owned(),
|
||||
"aria2.shutdown".to_owned(),
|
||||
"aria2.forceShutdown".to_owned(),
|
||||
"aria2.getGlobalStat".to_owned(),
|
||||
"aria2.saveSession".to_owned(),
|
||||
"system.multicall".to_owned(),
|
||||
"system.listMethods".to_owned(),
|
||||
"system.listNotifications".to_owned(),
|
||||
]
|
||||
);
|
||||
}
|
||||
other => panic!("unexpected listMethods result: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that `system.listNotifications` returns the upstream aria2 names.
|
||||
fn handle_json_list_notifications_matches_upstream_aria2_names() {
|
||||
let registry = RpcHandlerRegistry;
|
||||
let response = registry.handle_json(
|
||||
JsonRpcRequest {
|
||||
jsonrpc: Some("2.0".to_owned()),
|
||||
id: None,
|
||||
method: "system.listNotifications".to_owned(),
|
||||
params: Vec::new(),
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
ctx(),
|
||||
);
|
||||
|
||||
match response.result {
|
||||
Some(RpcValue::Array(items)) => {
|
||||
let names = items
|
||||
.into_iter()
|
||||
.map(|item| match item {
|
||||
RpcValue::String(name) => name,
|
||||
other => panic!("unexpected notification item: {other:?}"),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
names,
|
||||
vec![
|
||||
"aria2.onDownloadStart".to_owned(),
|
||||
"aria2.onDownloadPause".to_owned(),
|
||||
"aria2.onDownloadStop".to_owned(),
|
||||
"aria2.onDownloadComplete".to_owned(),
|
||||
"aria2.onDownloadError".to_owned(),
|
||||
"aria2.onBtDownloadComplete".to_owned(),
|
||||
]
|
||||
);
|
||||
}
|
||||
other => panic!("unexpected listNotifications result: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that `system.listMethods` rejects unexpected parameters.
|
||||
fn handle_json_list_methods_rejects_unexpected_params() {
|
||||
let registry = RpcHandlerRegistry;
|
||||
let response = registry.handle_json(
|
||||
JsonRpcRequest {
|
||||
jsonrpc: Some("2.0".to_owned()),
|
||||
id: None,
|
||||
method: "system.listMethods".to_owned(),
|
||||
params: vec![RpcValue::Bool(true)],
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
ctx(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
response.error,
|
||||
Some(RpcError::invalid_params(
|
||||
"system.listMethods takes no parameters"
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that `system.listNotifications` rejects unexpected parameters.
|
||||
fn handle_json_list_notifications_rejects_unexpected_params() {
|
||||
let registry = RpcHandlerRegistry;
|
||||
let response = registry.handle_json(
|
||||
JsonRpcRequest {
|
||||
jsonrpc: Some("2.0".to_owned()),
|
||||
id: None,
|
||||
method: "system.listNotifications".to_owned(),
|
||||
params: vec![RpcValue::Bool(true)],
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
ctx(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
response.error,
|
||||
Some(RpcError::invalid_params(
|
||||
"system.listNotifications takes no parameters"
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that `aria2.getVersion` rejects unexpected parameters.
|
||||
fn handle_json_get_version_rejects_unexpected_params() {
|
||||
let registry = RpcHandlerRegistry;
|
||||
let response = registry.handle_json(
|
||||
JsonRpcRequest {
|
||||
jsonrpc: Some("2.0".to_owned()),
|
||||
id: None,
|
||||
method: "aria2.getVersion".to_owned(),
|
||||
params: vec![RpcValue::String("token:abc".to_owned())],
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
ctx(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
response.error,
|
||||
Some(RpcError::invalid_params(
|
||||
"aria2.getVersion takes no parameters"
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that `system.multicall` rejects a missing method-spec array.
|
||||
fn handle_json_multicall_rejects_missing_method_specs() {
|
||||
let registry = RpcHandlerRegistry;
|
||||
let response = registry.handle_json(
|
||||
JsonRpcRequest {
|
||||
jsonrpc: Some("2.0".to_owned()),
|
||||
id: None,
|
||||
method: "system.multicall".to_owned(),
|
||||
params: Vec::new(),
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
ctx(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
response.error,
|
||||
Some(RpcError::invalid_params(
|
||||
"system.multicall requires method specs"
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that the legacy multicall alias still enforces array-shaped method specs.
|
||||
fn handle_json_legacy_multicall_alias_rejects_non_array_method_specs() {
|
||||
let registry = RpcHandlerRegistry;
|
||||
let response = registry.handle_json(
|
||||
JsonRpcRequest {
|
||||
jsonrpc: Some("2.0".to_owned()),
|
||||
id: None,
|
||||
method: "aria2.multicall".to_owned(),
|
||||
params: vec![RpcValue::Bool(true)],
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
ctx(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
response.error,
|
||||
Some(RpcError::invalid_params(
|
||||
"system.multicall expected array of method specs"
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that the synthetic invalid-request marker becomes a `JSON-RPC` invalid-request error.
|
||||
fn handle_json_synthetic_invalid_request_returns_jsonrpc_invalid_request_error() {
|
||||
let registry = RpcHandlerRegistry;
|
||||
let response = registry.handle_json(
|
||||
JsonRpcRequest {
|
||||
jsonrpc: Some("2.0".to_owned()),
|
||||
id: Some(crate::jsonrpc::JsonRpcId::Null),
|
||||
method: SYNTHETIC_INVALID_REQUEST_METHOD.to_owned(),
|
||||
params: vec![RpcValue::String("Invalid Request.".to_owned())],
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
ctx(),
|
||||
);
|
||||
|
||||
assert!(response.result.is_none());
|
||||
assert_eq!(
|
||||
response.error,
|
||||
Some(RpcError {
|
||||
code: RpcErrorCode::InvalidRequest,
|
||||
kind: RpcErrorKind::InvalidParams,
|
||||
message: "Invalid Request.".to_owned(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that the synthetic invalid-params marker becomes a `JSON-RPC` invalid-params error.
|
||||
fn handle_json_synthetic_invalid_params_returns_jsonrpc_invalid_params_error() {
|
||||
let registry = RpcHandlerRegistry;
|
||||
let response = registry.handle_json(
|
||||
JsonRpcRequest {
|
||||
jsonrpc: Some("2.0".to_owned()),
|
||||
id: Some(crate::jsonrpc::JsonRpcId::String("q2".to_owned())),
|
||||
method: SYNTHETIC_INVALID_PARAMS_METHOD.to_owned(),
|
||||
params: vec![RpcValue::String("Invalid params.".to_owned())],
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
ctx(),
|
||||
);
|
||||
|
||||
assert!(response.result.is_none());
|
||||
assert_eq!(
|
||||
response.error,
|
||||
Some(RpcError {
|
||||
code: RpcErrorCode::InvalidParams,
|
||||
kind: RpcErrorKind::InvalidParams,
|
||||
message: "Invalid params.".to_owned(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Verifies that `XML-RPC` listNotifications returns the prefixed aria2 event names.
|
||||
fn handle_xml_list_notifications_returns_prefixed_names() {
|
||||
let registry = RpcHandlerRegistry;
|
||||
let response = registry.handle_xml(
|
||||
XmlRpcMethodCall {
|
||||
method_name: "system.listNotifications".to_owned(),
|
||||
params: Vec::new(),
|
||||
meta: RpcMeta::default(),
|
||||
},
|
||||
ctx(),
|
||||
);
|
||||
|
||||
assert!(response.fault.is_none());
|
||||
match response.value {
|
||||
Some(XmlRpcValue::Array(items)) => {
|
||||
let names = items
|
||||
.into_iter()
|
||||
.map(|item| match item {
|
||||
XmlRpcValue::String(name) => name,
|
||||
other => panic!("unexpected XML notification item: {other:?}"),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
names,
|
||||
vec![
|
||||
"aria2.onDownloadStart".to_owned(),
|
||||
"aria2.onDownloadPause".to_owned(),
|
||||
"aria2.onDownloadStop".to_owned(),
|
||||
"aria2.onDownloadComplete".to_owned(),
|
||||
"aria2.onDownloadError".to_owned(),
|
||||
"aria2.onBtDownloadComplete".to_owned(),
|
||||
]
|
||||
);
|
||||
}
|
||||
other => panic!("unexpected XML listNotifications result: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user