use crate::error::{DecodeError, MsgTypeError};
use crate::field::FieldRef;
use arrayvec::ArrayString;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::fmt;
use std::ops::Range;
pub const MSG_TYPE_MAX_LEN: usize = 8;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct CustomMsgType(ArrayString<MSG_TYPE_MAX_LEN>);
impl CustomMsgType {
pub fn new(code: &str) -> Result<Self, MsgTypeError> {
if code.is_empty() {
return Err(MsgTypeError::Empty);
}
if code.len() > MSG_TYPE_MAX_LEN {
return Err(MsgTypeError::TooLong {
len: code.len(),
max_len: MSG_TYPE_MAX_LEN,
});
}
for (position, &byte) in code.as_bytes().iter().enumerate() {
if !(0x20..=0x7e).contains(&byte) {
return Err(MsgTypeError::IllegalByte { byte, position });
}
}
match ArrayString::<MSG_TYPE_MAX_LEN>::from(code) {
Ok(inner) => Ok(Self(inner)),
Err(_) => Err(MsgTypeError::TooLong {
len: code.len(),
max_len: MSG_TYPE_MAX_LEN,
}),
}
}
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl<'de> Deserialize<'de> for CustomMsgType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct CodeVisitor;
impl serde::de::Visitor<'_> for CodeVisitor {
type Value = CustomMsgType;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"a FIX MsgType code of 1..={MSG_TYPE_MAX_LEN} printable ASCII bytes"
)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
CustomMsgType::new(value).map_err(E::custom)
}
}
deserializer.deserialize_str(CodeVisitor)
}
}
macro_rules! define_msg_types {
(
$(
$(#[$variant_meta:meta])*
$variant:ident = $code:literal, admin = $admin:literal;
)*
) => {
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub enum MsgType {
$(
$(#[$variant_meta])*
$variant,
)*
Custom(CustomMsgType),
}
impl MsgType {
#[must_use]
pub fn as_str(&self) -> &str {
match self {
$( Self::$variant => $code, )*
Self::Custom(s) => s.as_str(),
}
}
pub fn new(code: &str) -> Result<Self, MsgTypeError> {
match code {
$( $code => Ok(Self::$variant), )*
other => Ok(Self::Custom(CustomMsgType::new(other)?)),
}
}
#[must_use]
pub fn is_admin(&self) -> bool {
match self {
$( Self::$variant => $admin, )*
Self::Custom(code) => match code.as_str() {
$( $code => $admin, )*
_ => false,
},
}
}
}
#[cfg(test)]
impl MsgType {
fn all_named() -> Vec<Self> {
vec![ $( Self::$variant, )* ]
}
}
};
}
define_msg_types! {
#[default]
Heartbeat = "0", admin = true;
TestRequest = "1", admin = true;
ResendRequest = "2", admin = true;
Reject = "3", admin = true;
SequenceReset = "4", admin = true;
Logout = "5", admin = true;
IndicationOfInterest = "6", admin = false;
Advertisement = "7", admin = false;
ExecutionReport = "8", admin = false;
OrderCancelReject = "9", admin = false;
Logon = "A", admin = true;
News = "B", admin = false;
Email = "C", admin = false;
NewOrderSingle = "D", admin = false;
NewOrderList = "E", admin = false;
OrderCancelRequest = "F", admin = false;
OrderCancelReplaceRequest = "G", admin = false;
OrderStatusRequest = "H", admin = false;
AllocationInstruction = "J", admin = false;
ListCancelRequest = "K", admin = false;
ListExecute = "L", admin = false;
ListStatusRequest = "M", admin = false;
ListStatus = "N", admin = false;
AllocationInstructionAck = "P", admin = false;
DontKnowTrade = "Q", admin = false;
QuoteRequest = "R", admin = false;
Quote = "S", admin = false;
SettlementInstructions = "T", admin = false;
MarketDataRequest = "V", admin = false;
MarketDataSnapshotFullRefresh = "W", admin = false;
MarketDataIncrementalRefresh = "X", admin = false;
MarketDataRequestReject = "Y", admin = false;
QuoteCancel = "Z", admin = false;
QuoteStatusRequest = "a", admin = false;
MassQuoteAcknowledgement = "b", admin = false;
SecurityDefinitionRequest = "c", admin = false;
SecurityDefinition = "d", admin = false;
SecurityStatusRequest = "e", admin = false;
SecurityStatus = "f", admin = false;
TradingSessionStatusRequest = "g", admin = false;
TradingSessionStatus = "h", admin = false;
MassQuote = "i", admin = false;
BusinessMessageReject = "j", admin = false;
BidRequest = "k", admin = false;
BidResponse = "l", admin = false;
ListStrikePrice = "m", admin = false;
XmlMessage = "n", admin = true;
RegistrationInstructions = "o", admin = false;
RegistrationInstructionsResponse = "p", admin = false;
OrderMassCancelRequest = "q", admin = false;
OrderMassCancelReport = "r", admin = false;
NewOrderCross = "s", admin = false;
CrossOrderCancelReplaceRequest = "t", admin = false;
CrossOrderCancelRequest = "u", admin = false;
SecurityTypeRequest = "v", admin = false;
SecurityTypes = "w", admin = false;
SecurityListRequest = "x", admin = false;
SecurityList = "y", admin = false;
DerivativeSecurityListRequest = "z", admin = false;
}
impl std::str::FromStr for MsgType {
type Err = MsgTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl MsgType {
#[must_use]
pub fn is_app(&self) -> bool {
!self.is_admin()
}
}
impl PartialEq for MsgType {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for MsgType {}
impl std::hash::Hash for MsgType {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
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]>,
}
#[inline]
fn check_range(range: &Range<usize>, buffer_len: usize) -> Result<(), DecodeError> {
if range.start > range.end || range.end > buffer_len {
return Err(DecodeError::RangeOutOfBounds {
start: range.start,
end: range.end,
buffer_len,
});
}
Ok(())
}
impl<'a> RawMessage<'a> {
pub fn new(
buffer: &'a [u8],
begin_string: Range<usize>,
body: Range<usize>,
msg_type: MsgType,
fields: SmallVec<[FieldRef<'a>; 32]>,
) -> Result<Self, DecodeError> {
check_range(&begin_string, buffer.len())?;
check_range(&body, buffer.len())?;
Ok(Self {
buffer,
begin_string,
body,
msg_type,
fields,
})
}
#[inline]
#[must_use]
pub const fn buffer(&self) -> &'a [u8] {
self.buffer
}
pub fn begin_string(&self) -> Result<&'a str, DecodeError> {
let range = self.begin_string.clone();
debug_assert!(
range.end <= self.buffer.len(),
"RawMessage ranges must be buffer-relative"
);
let bytes = self
.buffer
.get(range.clone())
.ok_or(DecodeError::RangeOutOfBounds {
start: range.start,
end: range.end,
buffer_len: self.buffer.len(),
})?;
std::str::from_utf8(bytes).map_err(DecodeError::from)
}
pub fn body(&self) -> Result<&'a [u8], DecodeError> {
let range = self.body.clone();
debug_assert!(
range.end <= self.buffer.len(),
"RawMessage ranges must be buffer-relative"
);
self.buffer
.get(range.clone())
.ok_or(DecodeError::RangeOutOfBounds {
start: range.start,
end: range.end,
buffer_len: self.buffer.len(),
})
}
#[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 buffer_start = raw.buffer.as_ptr() as usize;
let buffer_len = raw.buffer.len();
let mut field_offsets = Vec::with_capacity(raw.fields.len());
for field in &raw.fields {
let offset = (field.value.as_ptr() as usize)
.checked_sub(buffer_start)
.and_then(|start| {
let end = start.checked_add(field.value.len())?;
(end <= buffer_len).then_some(start..end)
});
if let Some(range) = offset {
field_offsets.push((field.tag, range));
}
}
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)
.and_then(|(_, range)| self.buffer.get(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::*;
use serde::de::IntoDeserializer;
use serde::de::value::{Error as ValueError, StrDeserializer};
use std::collections::hash_map::DefaultHasher;
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
fn custom_variant(code: &str) -> MsgType {
match CustomMsgType::new(code) {
Ok(inner) => MsgType::Custom(inner),
Err(err) => panic!("test code {code:?} must be a valid custom MsgType: {err}"),
}
}
#[test]
fn test_msg_type_from_str() {
assert_eq!("0".parse::<MsgType>(), Ok(MsgType::Heartbeat));
assert_eq!("A".parse::<MsgType>(), Ok(MsgType::Logon));
assert_eq!("D".parse::<MsgType>(), Ok(MsgType::NewOrderSingle));
assert_eq!("8".parse::<MsgType>(), Ok(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_every_variant_round_trips_through_its_code() {
for variant in MsgType::all_named() {
let code = variant.as_str().to_owned();
assert_eq!(
MsgType::new(&code),
Ok(variant.clone()),
"MsgType::new({code:?}) must recover {variant:?}"
);
assert!(
!matches!(MsgType::new(&code), Ok(MsgType::Custom(_))),
"{code:?} must map to a named variant, not Custom"
);
}
}
#[test]
fn test_msg_type_codes_are_unique() {
let named = MsgType::all_named();
let codes: HashSet<&str> = named.iter().map(MsgType::as_str).collect();
assert_eq!(
codes.len(),
named.len(),
"two variants share one wire code, so the reverse lookup is ambiguous"
);
}
#[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_is_admin_matches_dictionary_msgcat() {
const ADMIN_CODES: [&str; 8] = ["0", "1", "2", "3", "4", "5", "A", "n"];
for variant in MsgType::all_named() {
let expected = ADMIN_CODES.contains(&variant.as_str());
assert_eq!(
variant.is_admin(),
expected,
"{variant:?} ({}) disagrees with the dictionary msgcat",
variant.as_str()
);
assert_eq!(variant.is_app(), !expected);
}
}
#[test]
fn test_msg_type_xml_message_is_admin() {
assert_eq!(MsgType::new("n"), Ok(MsgType::XmlMessage));
assert!(MsgType::XmlMessage.is_admin());
assert!(!MsgType::XmlMessage.is_app());
}
#[test]
fn test_msg_type_custom() {
let custom = custom_variant("XX");
assert_eq!("XX".parse::<MsgType>(), Ok(custom.clone()));
assert_eq!(custom.as_str(), "XX");
assert!(!custom.is_admin());
assert!(custom.is_app());
}
#[test]
fn test_msg_type_new_normalises_known_code_out_of_custom() {
assert_eq!(MsgType::new("D"), Ok(MsgType::NewOrderSingle));
assert!(matches!(MsgType::new("D"), Ok(MsgType::NewOrderSingle)));
assert!(matches!(MsgType::new("ZZ"), Ok(MsgType::Custom(_))));
}
#[test]
fn test_msg_type_custom_at_the_bound_round_trips() {
let code = "U9999999";
assert_eq!(code.len(), MSG_TYPE_MAX_LEN);
let parsed = code.parse::<MsgType>();
assert_eq!(parsed, Ok(custom_variant(code)));
match parsed {
Ok(msg_type) => assert_eq!(msg_type.as_str(), code),
Err(err) => panic!("a {MSG_TYPE_MAX_LEN}-byte code must be accepted, got {err}"),
}
}
#[test]
fn test_msg_type_one_byte_over_the_bound_is_too_long_not_truncated() {
let code = "U99999999";
assert_eq!(code.len(), MSG_TYPE_MAX_LEN + 1);
assert_eq!(
code.parse::<MsgType>(),
Err(MsgTypeError::TooLong {
len: code.len(),
max_len: MSG_TYPE_MAX_LEN,
})
);
}
#[test]
fn test_msg_type_far_over_the_bound_is_too_long() {
let code = "A".repeat(4096);
assert_eq!(
MsgType::new(&code),
Err(MsgTypeError::TooLong {
len: 4096,
max_len: MSG_TYPE_MAX_LEN,
})
);
}
#[test]
fn test_msg_type_custom_owns_no_heap_allocation() {
assert!(
!std::mem::needs_drop::<MsgType>(),
"MsgType must own no heap allocation"
);
assert!(
!std::mem::needs_drop::<CustomMsgType>(),
"a custom MsgType code must live inline, not behind a pointer"
);
}
#[test]
fn test_msg_type_empty_code_is_rejected() {
assert_eq!("".parse::<MsgType>(), Err(MsgTypeError::Empty));
}
#[test]
fn test_msg_type_soh_byte_is_rejected() {
assert_eq!(
MsgType::new("A\x01B"),
Err(MsgTypeError::IllegalByte {
byte: 0x01,
position: 1,
})
);
}
#[test]
fn test_msg_type_control_or_non_ascii_byte_is_rejected() {
assert_eq!(
MsgType::new("A\x7fB"),
Err(MsgTypeError::IllegalByte {
byte: 0x7f,
position: 1,
})
);
assert_eq!(
MsgType::new("é"),
Err(MsgTypeError::IllegalByte {
byte: 0xc3,
position: 0,
})
);
}
#[test]
fn test_msg_type_equals_and_space_are_accepted() {
let with_equals = MsgType::new("U=1");
assert_eq!(with_equals, Ok(custom_variant("U=1")));
match with_equals {
Ok(msg_type) => assert_eq!(msg_type.as_str(), "U=1"),
Err(err) => panic!("`=` must be accepted in a custom code, got {err}"),
}
let with_space = MsgType::new("U 1");
assert_eq!(with_space, Ok(custom_variant("U 1")));
match with_space {
Ok(msg_type) => assert_eq!(msg_type.as_str(), "U 1"),
Err(err) => panic!("space must be accepted in a custom code, got {err}"),
}
}
#[test]
fn test_custom_msg_type_new_validates_like_msg_type_new() {
assert_eq!(CustomMsgType::new("").err(), Some(MsgTypeError::Empty));
assert_eq!(
CustomMsgType::new("U99999999").err(),
Some(MsgTypeError::TooLong {
len: 9,
max_len: MSG_TYPE_MAX_LEN,
})
);
assert_eq!(
CustomMsgType::new("A\x01B").err(),
Some(MsgTypeError::IllegalByte {
byte: 0x01,
position: 1,
})
);
match CustomMsgType::new("U=1") {
Ok(code) => assert_eq!(code.as_str(), "U=1"),
Err(err) => panic!("`U=1` must be a valid custom code, got {err}"),
}
}
#[test]
fn test_custom_msg_type_deserialize_rejects_illegal_code() {
let de: StrDeserializer<'_, ValueError> = "A\x01B".into_deserializer();
assert!(CustomMsgType::deserialize(de).is_err());
}
#[test]
fn test_custom_msg_type_deserialize_accepts_valid_code() {
let de: StrDeserializer<'_, ValueError> = "U=1".into_deserializer();
match CustomMsgType::deserialize(de) {
Ok(code) => assert_eq!(code.as_str(), "U=1"),
Err(err) => panic!("a valid code must deserialize, got {err}"),
}
}
#[test]
fn test_msg_type_error_converts_into_decode_error() {
let err: DecodeError = MsgTypeError::Empty.into();
assert_eq!(err, DecodeError::InvalidMsgType(MsgTypeError::Empty));
}
#[test]
fn test_msg_type_custom_equals_named_variant_with_same_wire_form() {
let custom = custom_variant("D");
assert_eq!(custom, MsgType::NewOrderSingle);
assert_eq!(MsgType::NewOrderSingle, custom);
assert_ne!(custom, MsgType::ExecutionReport);
}
#[test]
fn test_msg_type_equal_values_agree_on_is_admin() {
for variant in MsgType::all_named() {
let as_custom = custom_variant(variant.as_str());
assert_eq!(as_custom, variant);
assert_eq!(
as_custom.is_admin(),
variant.is_admin(),
"Custom({:?}) must classify like {variant:?}",
variant.as_str()
);
}
assert!(!custom_variant("ZZ").is_admin());
}
#[test]
fn test_msg_type_hash_follows_wire_form() {
fn hash_of(value: &MsgType) -> u64 {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}
let custom = custom_variant("D");
assert_eq!(hash_of(&custom), hash_of(&MsgType::NewOrderSingle));
let mut map: HashMap<MsgType, u32> = HashMap::new();
map.insert(MsgType::NewOrderSingle, 1);
assert_eq!(map.get(&custom), Some(&1));
}
fn single_field(buffer: &[u8]) -> SmallVec<[FieldRef<'_>; 32]> {
let mut fields = SmallVec::new();
fields.push(FieldRef::new(8, buffer));
fields
}
#[test]
fn test_raw_message_new_rejects_begin_string_range_past_buffer() {
let buffer: &[u8] = b"8=FIX.4.4\x01";
let fields = single_field(buffer);
let result = RawMessage::new(buffer, 2..64, 0..0, MsgType::Heartbeat, fields);
assert_eq!(
result.err(),
Some(DecodeError::RangeOutOfBounds {
start: 2,
end: 64,
buffer_len: buffer.len(),
})
);
}
#[test]
fn test_raw_message_new_rejects_body_range_past_buffer() {
let buffer: &[u8] = b"8=FIX.4.4\x01";
let fields = single_field(buffer);
let result = RawMessage::new(buffer, 2..9, 0..11, MsgType::Heartbeat, fields);
assert!(matches!(
result.err(),
Some(DecodeError::RangeOutOfBounds { .. })
));
}
#[test]
fn test_raw_message_new_rejects_inverted_range() {
let buffer: &[u8] = b"8=FIX.4.4\x01";
let fields = single_field(buffer);
let inverted = Range { start: 9, end: 2 };
let result = RawMessage::new(buffer, inverted, 0..0, MsgType::Heartbeat, fields);
assert!(matches!(
result.err(),
Some(DecodeError::RangeOutOfBounds { .. })
));
}
#[test]
fn test_raw_message_begin_string_and_body_are_buffer_relative() {
let buffer: &[u8] = b"8=FIX.4.4\x0135=0\x01";
let fields = single_field(buffer);
let Ok(msg) = RawMessage::new(buffer, 2..9, 10..15, MsgType::Heartbeat, fields) else {
panic!("in-bounds ranges must be accepted");
};
assert_eq!(msg.begin_string(), Ok("FIX.4.4"));
assert_eq!(msg.body(), Ok(&b"35=0\x01"[..]));
assert_eq!(msg.len(), buffer.len());
}
#[test]
fn test_raw_message_begin_string_invalid_utf8_is_typed_error() {
let buffer: &[u8] = b"8=\xff\xfe\x01";
let fields = single_field(buffer);
let Ok(msg) = RawMessage::new(buffer, 2..4, 0..0, MsgType::Heartbeat, fields) else {
panic!("in-bounds ranges must be accepted");
};
assert!(matches!(
msg.begin_string(),
Err(DecodeError::InvalidUtf8(_))
));
}
#[test]
fn test_owned_message_get_field_out_of_bounds_offset_returns_none() {
let buffer = Bytes::from_static(b"8=FIX.4.4\x01");
let msg = OwnedMessage::new(buffer, MsgType::Heartbeat, vec![(8, 2..64)]);
assert_eq!(msg.get_field(8), None);
}
#[test]
fn test_owned_message_from_raw_recovers_buffer_offsets() {
let buffer: &[u8] = b"8=FIX.4.4\x0135=D\x0149=SENDER\x01";
let mut fields: SmallVec<[FieldRef<'_>; 32]> = SmallVec::new();
let Some(begin) = buffer.get(2..9) else {
panic!("fixture buffer is long enough")
};
let Some(sender) = buffer.get(18..24) else {
panic!("fixture buffer is long enough")
};
fields.push(FieldRef::new(8, begin));
fields.push(FieldRef::new(49, sender));
let Ok(raw) = RawMessage::new(buffer, 2..9, 10..24, MsgType::NewOrderSingle, fields) else {
panic!("in-bounds ranges must be accepted");
};
let owned = OwnedMessage::from_raw(&raw);
assert_eq!(owned.field_count(), 2);
assert_eq!(owned.get_field_str(8), Some("FIX.4.4"));
assert_eq!(owned.get_field_str(49), Some("SENDER"));
}
#[test]
fn test_owned_message_from_raw_drops_field_not_borrowing_from_buffer() {
let buffer: &[u8] = b"8=FIX.4.4\x0135=D\x01";
let foreign: &[u8] = b"FOREIGN";
let mut fields: SmallVec<[FieldRef<'_>; 32]> = SmallVec::new();
let Some(begin) = buffer.get(2..9) else {
panic!("fixture buffer is long enough")
};
fields.push(FieldRef::new(8, begin));
fields.push(FieldRef::new(49, foreign));
let Ok(raw) = RawMessage::new(buffer, 2..9, 10..15, MsgType::NewOrderSingle, fields) else {
panic!("in-bounds ranges must be accepted");
};
let owned = OwnedMessage::from_raw(&raw);
assert_eq!(owned.field_count(), 1);
assert_eq!(owned.get_field_str(8), Some("FIX.4.4"));
assert_eq!(owned.get_field(49), None);
}
#[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);
}
}