642 lines
23 KiB
Rust
642 lines
23 KiB
Rust
//! 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:?}"),
|
|
}
|
|
}
|
|
}
|