Files
aria2-rust-pro/crates/aria2-rust-pro-rpc/src/model.rs
T

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");
}
}