use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::envelope::deserialize_unique_value;
use thiserror::Error;
use crate::{
CURRENT_PROTOCOL_VERSION, CorrelationId, ProtocolMetadata, ProtocolMetadataError,
ProtocolVersion,
};
pub const MAX_ERROR_MESSAGE_BYTES: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentErrorCode {
UnsupportedCommand,
UnsupportedRecord,
UnsupportedProtocolVersion,
InvalidCommand,
InvalidInput,
SequenceConflict,
RateLimited,
ProviderUnavailable,
Cancelled,
Internal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RetryClass {
Never,
Immediate,
AfterBackoff,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ProtocolError {
code: AgentErrorCode,
message: String,
retry: RetryClass,
correlation_id: Option<CorrelationId>,
details: ProtocolMetadata,
}
impl ProtocolError {
pub fn new(
code: AgentErrorCode,
message: impl Into<String>,
retry: RetryClass,
) -> Result<Self, ProtocolErrorValidationError> {
let message = message.into();
validate_message(&message)?;
Ok(Self {
code,
message,
retry,
correlation_id: None,
details: ProtocolMetadata::default(),
})
}
pub(crate) fn invalid_command(correlation_id: CorrelationId) -> Self {
Self {
code: AgentErrorCode::InvalidCommand,
message: "command envelope is invalid".to_owned(),
retry: RetryClass::Never,
correlation_id: Some(correlation_id),
details: ProtocolMetadata::default(),
}
}
pub(crate) fn invalid_record(correlation_id: CorrelationId) -> Self {
Self {
code: AgentErrorCode::InvalidInput,
message: "durable session record is invalid".to_owned(),
retry: RetryClass::Never,
correlation_id: Some(correlation_id),
details: ProtocolMetadata::default(),
}
}
pub(crate) fn unsupported_protocol_version(
correlation_id: CorrelationId,
received_version: ProtocolVersion,
) -> Self {
Self {
code: AgentErrorCode::UnsupportedProtocolVersion,
message: "protocol version is not supported".to_owned(),
retry: RetryClass::Never,
correlation_id: Some(correlation_id),
details: ProtocolMetadata::protocol_version_details(&received_version.to_string()),
}
}
pub(crate) fn unsupported_record(correlation_id: CorrelationId, record_type: &str) -> Self {
Self {
code: AgentErrorCode::UnsupportedRecord,
message: "durable record type is not supported".to_owned(),
retry: RetryClass::Never,
correlation_id: Some(correlation_id),
details: ProtocolMetadata::protocol_compatibility_details(Some(record_type)),
}
}
#[must_use]
pub fn unsupported_command(correlation_id: CorrelationId) -> Self {
let details = ProtocolMetadata::protocol_compatibility_details(None);
Self {
code: AgentErrorCode::UnsupportedCommand,
message: "command type is not supported".to_owned(),
retry: RetryClass::Never,
correlation_id: Some(correlation_id),
details,
}
}
#[must_use]
pub fn with_correlation_id(mut self, correlation_id: CorrelationId) -> Self {
self.correlation_id = Some(correlation_id);
self
}
#[must_use]
pub fn with_details(mut self, details: ProtocolMetadata) -> Self {
self.details = details;
self
}
#[must_use]
pub const fn code(&self) -> AgentErrorCode {
self.code
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
pub const fn retry(&self) -> RetryClass {
self.retry
}
#[must_use]
pub const fn correlation_id(&self) -> Option<&CorrelationId> {
self.correlation_id.as_ref()
}
#[must_use]
pub const fn details(&self) -> &ProtocolMetadata {
&self.details
}
fn validate(&self) -> Result<(), ProtocolErrorValidationError> {
validate_message(&self.message)
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct SerializableProtocolError<'a> {
code: AgentErrorCode,
message: &'a str,
retry: RetryClass,
#[serde(skip_serializing_if = "Option::is_none")]
correlation_id: &'a Option<CorrelationId>,
#[serde(skip_serializing_if = "ProtocolMetadata::is_empty")]
details: &'a ProtocolMetadata,
}
impl Serialize for ProtocolError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.validate().map_err(serde::ser::Error::custom)?;
SerializableProtocolError {
code: self.code,
message: &self.message,
retry: self.retry,
correlation_id: &self.correlation_id,
details: &self.details,
}
.serialize(serializer)
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawProtocolError {
code: AgentErrorCode,
message: String,
retry: RetryClass,
#[serde(default)]
correlation_id: Option<CorrelationId>,
#[serde(default)]
details: ProtocolMetadata,
}
impl<'de> Deserialize<'de> for ProtocolError {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw = RawProtocolError::deserialize(deserializer)?;
let mut error = Self::new(raw.code, raw.message, raw.retry)
.map_err(serde::de::Error::custom)?
.with_details(raw.details);
error.correlation_id = raw.correlation_id;
Ok(error)
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProtocolErrorEnvelope {
protocol_version: ProtocolVersion,
#[serde(rename = "type")]
kind: ProtocolErrorEnvelopeType,
error: ProtocolError,
}
impl ProtocolErrorEnvelope {
#[must_use]
pub const fn new(error: ProtocolError) -> Self {
Self {
protocol_version: CURRENT_PROTOCOL_VERSION,
kind: ProtocolErrorEnvelopeType::ProtocolError,
error,
}
}
#[must_use]
pub const fn protocol_version(&self) -> ProtocolVersion {
self.protocol_version
}
#[must_use]
pub const fn error(&self) -> &ProtocolError {
&self.error
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum ProtocolErrorEnvelopeType {
ProtocolError,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawProtocolErrorEnvelope {
protocol_version: ProtocolVersion,
#[serde(rename = "type")]
kind: ProtocolErrorEnvelopeType,
error: ProtocolError,
}
impl<'de> Deserialize<'de> for ProtocolErrorEnvelope {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = deserialize_unique_value(deserializer)?;
let raw = RawProtocolErrorEnvelope::deserialize(value).map_err(serde::de::Error::custom)?;
if raw.protocol_version.major() != CURRENT_PROTOCOL_VERSION.major() {
return Err(serde::de::Error::custom(
"unsupported protocol major version",
));
}
Ok(Self {
protocol_version: raw.protocol_version,
kind: raw.kind,
error: raw.error,
})
}
}
#[derive(Debug, Error)]
pub enum ProtocolErrorValidationError {
#[error("technical error message is invalid")]
InvalidMessage,
#[error("safe error details are invalid: {0}")]
InvalidDetails(#[from] ProtocolMetadataError),
}
fn validate_message(message: &str) -> Result<(), ProtocolErrorValidationError> {
if message.is_empty()
|| message.len() > MAX_ERROR_MESSAGE_BYTES
|| message.contains('\0')
|| message.chars().any(|character| character == '\r')
{
Err(ProtocolErrorValidationError::InvalidMessage)
} else {
Ok(())
}
}