//! 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; /// 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), /// Map of named nested values. Object(BTreeMap), } #[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, /// Error payload, if the request failed. pub error: Option, } #[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, /// Logical client identifier, if known. pub client: Option, } #[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, /// Transport session identifier, if one has been established. pub session_id: Option, /// 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"); } }