218 lines
6.7 KiB
Rust
218 lines
6.7 KiB
Rust
//! Shared value, error, and metadata types used across RPC transports.
|
|
#![expect(
|
|
clippy::redundant_pub_crate,
|
|
reason = "shared RPC model items stay crate-internal while retaining explicit visibilities"
|
|
)]
|
|
use std::collections::BTreeMap;
|
|
|
|
/// Canonical RPC option map shape for aria2-compatible option payloads.
|
|
pub type RpcOptionMap = BTreeMap<String, String>;
|
|
/// BitTorrent-specific status fields that aria2-compatible clients may request.
|
|
pub(super) const BT_STATUS_FIELDS: &[&str] = &[
|
|
"infoHash",
|
|
"numSeeders",
|
|
"seeder",
|
|
"connections",
|
|
"activeSegments",
|
|
"pieceLength",
|
|
"numPieces",
|
|
"completedPieces",
|
|
"bitfield",
|
|
"announceList",
|
|
"followedBy",
|
|
"following",
|
|
"belongsTo",
|
|
"verifiedLength",
|
|
"verifyIntegrityPending",
|
|
"isBt",
|
|
"metadataOnly",
|
|
"magnetUri",
|
|
"creationDate",
|
|
"comment",
|
|
"mode",
|
|
];
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
/// Transport-neutral RPC value representation.
|
|
pub enum RpcValue {
|
|
/// Null or absent value.
|
|
Null,
|
|
/// Boolean scalar value.
|
|
Bool(bool),
|
|
/// Signed integer value.
|
|
Number(i64),
|
|
/// UTF-8 string value.
|
|
String(String),
|
|
/// Ordered list of nested values.
|
|
Array(Vec<Self>),
|
|
/// Map of named nested values.
|
|
Object(BTreeMap<String, Self>),
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
/// Broad category for RPC failures.
|
|
pub enum RpcErrorKind {
|
|
/// The method name is not recognized.
|
|
UnknownMethod,
|
|
/// Parameters are malformed or unsupported.
|
|
InvalidParams,
|
|
/// Authentication or authorization failed.
|
|
Unauthorized,
|
|
/// The method exists but is not implemented by this backend.
|
|
Unsupported,
|
|
/// The backend encountered an unexpected internal failure.
|
|
Internal,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
/// JSON-RPC compatible error code surface.
|
|
pub enum RpcErrorCode {
|
|
/// Invalid JSON payload syntax.
|
|
ParseError = -32700,
|
|
/// Payload shape is not a valid JSON-RPC request.
|
|
InvalidRequest = -32600,
|
|
/// Method name is unknown.
|
|
MethodNotFound = -32601,
|
|
/// Parameters are invalid for the method.
|
|
InvalidParams = -32602,
|
|
/// Generic server-side failure.
|
|
InternalError = -32603,
|
|
/// Application-defined aria2-compatible failure.
|
|
ApplicationError = -32000,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
/// Normalized error payload shared across transports.
|
|
pub struct RpcError {
|
|
/// JSON-RPC compatible numeric code.
|
|
pub code: RpcErrorCode,
|
|
/// Broad error classification.
|
|
pub kind: RpcErrorKind,
|
|
/// Human-readable failure message.
|
|
pub message: String,
|
|
}
|
|
|
|
impl RpcError {
|
|
#[must_use]
|
|
/// Builds a method-not-found error in aria2-compatible wording.
|
|
pub fn unknown_method(method: &str) -> Self {
|
|
Self {
|
|
code: RpcErrorCode::MethodNotFound,
|
|
kind: RpcErrorKind::UnknownMethod,
|
|
message: format!("Method not found: {method}"),
|
|
}
|
|
}
|
|
#[must_use]
|
|
/// Builds a parse error with the provided message.
|
|
pub fn parse_error(message: &str) -> Self {
|
|
Self {
|
|
code: RpcErrorCode::ParseError,
|
|
kind: RpcErrorKind::InvalidParams,
|
|
message: message.to_owned(),
|
|
}
|
|
}
|
|
#[must_use]
|
|
/// Builds an invalid-request error with the provided message.
|
|
pub fn invalid_request(message: &str) -> Self {
|
|
Self {
|
|
code: RpcErrorCode::InvalidRequest,
|
|
kind: RpcErrorKind::InvalidParams,
|
|
message: message.to_owned(),
|
|
}
|
|
}
|
|
#[must_use]
|
|
/// Builds an unsupported-method error with the provided message.
|
|
pub fn unsupported(message: &str) -> Self {
|
|
Self {
|
|
code: RpcErrorCode::ApplicationError,
|
|
kind: RpcErrorKind::Unsupported,
|
|
message: message.to_owned(),
|
|
}
|
|
}
|
|
#[must_use]
|
|
/// Builds an invalid-parameters error with the provided message.
|
|
pub fn invalid_params(message: &str) -> Self {
|
|
Self {
|
|
code: RpcErrorCode::InvalidParams,
|
|
kind: RpcErrorKind::InvalidParams,
|
|
message: message.to_owned(),
|
|
}
|
|
}
|
|
#[must_use]
|
|
/// Builds an authorization failure with the provided message.
|
|
pub fn unauthorized(message: &str) -> Self {
|
|
Self {
|
|
code: RpcErrorCode::ApplicationError,
|
|
kind: RpcErrorKind::Unauthorized,
|
|
message: message.to_owned(),
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
/// Returns the XML-RPC fault code used for this normalized error.
|
|
pub const fn xml_fault_code(&self) -> i32 {
|
|
1
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
/// Transport-neutral result envelope returned by handlers.
|
|
pub struct RpcResultEnvelope {
|
|
/// Successful result payload, if any.
|
|
pub result: Option<RpcValue>,
|
|
/// Error payload, if the request failed.
|
|
pub error: Option<RpcError>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
/// Optional metadata attached to inbound or outbound RPC traffic.
|
|
pub struct RpcMeta {
|
|
/// Correlation identifier propagated across RPC hops.
|
|
pub trace_id: Option<String>,
|
|
/// Logical client identifier, if known.
|
|
pub client: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
/// Authentication context derived from the active transport session.
|
|
pub struct RpcAuthContext {
|
|
/// Secret token presented by the client.
|
|
pub token: Option<String>,
|
|
/// Transport session identifier, if one has been established.
|
|
pub session_id: Option<String>,
|
|
/// Whether the current request is authenticated.
|
|
pub authenticated: bool,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
/// Unit tests for the shared RPC model surface.
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
/// Verifies that unknown methods use the upstream aria2 wording.
|
|
fn unknown_method_uses_upstream_style_message() {
|
|
let error = RpcError::unknown_method("aria2.notFound");
|
|
|
|
assert_eq!(error.code, RpcErrorCode::MethodNotFound);
|
|
assert_eq!(error.kind, RpcErrorKind::UnknownMethod);
|
|
assert_eq!(error.message, "Method not found: aria2.notFound");
|
|
assert_eq!(error.xml_fault_code(), 1);
|
|
}
|
|
|
|
#[test]
|
|
/// Verifies that parse and invalid-request errors keep their transport codes.
|
|
fn parse_and_invalid_request_keep_transport_codes() {
|
|
let parse = RpcError::parse_error("unexpected trailing token");
|
|
let invalid = RpcError::invalid_request("jsonrpc batch request must not be empty");
|
|
|
|
assert_eq!(parse.code, RpcErrorCode::ParseError);
|
|
assert_eq!(parse.kind, RpcErrorKind::InvalidParams);
|
|
assert_eq!(parse.message, "unexpected trailing token");
|
|
|
|
assert_eq!(invalid.code, RpcErrorCode::InvalidRequest);
|
|
assert_eq!(invalid.kind, RpcErrorKind::InvalidParams);
|
|
assert_eq!(invalid.message, "jsonrpc batch request must not be empty");
|
|
}
|
|
}
|