use smartstring::{LazyCompact, SmartString};
use thiserror::Error;
type FixString = SmartString<LazyCompact>;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)]
pub enum Error {
#[error("Encoding error: {0}")]
Encode(#[from] EncodeError),
#[error("Decoding error: {0}")]
Decode(#[from] DecodeError),
#[error("Schema error: {0}")]
Schema(FixString),
#[error("Configuration error: {0}")]
Config(FixString),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("ASN.1 library error: {0}")]
Asn1(String),
}
#[derive(Debug, Error)]
pub enum EncodeError {
#[error("Invalid field value for tag {tag}: {reason}")]
InvalidFieldValue {
tag: u32,
reason: FixString,
},
#[error("Message size {size} exceeds maximum {max_size}")]
MessageTooLarge {
size: usize,
max_size: usize,
},
#[error("Required field {tag} ({name}) is missing")]
RequiredFieldMissing {
tag: u32,
name: FixString,
},
#[error("Encoding rule {rule} not supported for message type {msg_type}")]
UnsupportedEncodingRule {
rule: &'static str,
msg_type: FixString,
},
#[error("Buffer capacity exceeded: needed {needed}, available {available}")]
BufferCapacityExceeded {
needed: usize,
available: usize,
},
#[error("Schema mismatch: {0}")]
SchemaMismatch(FixString),
#[error("Internal ASN.1 encoding error: {0}")]
Internal(String),
}
#[derive(Debug, Error)]
pub enum DecodeError {
#[error("Invalid ASN.1 tag {tag:02X} at offset {offset}")]
InvalidTag {
tag: u8,
offset: usize,
},
#[error("Unexpected end of input at offset {offset}, needed {needed} more bytes")]
UnexpectedEof {
offset: usize,
needed: usize,
},
#[error("Invalid length encoding at offset {offset}")]
InvalidLength {
offset: usize,
},
#[error("Value constraint violation for field {field}: {reason}")]
ConstraintViolation {
field: FixString,
reason: FixString,
},
#[error("Checksum validation failed: expected {expected}, got {actual}")]
ChecksumMismatch {
expected: u32,
actual: u32,
},
#[error("Maximum recursion depth {max_depth} exceeded")]
RecursionDepthExceeded {
max_depth: u32,
},
#[error("Invalid UTF-8 in string field at offset {offset}")]
InvalidUtf8 {
offset: usize,
},
#[error("Unsupported encoding rule for decoding: {0}")]
UnsupportedEncodingRule(&'static str),
#[error("Schema required for {encoding_rule} decoding but not provided")]
SchemaRequired {
encoding_rule: &'static str,
},
#[error("Message size {size} exceeds maximum {max_size}")]
MessageTooLarge {
size: usize,
max_size: usize,
},
#[error("Internal ASN.1 decoding error: {0}")]
Internal(String),
}
impl From<rasn::error::EncodeError> for EncodeError {
fn from(err: rasn::error::EncodeError) -> Self {
Self::Internal(err.to_string())
}
}
impl From<rasn::error::DecodeError> for DecodeError {
fn from(err: rasn::error::DecodeError) -> Self {
Self::Internal(err.to_string())
}
}
impl From<rasn::error::EncodeError> for Error {
fn from(err: rasn::error::EncodeError) -> Self {
Self::Encode(err.into())
}
}
impl From<rasn::error::DecodeError> for Error {
fn from(err: rasn::error::DecodeError) -> Self {
Self::Decode(err.into())
}
}
#[allow(dead_code)]
pub(crate) trait ErrorContext<T> {
fn context(self, msg: impl Into<FixString>) -> Result<T>;
fn field_context(self, tag: u32, name: impl Into<FixString>) -> Result<T>;
}
impl<T, E> ErrorContext<T> for std::result::Result<T, E>
where
E: Into<Error>,
{
fn context(self, msg: impl Into<FixString>) -> Result<T> {
self.map_err(|e| {
let base_error = e.into();
match base_error {
Error::Encode(EncodeError::Internal(s)) => Error::Encode(
EncodeError::SchemaMismatch(format!("{}: {}", msg.into(), s).into()),
),
Error::Decode(DecodeError::Internal(s)) => {
Error::Schema(format!("{}: {}", msg.into(), s).into())
}
other => other,
}
})
}
fn field_context(self, tag: u32, name: impl Into<FixString>) -> Result<T> {
self.map_err(|e| {
let base_error = e.into();
match base_error {
Error::Encode(EncodeError::Internal(s)) => {
Error::Encode(EncodeError::InvalidFieldValue {
tag,
reason: format!("{} - {}", name.into(), s).into(),
})
}
other => other,
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = EncodeError::MessageTooLarge {
size: 100_000,
max_size: 65_536,
};
assert_eq!(err.to_string(), "Message size 100000 exceeds maximum 65536");
}
#[test]
fn test_error_conversion() {
let encode_err = EncodeError::Internal("test error".to_string());
let main_error: Error = encode_err.into();
assert!(matches!(main_error, Error::Encode(_)));
}
}