use crate::error::DecodeError;
use crate::field::FieldRef;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::fmt;
use std::ops::Range;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum MsgType {
#[default]
Heartbeat,
TestRequest,
ResendRequest,
Reject,
SequenceReset,
Logout,
IndicationOfInterest,
Advertisement,
ExecutionReport,
OrderCancelReject,
Logon,
News,
Email,
NewOrderSingle,
NewOrderList,
OrderCancelRequest,
OrderCancelReplaceRequest,
OrderStatusRequest,
AllocationInstruction,
ListCancelRequest,
ListExecute,
ListStatusRequest,
ListStatus,
AllocationInstructionAck,
DontKnowTrade,
QuoteRequest,
Quote,
SettlementInstructions,
MarketDataRequest,
MarketDataSnapshotFullRefresh,
MarketDataIncrementalRefresh,
MarketDataRequestReject,
QuoteCancel,
QuoteStatusRequest,
MassQuoteAcknowledgement,
SecurityDefinitionRequest,
SecurityDefinition,
SecurityStatusRequest,
SecurityStatus,
TradingSessionStatusRequest,
TradingSessionStatus,
MassQuote,
BusinessMessageReject,
BidRequest,
BidResponse,
ListStrikePrice,
XmlMessage,
RegistrationInstructions,
RegistrationInstructionsResponse,
OrderMassCancelRequest,
OrderMassCancelReport,
NewOrderCross,
CrossOrderCancelReplaceRequest,
CrossOrderCancelRequest,
SecurityTypeRequest,
SecurityTypes,
SecurityListRequest,
SecurityList,
DerivativeSecurityListRequest,
Custom(String),
}
impl std::str::FromStr for MsgType {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"0" => Self::Heartbeat,
"1" => Self::TestRequest,
"2" => Self::ResendRequest,
"3" => Self::Reject,
"4" => Self::SequenceReset,
"5" => Self::Logout,
"6" => Self::IndicationOfInterest,
"7" => Self::Advertisement,
"8" => Self::ExecutionReport,
"9" => Self::OrderCancelReject,
"A" => Self::Logon,
"B" => Self::News,
"C" => Self::Email,
"D" => Self::NewOrderSingle,
"E" => Self::NewOrderList,
"F" => Self::OrderCancelRequest,
"G" => Self::OrderCancelReplaceRequest,
"H" => Self::OrderStatusRequest,
"J" => Self::AllocationInstruction,
"K" => Self::ListCancelRequest,
"L" => Self::ListExecute,
"M" => Self::ListStatusRequest,
"N" => Self::ListStatus,
"P" => Self::AllocationInstructionAck,
"Q" => Self::DontKnowTrade,
"R" => Self::QuoteRequest,
"S" => Self::Quote,
"T" => Self::SettlementInstructions,
"V" => Self::MarketDataRequest,
"W" => Self::MarketDataSnapshotFullRefresh,
"X" => Self::MarketDataIncrementalRefresh,
"Y" => Self::MarketDataRequestReject,
"Z" => Self::QuoteCancel,
"a" => Self::QuoteStatusRequest,
"b" => Self::MassQuoteAcknowledgement,
"c" => Self::SecurityDefinitionRequest,
"d" => Self::SecurityDefinition,
"e" => Self::SecurityStatusRequest,
"f" => Self::SecurityStatus,
"g" => Self::TradingSessionStatusRequest,
"h" => Self::TradingSessionStatus,
"i" => Self::MassQuote,
"j" => Self::BusinessMessageReject,
"k" => Self::BidRequest,
"l" => Self::BidResponse,
"m" => Self::ListStrikePrice,
"n" => Self::XmlMessage,
"o" => Self::RegistrationInstructions,
"p" => Self::RegistrationInstructionsResponse,
"q" => Self::OrderMassCancelRequest,
"r" => Self::OrderMassCancelReport,
"s" => Self::NewOrderCross,
"t" => Self::CrossOrderCancelReplaceRequest,
"u" => Self::CrossOrderCancelRequest,
"v" => Self::SecurityTypeRequest,
"w" => Self::SecurityTypes,
"x" => Self::SecurityListRequest,
"y" => Self::SecurityList,
"z" => Self::DerivativeSecurityListRequest,
other => Self::Custom(other.to_string()),
})
}
}
impl MsgType {
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Self::Heartbeat => "0",
Self::TestRequest => "1",
Self::ResendRequest => "2",
Self::Reject => "3",
Self::SequenceReset => "4",
Self::Logout => "5",
Self::IndicationOfInterest => "6",
Self::Advertisement => "7",
Self::ExecutionReport => "8",
Self::OrderCancelReject => "9",
Self::Logon => "A",
Self::News => "B",
Self::Email => "C",
Self::NewOrderSingle => "D",
Self::NewOrderList => "E",
Self::OrderCancelRequest => "F",
Self::OrderCancelReplaceRequest => "G",
Self::OrderStatusRequest => "H",
Self::AllocationInstruction => "J",
Self::ListCancelRequest => "K",
Self::ListExecute => "L",
Self::ListStatusRequest => "M",
Self::ListStatus => "N",
Self::AllocationInstructionAck => "P",
Self::DontKnowTrade => "Q",
Self::QuoteRequest => "R",
Self::Quote => "S",
Self::SettlementInstructions => "T",
Self::MarketDataRequest => "V",
Self::MarketDataSnapshotFullRefresh => "W",
Self::MarketDataIncrementalRefresh => "X",
Self::MarketDataRequestReject => "Y",
Self::QuoteCancel => "Z",
Self::QuoteStatusRequest => "a",
Self::MassQuoteAcknowledgement => "b",
Self::SecurityDefinitionRequest => "c",
Self::SecurityDefinition => "d",
Self::SecurityStatusRequest => "e",
Self::SecurityStatus => "f",
Self::TradingSessionStatusRequest => "g",
Self::TradingSessionStatus => "h",
Self::MassQuote => "i",
Self::BusinessMessageReject => "j",
Self::BidRequest => "k",
Self::BidResponse => "l",
Self::ListStrikePrice => "m",
Self::XmlMessage => "n",
Self::RegistrationInstructions => "o",
Self::RegistrationInstructionsResponse => "p",
Self::OrderMassCancelRequest => "q",
Self::OrderMassCancelReport => "r",
Self::NewOrderCross => "s",
Self::CrossOrderCancelReplaceRequest => "t",
Self::CrossOrderCancelRequest => "u",
Self::SecurityTypeRequest => "v",
Self::SecurityTypes => "w",
Self::SecurityListRequest => "x",
Self::SecurityList => "y",
Self::DerivativeSecurityListRequest => "z",
Self::Custom(s) => s.as_str(),
}
}
#[must_use]
pub fn is_admin(&self) -> bool {
matches!(
self,
Self::Heartbeat
| Self::TestRequest
| Self::ResendRequest
| Self::Reject
| Self::SequenceReset
| Self::Logout
| Self::Logon
)
}
#[must_use]
pub fn is_app(&self) -> bool {
!self.is_admin()
}
}
impl fmt::Display for MsgType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone)]
pub struct RawMessage<'a> {
buffer: &'a [u8],
begin_string: Range<usize>,
body: Range<usize>,
msg_type: MsgType,
fields: SmallVec<[FieldRef<'a>; 32]>,
}
impl<'a> RawMessage<'a> {
#[must_use]
pub fn new(
buffer: &'a [u8],
begin_string: Range<usize>,
body: Range<usize>,
msg_type: MsgType,
fields: SmallVec<[FieldRef<'a>; 32]>,
) -> Self {
Self {
buffer,
begin_string,
body,
msg_type,
fields,
}
}
#[inline]
#[must_use]
pub const fn buffer(&self) -> &'a [u8] {
self.buffer
}
#[must_use]
pub fn begin_string(&self) -> &'a str {
std::str::from_utf8(&self.buffer[self.begin_string.clone()]).unwrap_or("")
}
#[inline]
#[must_use]
pub fn msg_type(&self) -> &MsgType {
&self.msg_type
}
#[inline]
pub fn fields(&self) -> impl Iterator<Item = &FieldRef<'a>> {
self.fields.iter()
}
#[inline]
#[must_use]
pub fn field_count(&self) -> usize {
self.fields.len()
}
#[must_use]
pub fn get_field(&self, tag: u32) -> Option<&FieldRef<'a>> {
self.fields.iter().find(|f| f.tag == tag)
}
#[must_use]
pub fn get_field_str(&self, tag: u32) -> Option<&'a str> {
self.get_field(tag).and_then(|f| f.as_str().ok())
}
pub fn get_field_as<T: std::str::FromStr>(&self, tag: u32) -> Result<T, DecodeError> {
self.get_field(tag)
.ok_or(DecodeError::MissingRequiredField { tag })?
.parse()
}
#[inline]
#[must_use]
pub fn body_range(&self) -> &Range<usize> {
&self.body
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.buffer.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
#[must_use]
pub fn to_owned(&self) -> OwnedMessage {
OwnedMessage::from_raw(self)
}
}
#[derive(Debug, Clone)]
pub struct OwnedMessage {
buffer: Bytes,
msg_type: MsgType,
field_offsets: Vec<(u32, Range<usize>)>,
}
impl OwnedMessage {
#[must_use]
pub fn from_raw(raw: &RawMessage<'_>) -> Self {
let buffer = Bytes::copy_from_slice(raw.buffer);
let field_offsets = raw
.fields
.iter()
.map(|f| {
let start = f.value.as_ptr() as usize - raw.buffer.as_ptr() as usize;
let end = start + f.value.len();
(f.tag, start..end)
})
.collect();
Self {
buffer,
msg_type: raw.msg_type.clone(),
field_offsets,
}
}
#[must_use]
pub fn new(buffer: Bytes, msg_type: MsgType, field_offsets: Vec<(u32, Range<usize>)>) -> Self {
Self {
buffer,
msg_type,
field_offsets,
}
}
#[inline]
#[must_use]
pub fn msg_type(&self) -> &MsgType {
&self.msg_type
}
#[inline]
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.buffer
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.buffer.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
#[must_use]
pub fn get_field(&self, tag: u32) -> Option<&[u8]> {
self.field_offsets
.iter()
.find(|(t, _)| *t == tag)
.map(|(_, range)| &self.buffer[range.clone()])
}
#[must_use]
pub fn get_field_str(&self, tag: u32) -> Option<&str> {
self.get_field(tag)
.and_then(|b| std::str::from_utf8(b).ok())
}
#[inline]
#[must_use]
pub fn field_count(&self) -> usize {
self.field_offsets.len()
}
#[must_use]
pub fn into_bytes(self) -> Bytes {
self.buffer
}
}
pub trait FixMessage: Sized {
const MSG_TYPE: &'static str;
fn from_raw(raw: &RawMessage<'_>) -> Result<Self, DecodeError>;
fn encode(&self, buf: &mut Vec<u8>) -> Result<(), crate::error::EncodeError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_msg_type_from_str() {
assert_eq!("0".parse::<MsgType>().unwrap(), MsgType::Heartbeat);
assert_eq!("A".parse::<MsgType>().unwrap(), MsgType::Logon);
assert_eq!("D".parse::<MsgType>().unwrap(), MsgType::NewOrderSingle);
assert_eq!("8".parse::<MsgType>().unwrap(), MsgType::ExecutionReport);
}
#[test]
fn test_msg_type_as_str() {
assert_eq!(MsgType::Heartbeat.as_str(), "0");
assert_eq!(MsgType::Logon.as_str(), "A");
assert_eq!(MsgType::NewOrderSingle.as_str(), "D");
}
#[test]
fn test_msg_type_is_admin() {
assert!(MsgType::Heartbeat.is_admin());
assert!(MsgType::Logon.is_admin());
assert!(MsgType::Logout.is_admin());
assert!(!MsgType::NewOrderSingle.is_admin());
assert!(!MsgType::ExecutionReport.is_admin());
}
#[test]
fn test_msg_type_custom() {
let custom: MsgType = "XX".parse().unwrap();
assert!(matches!(custom, MsgType::Custom(_)));
assert_eq!(custom.as_str(), "XX");
}
#[test]
fn test_owned_message_field_access() {
let buffer = Bytes::from_static(b"8=FIX.4.4\x0135=D\x0149=SENDER\x01");
let field_offsets = vec![(8, 2..9), (35, 13..14), (49, 18..24)];
let msg = OwnedMessage::new(buffer, MsgType::NewOrderSingle, field_offsets);
assert_eq!(msg.get_field_str(8), Some("FIX.4.4"));
assert_eq!(msg.get_field_str(35), Some("D"));
assert_eq!(msg.get_field_str(49), Some("SENDER"));
assert_eq!(msg.get_field_str(999), None);
}
}