chore: initial sanitized public snapshot
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
//! 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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user