From 9fdef69dbbd96523ffd49a5c9b3607581b846d89 Mon Sep 17 00:00:00 2001 From: David Steiner Date: Fri, 23 Jan 2026 12:00:49 +0100 Subject: [PATCH 1/4] Remove unwraps in build_message_specifications --- crates/hotfix-message/src/builder.rs | 40 +++++++++++++++------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/crates/hotfix-message/src/builder.rs b/crates/hotfix-message/src/builder.rs index 78e4c37..9409a47 100644 --- a/crates/hotfix-message/src/builder.rs +++ b/crates/hotfix-message/src/builder.rs @@ -6,7 +6,6 @@ use crate::message::{Config, Message}; use crate::parsed_message::{GarbledReason, InvalidReason, ParsedMessage}; use crate::parts::{Body, Header, RepeatingGroup, Trailer}; use crate::tags::{BEGIN_STRING, BODY_LENGTH, CHECK_SUM, MSG_TYPE}; -use anyhow::anyhow; use hotfix_dictionary::{Dictionary, LayoutItem, LayoutItemKind, TagU32}; use std::collections::{HashMap, HashSet}; @@ -316,11 +315,11 @@ impl MessageBuilder { fn get_tags_for_component( dict: &Dictionary, component_name: &str, - ) -> anyhow::Result> { + ) -> ParserResult> { let mut tags = HashSet::new(); let component = dict .component_by_name(component_name) - .ok_or(ParserError::InvalidComponent(component_name.to_string()))?; + .ok_or_else(|| ParserError::InvalidComponent(component_name.to_string()))?; for item in component.items() { if let LayoutItemKind::Field(field) = item.kind() { tags.insert(field.tag()); @@ -457,7 +456,7 @@ impl MessageSpecification { fn build_message_specifications( dict: &Dictionary, -) -> anyhow::Result> { +) -> ParserResult> { let mut definitions = HashMap::new(); for message in dict.messages() { @@ -467,12 +466,14 @@ fn build_message_specifications( .flatten() .collect(); + let mut groups = HashMap::new(); + for item in message.layout() { + groups.extend(extract_groups(dict, item)?); + } + let message_def = MessageSpecification { fields, - groups: message.layout().fold(HashMap::new(), |mut acc, item| { - acc.extend(extract_groups(dict, item).unwrap()); - acc - }), + groups, }; definitions.insert(message.msg_type().to_string(), message_def); } @@ -480,13 +481,13 @@ fn build_message_specifications( Ok(definitions) } -fn extract_fields(dict: &Dictionary, item: LayoutItem) -> anyhow::Result> { +fn extract_fields(dict: &Dictionary, item: LayoutItem) -> ParserResult> { let is_required = item.required(); let fields = match item.kind() { LayoutItemKind::Component(c) => { let component = dict .component_by_name(c.name()) - .ok_or_else(|| anyhow!("missing component"))?; + .ok_or_else(|| ParserError::InvalidComponent(c.name().to_string()))?; component .items() .flat_map(|i| extract_fields(dict, i)) @@ -509,18 +510,22 @@ fn extract_fields(dict: &Dictionary, item: LayoutItem) -> anyhow::Result anyhow::Result> { +) -> ParserResult> { let mut groups = HashMap::new(); match item.kind() { LayoutItemKind::Component(c) => { let component = dict .component_by_name(c.name()) - .ok_or_else(|| anyhow!("missing component"))?; - component.items().for_each(|i| { - groups.extend(extract_groups(dict, i).unwrap()); - }) + .ok_or_else(|| ParserError::InvalidComponent(c.name().to_string()))?; + for i in component.items() { + groups.extend(extract_groups(dict, i)?); + } } LayoutItemKind::Group(field, items) => { + let mut nested_groups = HashMap::new(); + for i in items.iter() { + nested_groups.extend(extract_groups(dict, i.clone())?); + } groups.insert( field.tag(), GroupSpecification { @@ -530,10 +535,7 @@ fn extract_groups( .flat_map(|i| extract_fields(dict, i.clone())) .flatten() .collect(), - nested_groups: items.iter().fold(HashMap::new(), |mut acc, i| { - acc.extend(extract_groups(dict, i.clone()).unwrap()); - acc - }), + nested_groups, }, ); } From 511b092eb5b9302025bf6f8f0117cb873f3f5cc5 Mon Sep 17 00:00:00 2001 From: David Steiner Date: Fri, 23 Jan 2026 12:11:44 +0100 Subject: [PATCH 2/4] Disallow unwraps and expects in builder.rs --- crates/hotfix/clippy.toml => clippy.toml | 0 crates/hotfix-message/src/builder.rs | 22 +++++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) rename crates/hotfix/clippy.toml => clippy.toml (100%) diff --git a/crates/hotfix/clippy.toml b/clippy.toml similarity index 100% rename from crates/hotfix/clippy.toml rename to clippy.toml diff --git a/crates/hotfix-message/src/builder.rs b/crates/hotfix-message/src/builder.rs index 9409a47..b778352 100644 --- a/crates/hotfix-message/src/builder.rs +++ b/crates/hotfix-message/src/builder.rs @@ -1,3 +1,7 @@ +#![deny(clippy::expect_used)] +#![deny(clippy::panic)] +#![deny(clippy::unwrap_used)] + use crate::Part; use crate::error::{MessageIntegrityError, ParserError, ParserResult}; use crate::field_map::Field; @@ -65,7 +69,8 @@ impl MessageBuilder { } }; - let msg_type = header.get::<&str>(MSG_TYPE).unwrap(); // we know this is valid at this point as we have already verified the integrity of the header + #[allow(clippy::expect_used)] + let msg_type = header.get::<&str>(MSG_TYPE).expect("we know this is valid at this point as we have already verified the integrity of the header"); let (body, next) = match self.build_body(msg_type, &mut parser, next) { Ok((body, field)) => (body, field), Err(err) => { @@ -173,9 +178,10 @@ impl MessageBuilder { } else { // check the message type once all other header fields have been parsed // we delay it until after parsing so our rejection has access to fields like the sequence number + #[allow(clippy::expect_used)] let msg_type = header .get::<&str>(MSG_TYPE) - .expect("this should never fail as we've verified the integrity of the header"); + .expect("this never fails as we've verified the integrity of the header"); if self.dict.message_by_msgtype(msg_type).is_none() { return Err(ParserError::InvalidMsgType(msg_type.to_string())); } @@ -196,12 +202,12 @@ impl MessageBuilder { let mut field = next_field; while message_def.contains_tag(field.tag) { - let tag = field.tag.get(); + let tag = field.tag; body.store_field(field); // check if it's the start of a group and parse the group as needed - let field_def = self.get_dict_field_by_tag(tag)?; - match message_def.get_group(TagU32::new(tag).unwrap()) { + let field_def = self.get_dict_field_by_tag(tag.get())?; + match message_def.get_group(tag) { Some(group_def) => { let (groups, next) = Self::parse_groups(parser, group_def, field_def.tag())?; body.set_groups(groups); @@ -424,6 +430,7 @@ impl GroupSpecification { } pub fn delimiter_tag(&self) -> TagU32 { + #[allow(clippy::expect_used)] self.fields .first() .expect("groups always have at least one field") @@ -471,10 +478,7 @@ fn build_message_specifications( groups.extend(extract_groups(dict, item)?); } - let message_def = MessageSpecification { - fields, - groups, - }; + let message_def = MessageSpecification { fields, groups }; definitions.insert(message.msg_type().to_string(), message_def); } From 49d862bf857d413bf9934b78a55fe3ebb9de026e Mon Sep 17 00:00:00 2001 From: David Steiner Date: Fri, 23 Jan 2026 17:43:17 +0100 Subject: [PATCH 3/4] Disallow unwraps and expects everywhere in hotfix-message --- crates/hotfix-message/src/builder.rs | 13 ++++++------ crates/hotfix-message/src/encoder.rs | 4 ++-- .../src/encoding/definitions.rs | 1 + .../src/encoding/field_access.rs | 1 + .../src/encoding/field_types.rs | 8 +++++-- .../src/encoding/field_types/tagu32.rs | 5 +++-- .../src/encoding/field_types/timestamp.rs | 8 ++++--- .../src/encoding/field_types/tz.rs | 6 +++--- .../src/encoding/field_types/utils_chrono.rs | 20 +++++++----------- crates/hotfix-message/src/error.rs | 10 +++++++++ crates/hotfix-message/src/lib.rs | 4 ++++ crates/hotfix-message/src/parts.rs | 21 ++++++++++++------- crates/hotfix-message/src/parts/header.rs | 11 ++++++++-- crates/hotfix-message/src/parts/trailer.rs | 7 +++++-- examples/load-testing/src/messages.rs | 2 +- examples/simple-new-order/src/messages.rs | 2 +- 16 files changed, 79 insertions(+), 44 deletions(-) diff --git a/crates/hotfix-message/src/builder.rs b/crates/hotfix-message/src/builder.rs index b778352..063cdc4 100644 --- a/crates/hotfix-message/src/builder.rs +++ b/crates/hotfix-message/src/builder.rs @@ -1,7 +1,3 @@ -#![deny(clippy::expect_used)] -#![deny(clippy::panic)] -#![deny(clippy::unwrap_used)] - use crate::Part; use crate::error::{MessageIntegrityError, ParserError, ParserResult}; use crate::field_map::Field; @@ -210,7 +206,9 @@ impl MessageBuilder { match message_def.get_group(tag) { Some(group_def) => { let (groups, next) = Self::parse_groups(parser, group_def, field_def.tag())?; - body.set_groups(groups); + #[allow(clippy::expect_used)] + body.set_groups(groups) + .expect("groups are guaranteed to be valid at this point"); field = next; } None => { @@ -264,7 +262,10 @@ impl MessageBuilder { { let (groups, next) = Self::parse_groups(parser, nested_group_def, current_tag)?; - group.set_groups(groups); + #[allow(clippy::expect_used)] + group + .set_groups(groups) + .expect("groups are guaranteed to be valid at this point"); next } else { parser diff --git a/crates/hotfix-message/src/encoder.rs b/crates/hotfix-message/src/encoder.rs index e9c2609..31fd073 100644 --- a/crates/hotfix-message/src/encoder.rs +++ b/crates/hotfix-message/src/encoder.rs @@ -138,14 +138,14 @@ mod tests { )); subparty_2.store_field(Field::new(fix44::PARTY_SUB_ID_TYPE.tag(), b"2".to_vec())); - party_1.set_groups(vec![subparty_1, subparty_2]); + party_1.set_groups(vec![subparty_1, subparty_2])?; let mut party_2 = RepeatingGroup::new(fix44::NO_PARTY_I_DS, fix44::PARTY_ID); party_2.store_field(Field::new(fix44::PARTY_ID.tag(), b"PARTY_B".to_vec())); party_2.store_field(Field::new(fix44::PARTY_ID_SOURCE.tag(), b"D".to_vec())); party_2.store_field(Field::new(fix44::PARTY_ROLE.tag(), b"2".to_vec())); - msg.body.set_groups(vec![party_1, party_2]); + msg.body.set_groups(vec![party_1, party_2])?; let config = Config { separator: b'|' }; let raw_message = msg.encode(&config)?; diff --git a/crates/hotfix-message/src/encoding/definitions.rs b/crates/hotfix-message/src/encoding/definitions.rs index 77fc0df..377df81 100644 --- a/crates/hotfix-message/src/encoding/definitions.rs +++ b/crates/hotfix-message/src/encoding/definitions.rs @@ -23,6 +23,7 @@ pub struct HardCodedFixFieldDefinition { impl dict::IsFieldDefinition for HardCodedFixFieldDefinition { #[inline] fn tag(&self) -> TagU32 { + #[allow(clippy::expect_used)] TagU32::new(self.tag).expect("Invalid tag number 0.") } diff --git a/crates/hotfix-message/src/encoding/field_access.rs b/crates/hotfix-message/src/encoding/field_access.rs index fabfc06..b9a9a57 100644 --- a/crates/hotfix-message/src/encoding/field_access.rs +++ b/crates/hotfix-message/src/encoding/field_access.rs @@ -88,6 +88,7 @@ where /// valid UTF-8. As such, you should only *ever* use this function for /// [`FieldType`] implementors that are guaranteed to be representable with /// valid UTF-8 (like numbers with ASCII digits). + #[allow(clippy::expect_used)] fn to_string(&self) -> String { String::from_utf8(self.to_bytes()).expect("Invalid UTF-8 representation of FIX field.") } diff --git a/crates/hotfix-message/src/encoding/field_types.rs b/crates/hotfix-message/src/encoding/field_types.rs index 28afb2c..7171144 100644 --- a/crates/hotfix-message/src/encoding/field_types.rs +++ b/crates/hotfix-message/src/encoding/field_types.rs @@ -115,8 +115,12 @@ where { let serialized = item.to_bytes(); let bytes = &serialized[..]; - let deserialized = T::deserialize(bytes).ok().unwrap(); - let deserialized_lossy = T::deserialize_lossy(bytes).ok().unwrap(); + let Some(deserialized) = T::deserialize(bytes).ok() else { + return false; + }; + let Some(deserialized_lossy) = T::deserialize_lossy(bytes).ok() else { + return false; + }; deserialized == item && deserialized_lossy == item } diff --git a/crates/hotfix-message/src/encoding/field_types/tagu32.rs b/crates/hotfix-message/src/encoding/field_types/tagu32.rs index 7f25cf6..c1fbf48 100644 --- a/crates/hotfix-message/src/encoding/field_types/tagu32.rs +++ b/crates/hotfix-message/src/encoding/field_types/tagu32.rs @@ -14,7 +14,7 @@ impl<'a> FieldType<'a> for TagU32 { B: Buffer, { let initial_len = buffer.len(); - write!(BufferWriter(buffer), "{self}").unwrap(); + let _ = write!(BufferWriter(buffer), "{self}"); buffer.len() - initial_len } @@ -29,6 +29,7 @@ impl<'a> FieldType<'a> for TagU32 { #[inline] fn deserialize_lossy(data: &'a [u8]) -> Result { let n = u32::deserialize_lossy(data)?; - Ok(TagU32::new(n.max(1)).unwrap()) + #[allow(clippy::expect_used)] + Ok(TagU32::new(n.max(1)).expect("guaranteed to be non-zero")) } } diff --git a/crates/hotfix-message/src/encoding/field_types/timestamp.rs b/crates/hotfix-message/src/encoding/field_types/timestamp.rs index e58a69e..eff455c 100644 --- a/crates/hotfix-message/src/encoding/field_types/timestamp.rs +++ b/crates/hotfix-message/src/encoding/field_types/timestamp.rs @@ -25,18 +25,20 @@ impl Timestamp { } /// Returns the current UTC system time with millisecond precision. + #[allow(clippy::expect_used)] pub fn utc_now() -> Self { use chrono::{Datelike, Timelike}; let utc: chrono::DateTime = chrono::Utc::now(); - let date = Date::new(utc.year() as u32, utc.month(), utc.day()); + let date = Date::new(utc.year() as u32, utc.month(), utc.day()) + .expect("chrono::Utc::now() always produces a valid date"); let time = Time::from_hmsm( utc.hour(), utc.minute(), utc.second(), utc.nanosecond() / 1_000_000, ) - .unwrap(); - Self::new(date.unwrap(), time) + .expect("chrono::Utc::now() always produces a valid time"); + Self::new(date, time) } /// Returns the date of `self`. diff --git a/crates/hotfix-message/src/encoding/field_types/tz.rs b/crates/hotfix-message/src/encoding/field_types/tz.rs index 501a2f2..1f0ccc2 100644 --- a/crates/hotfix-message/src/encoding/field_types/tz.rs +++ b/crates/hotfix-message/src/encoding/field_types/tz.rs @@ -58,9 +58,9 @@ impl Tz { #[cfg(feature = "utils-chrono")] #[cfg_attr(doc_cfg, doc(cfg(feature = "utils-chrono")))] pub fn to_chrono_offset(&self) -> chrono::FixedOffset { - // unwrap(): we already verified that the offset is within bounds during - // deserialization - chrono::FixedOffset::east_opt(self.offset().1.as_secs() as i32).unwrap() + #[allow(clippy::expect_used)] + chrono::FixedOffset::east_opt(self.offset().1.as_secs() as i32) + .expect("we already verified that the offset is within bounds during deserialisation") } /// Creates a [`Tz`] from a [`chrono::FixedOffset`]. diff --git a/crates/hotfix-message/src/encoding/field_types/utils_chrono.rs b/crates/hotfix-message/src/encoding/field_types/utils_chrono.rs index d4adf68..28afbdd 100644 --- a/crates/hotfix-message/src/encoding/field_types/utils_chrono.rs +++ b/crates/hotfix-message/src/encoding/field_types/utils_chrono.rs @@ -18,7 +18,7 @@ impl Default for WithMilliseconds { } #[cfg_attr(doc_cfg, doc(cfg(feature = "utils-chrono")))] -impl<'a> FieldType<'a> for chrono::NaiveDateTime { +impl<'a> FieldType<'a> for NaiveDateTime { type Error = &'static str; type SerializeSettings = WithMilliseconds; @@ -38,8 +38,7 @@ impl<'a> FieldType<'a> for chrono::NaiveDateTime { } let date = chrono::NaiveDate::deserialize(&data[..8])?; - let hyphen = <&[u8]>::deserialize(&data[8..9]).unwrap(); - if hyphen != b"-" { + if data[8] != b'-' { return Err("Hyphen in datetime not found."); } let time = chrono::NaiveTime::deserialize(&data[9..])?; @@ -59,14 +58,13 @@ impl<'a> FieldType<'a> for chrono::NaiveDate { B: Buffer, { use chrono::Datelike; - write!( + let _ = write!( BufferWriter(buffer), "{:04}{:02}{:02}", self.year(), self.month(), self.day(), - ) - .unwrap(); + ); 8 } @@ -90,21 +88,19 @@ impl<'a> FieldType<'a> for chrono::NaiveTime { where B: Buffer, { - write!( + let _ = write!( BufferWriter(buffer), "{:02}:{:02}:{:02}", self.hour(), self.minute(), self.second() - ) - .unwrap(); + ); if with_millis { - write!( + let _ = write!( BufferWriter(buffer), ".{:03}", self.nanosecond() / 1_000_000 - ) - .unwrap(); + ); 12 } else { 8 diff --git a/crates/hotfix-message/src/error.rs b/crates/hotfix-message/src/error.rs index 6095ffe..360331d 100644 --- a/crates/hotfix-message/src/error.rs +++ b/crates/hotfix-message/src/error.rs @@ -1,3 +1,5 @@ +use hotfix_dictionary::TagU32; +use std::collections::HashSet; use std::io; use thiserror::Error; @@ -43,3 +45,11 @@ pub(crate) enum MessageIntegrityError { #[error("Invalid CheckSum")] InvalidCheckSum, } + +#[derive(Error, Debug)] +pub enum SetGroupsError { + #[error("Supplied empty vector of groups")] + EmptyGroups, + #[error("Supplied groups contain multiple start tags")] + MultipleStartTagsAndDelimiters(HashSet<(TagU32, TagU32)>), +} diff --git a/crates/hotfix-message/src/lib.rs b/crates/hotfix-message/src/lib.rs index 45f0b81..3d9d1ae 100644 --- a/crates/hotfix-message/src/lib.rs +++ b/crates/hotfix-message/src/lib.rs @@ -1,3 +1,7 @@ +#![deny(clippy::expect_used)] +#![deny(clippy::panic)] +#![deny(clippy::unwrap_used)] + mod builder; mod encoder; mod encoding; diff --git a/crates/hotfix-message/src/parts.rs b/crates/hotfix-message/src/parts.rs index 7f6bfd2..02dbf47 100644 --- a/crates/hotfix-message/src/parts.rs +++ b/crates/hotfix-message/src/parts.rs @@ -9,13 +9,13 @@ mod trailer; use hotfix_dictionary::{IsFieldDefinition, TagU32}; use crate::encoding::FieldValueError; +use crate::error::SetGroupsError; use crate::{FieldType, HardCodedFixFieldDefinition}; pub(crate) use body::Body; pub(crate) use header::Header; pub use repeating_group::RepeatingGroup; pub(crate) use trailer::Trailer; -// TODO: what a rubbish name.. but can't think of anything better that's not overloaded with fefix names pub trait Part { fn get_field_map(&self) -> &FieldMap; fn get_field_map_mut(&mut self) -> &mut FieldMap; @@ -24,9 +24,7 @@ pub trait Part { where V: FieldType<'a>, { - let tag = TagU32::new(field_definition.tag).unwrap(); - let field = Field::new(tag, value.to_bytes()); - + let field = Field::new(field_definition.tag(), value.to_bytes()); self.store_field(field); } @@ -58,14 +56,21 @@ pub trait Part { self.get_field_map_mut().fields.shift_remove(&field.tag()) } - fn set_groups(&mut self, groups: Vec) { + fn set_groups(&mut self, groups: Vec) -> Result<(), SetGroupsError> { let tags: HashSet<(TagU32, TagU32)> = groups .iter() .map(|g| (g.start_tag, g.delimiter_tag)) .collect(); - assert_eq!(tags.len(), 1); - let (start_tag, _) = tags.into_iter().next().unwrap(); - self.get_field_map_mut().set_groups(start_tag, groups); + let (start_tag, _) = &tags + .iter() + .next() + .ok_or_else(|| SetGroupsError::EmptyGroups)?; + if tags.len() > 1 { + return Err(SetGroupsError::MultipleStartTagsAndDelimiters(tags)); + } + self.get_field_map_mut().set_groups(*start_tag, groups); + + Ok(()) } fn get_group(&self, start_tag: TagU32, index: usize) -> Option<&RepeatingGroup> { diff --git a/crates/hotfix-message/src/parts/header.rs b/crates/hotfix-message/src/parts/header.rs index 4991519..277341c 100644 --- a/crates/hotfix-message/src/parts/header.rs +++ b/crates/hotfix-message/src/parts/header.rs @@ -1,6 +1,8 @@ +use hotfix_dictionary::IsFieldDefinition; + use crate::field_map::FieldMap; use crate::parts::Part; -use hotfix_dictionary::TagU32; +use crate::session_fields; #[derive(Default)] pub struct Header { @@ -17,7 +19,12 @@ impl Part for Header { } fn calculate_length(&self) -> usize { - let skip = vec![TagU32::new(8).unwrap(), TagU32::new(9).unwrap()]; + // when calculating the trailer's contribution to the message length, + // the BeginString and BodyLength fields are not to be counted + let skip = vec![ + session_fields::BEGIN_STRING.tag(), + session_fields::BODY_LENGTH.tag(), + ]; self.fields.calculate_length(&skip) } } diff --git a/crates/hotfix-message/src/parts/trailer.rs b/crates/hotfix-message/src/parts/trailer.rs index 64f4704..01e2fee 100644 --- a/crates/hotfix-message/src/parts/trailer.rs +++ b/crates/hotfix-message/src/parts/trailer.rs @@ -1,6 +1,7 @@ use crate::field_map::FieldMap; use crate::parts::Part; -use hotfix_dictionary::TagU32; +use crate::session_fields; +use hotfix_dictionary::IsFieldDefinition; #[derive(Default)] pub struct Trailer { @@ -17,7 +18,9 @@ impl Part for Trailer { } fn calculate_length(&self) -> usize { - let skip = vec![TagU32::new(10).unwrap()]; + // when calculating the trailer's contribution to the message length, + // the checksum itself must be skipped to avoid a circular dependency + let skip = vec![session_fields::CHECK_SUM.tag()]; self.fields.calculate_length(&skip) } } diff --git a/examples/load-testing/src/messages.rs b/examples/load-testing/src/messages.rs index 02ad607..7e54600 100644 --- a/examples/load-testing/src/messages.rs +++ b/examples/load-testing/src/messages.rs @@ -85,7 +85,7 @@ impl OutboundMessage for OutboundMsg { let mut allocation = RepeatingGroup::new(fix44::NO_ALLOCS, fix44::ALLOC_ACCOUNT); allocation.set(fix44::ALLOC_ACCOUNT, order.allocation_account.as_str()); allocation.set(fix44::ALLOC_QTY, order.allocation_quantity); - msg.set_groups(vec![allocation]); + msg.set_groups(vec![allocation]).unwrap(); } } } diff --git a/examples/simple-new-order/src/messages.rs b/examples/simple-new-order/src/messages.rs index dbee98f..5f7092f 100644 --- a/examples/simple-new-order/src/messages.rs +++ b/examples/simple-new-order/src/messages.rs @@ -48,7 +48,7 @@ impl OutboundMessage for OutboundMsg { let mut allocation = RepeatingGroup::new(fix44::NO_ALLOCS, fix44::ALLOC_ACCOUNT); allocation.set(fix44::ALLOC_ACCOUNT, order.allocation_account.as_str()); allocation.set(fix44::ALLOC_QTY, order.allocation_quantity); - msg.set_groups(vec![allocation]); + msg.set_groups(vec![allocation]).unwrap(); } } } From 00ec1910ce2bdf21a4e9271a4c13c6b90b2d711d Mon Sep 17 00:00:00 2001 From: David Steiner Date: Fri, 23 Jan 2026 17:47:34 +0100 Subject: [PATCH 4/4] Remove remaining traces of anyhow in non-test hotfix-message code --- crates/hotfix-message/Cargo.toml | 1 - crates/hotfix-message/src/builder.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/hotfix-message/Cargo.toml b/crates/hotfix-message/Cargo.toml index ecb0c32..79d9b1a 100644 --- a/crates/hotfix-message/Cargo.toml +++ b/crates/hotfix-message/Cargo.toml @@ -24,7 +24,6 @@ workspace = true hotfix-derive = { version = "0.1.2", path = "../hotfix-derive" } hotfix-dictionary = { version = "0.1.5", path = "../hotfix-dictionary" } -anyhow.workspace = true chrono.workspace = true indexmap.workspace = true thiserror.workspace = true diff --git a/crates/hotfix-message/src/builder.rs b/crates/hotfix-message/src/builder.rs index 063cdc4..fff0404 100644 --- a/crates/hotfix-message/src/builder.rs +++ b/crates/hotfix-message/src/builder.rs @@ -31,7 +31,7 @@ pub struct MessageBuilder { } impl MessageBuilder { - pub fn new(dict: Dictionary, config: Config) -> anyhow::Result { + pub fn new(dict: Dictionary, config: Config) -> ParserResult { let header_tags = Self::get_tags_for_component(&dict, "StandardHeader")?; let trailer_tags = Self::get_tags_for_component(&dict, "StandardTrailer")?; let message_definitions = build_message_specifications(&dict)?;