use serde::{Deserialize, Serialize};
use std::fmt;
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InteractionKind {
Authorization,
Unrecognized,
}
impl InteractionKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::Authorization => "authorization",
Self::Unrecognized => "unrecognized",
}
}
pub fn from_wire(kind: &str) -> Self {
match kind {
"authorization" => Self::Authorization,
_ => Self::Unrecognized,
}
}
}
impl Serialize for InteractionKind {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> std::result::Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for InteractionKind {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
Ok(Self::from_wire(&String::deserialize(deserializer)?))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct InteractionReference {
pub kind: InteractionKind,
pub id: String,
#[serde(deserialize_with = "crate::protocol::deserialize_required_nullable")]
pub expires_at_unix_ms: Option<u64>,
}
impl InteractionReference {
pub fn authorization(id: impl Into<String>, expires_at_unix_ms: Option<u64>) -> Self {
Self {
kind: InteractionKind::Authorization,
id: id.into(),
expires_at_unix_ms,
}
}
fn validate(&self) -> std::result::Result<(), &'static str> {
if self.id.is_empty()
|| self.id.len() > 128
|| !self
.id
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'))
{
return Err("interaction id has an invalid format");
}
if self.expires_at_unix_ms == Some(0) {
return Err("interaction expiry must be positive");
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind {
ParseError,
InvalidRequest,
MethodNotFound,
InvalidParams,
Internal,
UnsupportedVersion,
CapabilityRequired,
DeadlineExceeded,
Cancelled,
Unavailable,
PermissionDenied,
InteractionRequired,
Conflict,
OperationFailed,
MessageTooLarge,
RepresentationMismatch,
Unrecognized,
}
impl ErrorKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::ParseError => "parse_error",
Self::InvalidRequest => "invalid_request",
Self::MethodNotFound => "method_not_found",
Self::InvalidParams => "invalid_params",
Self::Internal => "internal",
Self::UnsupportedVersion => "unsupported_version",
Self::CapabilityRequired => "capability_required",
Self::DeadlineExceeded => "deadline_exceeded",
Self::Cancelled => "cancelled",
Self::Unavailable => "unavailable",
Self::PermissionDenied => "permission_denied",
Self::InteractionRequired => "interaction_required",
Self::Conflict => "conflict",
Self::OperationFailed => "operation_failed",
Self::MessageTooLarge => "message_too_large",
Self::RepresentationMismatch => "representation_mismatch",
Self::Unrecognized => "unrecognized",
}
}
pub fn from_wire(kind: &str) -> Self {
Self::DEFINED
.iter()
.copied()
.find(|defined| defined.as_str() == kind)
.unwrap_or(Self::Unrecognized)
}
pub fn from_code(code: i32) -> Option<Self> {
Self::DEFINED
.iter()
.copied()
.find(|defined| defined.code() == code)
}
pub const DEFINED: &'static [Self] = &[
Self::ParseError,
Self::InvalidRequest,
Self::MethodNotFound,
Self::InvalidParams,
Self::Internal,
Self::UnsupportedVersion,
Self::CapabilityRequired,
Self::DeadlineExceeded,
Self::Cancelled,
Self::Unavailable,
Self::PermissionDenied,
Self::InteractionRequired,
Self::Conflict,
Self::OperationFailed,
Self::MessageTooLarge,
Self::RepresentationMismatch,
];
pub const fn code(self) -> i32 {
match self {
Self::ParseError => -32700,
Self::InvalidRequest => -32600,
Self::MethodNotFound => -32601,
Self::InvalidParams => -32602,
Self::Internal => -32603,
Self::UnsupportedVersion => -32000,
Self::CapabilityRequired => -32001,
Self::DeadlineExceeded => -32002,
Self::Cancelled => -32003,
Self::Unavailable => -32004,
Self::PermissionDenied => -32005,
Self::InteractionRequired => -32006,
Self::Conflict => -32007,
Self::OperationFailed => -32008,
Self::MessageTooLarge => -32009,
Self::RepresentationMismatch => -32010,
Self::Unrecognized => 0,
}
}
pub const fn message(self) -> &'static str {
match self {
Self::ParseError => "parse error",
Self::InvalidRequest => "invalid request",
Self::MethodNotFound => "method not found",
Self::InvalidParams => "invalid params",
Self::Internal => "internal error",
Self::UnsupportedVersion => "unsupported version",
Self::CapabilityRequired => "capability required",
Self::DeadlineExceeded => "deadline exceeded",
Self::Cancelled => "cancelled",
Self::Unavailable => "unavailable",
Self::PermissionDenied => "permission denied",
Self::InteractionRequired => "interaction required",
Self::Conflict => "conflict",
Self::OperationFailed => "operation failed",
Self::MessageTooLarge => "message too large",
Self::RepresentationMismatch => "representation mismatch",
Self::Unrecognized => "unrecognized error",
}
}
pub const fn retryable_by_default(self) -> bool {
matches!(self, Self::Unavailable)
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.message())
}
}
impl Serialize for ErrorKind {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> std::result::Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for ErrorKind {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
Ok(Self::from_wire(&String::deserialize(deserializer)?))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ErrorData {
pub kind: ErrorKind,
pub retryable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry_after_ms: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interaction: Option<InteractionReference>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RpcError {
pub code: i32,
pub message: String,
pub data: ErrorData,
}
impl RpcError {
pub fn new(kind: ErrorKind) -> Self {
Self {
code: kind.code(),
message: kind.message().to_string(),
data: ErrorData {
kind,
retryable: kind.retryable_by_default(),
retry_after_ms: None,
interaction: None,
},
}
}
pub fn interaction_required(reference: Option<InteractionReference>) -> Self {
let mut error = Self::new(ErrorKind::InteractionRequired);
error.data.interaction = reference;
error
}
pub fn unavailable(retry_after_ms: Option<u64>) -> Self {
let mut error = Self::new(ErrorKind::Unavailable);
error.data.retry_after_ms = retry_after_ms;
error
}
pub fn validate(&self) -> std::result::Result<(), &'static str> {
if self.data.kind == ErrorKind::Unrecognized {
if ErrorKind::from_code(self.code).is_some() {
return Err("defined error code arrived with an undefined kind");
}
} else if self.code != self.data.kind.code() {
return Err("error code does not match error kind");
}
if self.message.is_empty() || self.message.len() > 256 {
return Err("error message has an invalid byte length");
}
if let Some(retry_after_ms) = self.data.retry_after_ms {
if !matches!(
self.data.kind,
ErrorKind::Unavailable | ErrorKind::Unrecognized
) {
return Err("retry_after_ms is only valid for unavailable");
}
if retry_after_ms == 0 {
return Err("retry_after_ms must be positive");
}
}
if let Some(interaction) = &self.data.interaction {
if self.data.kind != ErrorKind::InteractionRequired {
return Err("interaction is only valid for interaction_required");
}
interaction.validate()?;
}
Ok(())
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("I/O error")]
Io(#[from] std::io::Error),
#[error("protocol error: {0}")]
Protocol(&'static str),
#[error("protocol error: {0}")]
ProtocolOwned(String),
#[error("remote {0:?}")]
Remote(RpcError),
#[error("request cancelled")]
Cancelled,
#[error("request deadline exceeded")]
DeadlineExceeded,
#[error("endpoint unavailable")]
Unavailable,
#[error("session is closed")]
Closed,
}
impl Error {
pub const fn rpc_kind(&self) -> Option<ErrorKind> {
match self {
Self::Remote(error) => Some(error.data.kind),
Self::Cancelled => Some(ErrorKind::Cancelled),
Self::DeadlineExceeded => Some(ErrorKind::DeadlineExceeded),
Self::Unavailable => Some(ErrorKind::Unavailable),
Self::Io(_) | Self::Protocol(_) | Self::ProtocolOwned(_) | Self::Closed => None,
}
}
pub const fn stable_message(&self) -> &'static str {
match self {
Self::Io(_) => "I/O error",
Self::Protocol(_) | Self::ProtocolOwned(_) => "protocol error",
Self::Remote(_) => "remote error",
Self::Cancelled => "cancelled",
Self::DeadlineExceeded => "deadline exceeded",
Self::Unavailable => "unavailable",
Self::Closed => "session closed",
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rpc_kind_preserves_closed_remote_semantics() {
for kind in [
ErrorKind::InteractionRequired,
ErrorKind::PermissionDenied,
ErrorKind::Conflict,
ErrorKind::Unavailable,
] {
let error = if kind == ErrorKind::Unavailable {
Error::Unavailable
} else {
Error::Remote(RpcError::new(kind))
};
assert_eq!(error.rpc_kind(), Some(kind));
assert_eq!(kind.to_string(), kind.message());
assert!(!kind.as_str().contains(' '));
}
assert_eq!(Error::Closed.rpc_kind(), None);
assert_eq!(Error::Protocol("bad frame").rpc_kind(), None);
}
#[test]
fn an_undefined_kind_decodes_instead_of_failing_the_frame() {
let later: RpcError = serde_json::from_str(
r#"{"code":-32011,"message":"dynamic session required",
"data":{"kind":"dynamic_session_required","retryable":false}}"#,
)
.unwrap();
assert_eq!(later.data.kind, ErrorKind::Unrecognized);
later.validate().unwrap();
assert!(!later.data.kind.retryable_by_default());
let mismatched: RpcError = serde_json::from_str(
r#"{"code":-32005,"message":"permission denied",
"data":{"kind":"something_else","retryable":false}}"#,
)
.unwrap();
assert_eq!(mismatched.data.kind, ErrorKind::Unrecognized);
assert!(mismatched.validate().is_err());
for kind in ErrorKind::DEFINED {
assert_eq!(ErrorKind::from_wire(kind.as_str()), *kind);
assert_eq!(ErrorKind::from_code(kind.code()), Some(*kind));
}
assert_eq!(ErrorKind::from_code(-32011), None);
}
#[test]
fn retry_after_must_be_positive() {
assert!(RpcError::unavailable(Some(1)).validate().is_ok());
assert!(RpcError::unavailable(Some(0)).validate().is_err());
}
#[test]
fn interaction_references_are_bounded_and_kind_specific() {
let reference = InteractionReference::authorization("apr_7K3M", Some(1));
let error = RpcError::interaction_required(Some(reference.clone()));
error.validate().unwrap();
let encoded = serde_json::to_string(&error).unwrap();
let decoded: RpcError = serde_json::from_str(&encoded).unwrap();
assert_eq!(decoded.data.interaction, Some(reference));
let mut wrong_kind = RpcError::new(ErrorKind::PermissionDenied);
wrong_kind.data.interaction = Some(InteractionReference::authorization("apr_1", None));
assert!(wrong_kind.validate().is_err());
assert!(
RpcError::interaction_required(Some(InteractionReference::authorization(
"terminal escape \u{1b}",
None,
)))
.validate()
.is_err()
);
}
#[test]
fn later_interaction_kinds_decode_without_becoming_authority() {
let error: RpcError = serde_json::from_str(
r#"{"code":-32006,"message":"interaction required",
"data":{"kind":"interaction_required","retryable":false,
"interaction":{"kind":"later_kind","id":"ref_1",
"expires_at_unix_ms":null}}}"#,
)
.unwrap();
assert_eq!(
error.data.interaction.as_ref().unwrap().kind,
InteractionKind::Unrecognized
);
error.validate().unwrap();
}
}