Skip to content

Commit f6015bb

Browse files
authored
chore: remove anyhow from hotfix-message crate (#285)
* Remove unwraps in build_message_specifications * Disallow unwraps and expects in builder.rs * Disallow unwraps and expects everywhere in hotfix-message * Remove remaining traces of anyhow in non-test hotfix-message code
1 parent 3d5f2fb commit f6015bb

18 files changed

Lines changed: 109 additions & 69 deletions

File tree

File renamed without changes.

crates/hotfix-message/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ workspace = true
2424
hotfix-derive = { version = "0.1.2", path = "../hotfix-derive" }
2525
hotfix-dictionary = { version = "0.1.5", path = "../hotfix-dictionary" }
2626

27-
anyhow.workspace = true
2827
chrono.workspace = true
2928
indexmap.workspace = true
3029
thiserror.workspace = true

crates/hotfix-message/src/builder.rs

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::message::{Config, Message};
66
use crate::parsed_message::{GarbledReason, InvalidReason, ParsedMessage};
77
use crate::parts::{Body, Header, RepeatingGroup, Trailer};
88
use crate::tags::{BEGIN_STRING, BODY_LENGTH, CHECK_SUM, MSG_TYPE};
9-
use anyhow::anyhow;
109
use hotfix_dictionary::{Dictionary, LayoutItem, LayoutItemKind, TagU32};
1110
use std::collections::{HashMap, HashSet};
1211

@@ -32,7 +31,7 @@ pub struct MessageBuilder {
3231
}
3332

3433
impl MessageBuilder {
35-
pub fn new(dict: Dictionary, config: Config) -> anyhow::Result<Self> {
34+
pub fn new(dict: Dictionary, config: Config) -> ParserResult<Self> {
3635
let header_tags = Self::get_tags_for_component(&dict, "StandardHeader")?;
3736
let trailer_tags = Self::get_tags_for_component(&dict, "StandardTrailer")?;
3837
let message_definitions = build_message_specifications(&dict)?;
@@ -66,7 +65,8 @@ impl MessageBuilder {
6665
}
6766
};
6867

69-
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
68+
#[allow(clippy::expect_used)]
69+
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");
7070
let (body, next) = match self.build_body(msg_type, &mut parser, next) {
7171
Ok((body, field)) => (body, field),
7272
Err(err) => {
@@ -174,9 +174,10 @@ impl MessageBuilder {
174174
} else {
175175
// check the message type once all other header fields have been parsed
176176
// we delay it until after parsing so our rejection has access to fields like the sequence number
177+
#[allow(clippy::expect_used)]
177178
let msg_type = header
178179
.get::<&str>(MSG_TYPE)
179-
.expect("this should never fail as we've verified the integrity of the header");
180+
.expect("this never fails as we've verified the integrity of the header");
180181
if self.dict.message_by_msgtype(msg_type).is_none() {
181182
return Err(ParserError::InvalidMsgType(msg_type.to_string()));
182183
}
@@ -197,15 +198,17 @@ impl MessageBuilder {
197198
let mut field = next_field;
198199

199200
while message_def.contains_tag(field.tag) {
200-
let tag = field.tag.get();
201+
let tag = field.tag;
201202
body.store_field(field);
202203

203204
// check if it's the start of a group and parse the group as needed
204-
let field_def = self.get_dict_field_by_tag(tag)?;
205-
match message_def.get_group(TagU32::new(tag).unwrap()) {
205+
let field_def = self.get_dict_field_by_tag(tag.get())?;
206+
match message_def.get_group(tag) {
206207
Some(group_def) => {
207208
let (groups, next) = Self::parse_groups(parser, group_def, field_def.tag())?;
208-
body.set_groups(groups);
209+
#[allow(clippy::expect_used)]
210+
body.set_groups(groups)
211+
.expect("groups are guaranteed to be valid at this point");
209212
field = next;
210213
}
211214
None => {
@@ -259,7 +262,10 @@ impl MessageBuilder {
259262
{
260263
let (groups, next) =
261264
Self::parse_groups(parser, nested_group_def, current_tag)?;
262-
group.set_groups(groups);
265+
#[allow(clippy::expect_used)]
266+
group
267+
.set_groups(groups)
268+
.expect("groups are guaranteed to be valid at this point");
263269
next
264270
} else {
265271
parser
@@ -316,11 +322,11 @@ impl MessageBuilder {
316322
fn get_tags_for_component(
317323
dict: &Dictionary,
318324
component_name: &str,
319-
) -> anyhow::Result<HashSet<TagU32>> {
325+
) -> ParserResult<HashSet<TagU32>> {
320326
let mut tags = HashSet::new();
321327
let component = dict
322328
.component_by_name(component_name)
323-
.ok_or(ParserError::InvalidComponent(component_name.to_string()))?;
329+
.ok_or_else(|| ParserError::InvalidComponent(component_name.to_string()))?;
324330
for item in component.items() {
325331
if let LayoutItemKind::Field(field) = item.kind() {
326332
tags.insert(field.tag());
@@ -425,6 +431,7 @@ impl GroupSpecification {
425431
}
426432

427433
pub fn delimiter_tag(&self) -> TagU32 {
434+
#[allow(clippy::expect_used)]
428435
self.fields
429436
.first()
430437
.expect("groups always have at least one field")
@@ -457,7 +464,7 @@ impl MessageSpecification {
457464

458465
fn build_message_specifications(
459466
dict: &Dictionary,
460-
) -> anyhow::Result<HashMap<String, MessageSpecification>> {
467+
) -> ParserResult<HashMap<String, MessageSpecification>> {
461468
let mut definitions = HashMap::new();
462469

463470
for message in dict.messages() {
@@ -467,26 +474,25 @@ fn build_message_specifications(
467474
.flatten()
468475
.collect();
469476

470-
let message_def = MessageSpecification {
471-
fields,
472-
groups: message.layout().fold(HashMap::new(), |mut acc, item| {
473-
acc.extend(extract_groups(dict, item).unwrap());
474-
acc
475-
}),
476-
};
477+
let mut groups = HashMap::new();
478+
for item in message.layout() {
479+
groups.extend(extract_groups(dict, item)?);
480+
}
481+
482+
let message_def = MessageSpecification { fields, groups };
477483
definitions.insert(message.msg_type().to_string(), message_def);
478484
}
479485

480486
Ok(definitions)
481487
}
482488

483-
fn extract_fields(dict: &Dictionary, item: LayoutItem) -> anyhow::Result<Vec<FieldSpecification>> {
489+
fn extract_fields(dict: &Dictionary, item: LayoutItem) -> ParserResult<Vec<FieldSpecification>> {
484490
let is_required = item.required();
485491
let fields = match item.kind() {
486492
LayoutItemKind::Component(c) => {
487493
let component = dict
488494
.component_by_name(c.name())
489-
.ok_or_else(|| anyhow!("missing component"))?;
495+
.ok_or_else(|| ParserError::InvalidComponent(c.name().to_string()))?;
490496
component
491497
.items()
492498
.flat_map(|i| extract_fields(dict, i))
@@ -509,18 +515,22 @@ fn extract_fields(dict: &Dictionary, item: LayoutItem) -> anyhow::Result<Vec<Fie
509515
fn extract_groups(
510516
dict: &Dictionary,
511517
item: LayoutItem,
512-
) -> anyhow::Result<HashMap<TagU32, GroupSpecification>> {
518+
) -> ParserResult<HashMap<TagU32, GroupSpecification>> {
513519
let mut groups = HashMap::new();
514520
match item.kind() {
515521
LayoutItemKind::Component(c) => {
516522
let component = dict
517523
.component_by_name(c.name())
518-
.ok_or_else(|| anyhow!("missing component"))?;
519-
component.items().for_each(|i| {
520-
groups.extend(extract_groups(dict, i).unwrap());
521-
})
524+
.ok_or_else(|| ParserError::InvalidComponent(c.name().to_string()))?;
525+
for i in component.items() {
526+
groups.extend(extract_groups(dict, i)?);
527+
}
522528
}
523529
LayoutItemKind::Group(field, items) => {
530+
let mut nested_groups = HashMap::new();
531+
for i in items.iter() {
532+
nested_groups.extend(extract_groups(dict, i.clone())?);
533+
}
524534
groups.insert(
525535
field.tag(),
526536
GroupSpecification {
@@ -530,10 +540,7 @@ fn extract_groups(
530540
.flat_map(|i| extract_fields(dict, i.clone()))
531541
.flatten()
532542
.collect(),
533-
nested_groups: items.iter().fold(HashMap::new(), |mut acc, i| {
534-
acc.extend(extract_groups(dict, i.clone()).unwrap());
535-
acc
536-
}),
543+
nested_groups,
537544
},
538545
);
539546
}

crates/hotfix-message/src/encoder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,14 @@ mod tests {
138138
));
139139
subparty_2.store_field(Field::new(fix44::PARTY_SUB_ID_TYPE.tag(), b"2".to_vec()));
140140

141-
party_1.set_groups(vec![subparty_1, subparty_2]);
141+
party_1.set_groups(vec![subparty_1, subparty_2])?;
142142

143143
let mut party_2 = RepeatingGroup::new(fix44::NO_PARTY_I_DS, fix44::PARTY_ID);
144144
party_2.store_field(Field::new(fix44::PARTY_ID.tag(), b"PARTY_B".to_vec()));
145145
party_2.store_field(Field::new(fix44::PARTY_ID_SOURCE.tag(), b"D".to_vec()));
146146
party_2.store_field(Field::new(fix44::PARTY_ROLE.tag(), b"2".to_vec()));
147147

148-
msg.body.set_groups(vec![party_1, party_2]);
148+
msg.body.set_groups(vec![party_1, party_2])?;
149149
let config = Config { separator: b'|' };
150150
let raw_message = msg.encode(&config)?;
151151

crates/hotfix-message/src/encoding/definitions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub struct HardCodedFixFieldDefinition {
2323
impl dict::IsFieldDefinition for HardCodedFixFieldDefinition {
2424
#[inline]
2525
fn tag(&self) -> TagU32 {
26+
#[allow(clippy::expect_used)]
2627
TagU32::new(self.tag).expect("Invalid tag number 0.")
2728
}
2829

crates/hotfix-message/src/encoding/field_access.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ where
8888
/// valid UTF-8. As such, you should only *ever* use this function for
8989
/// [`FieldType`] implementors that are guaranteed to be representable with
9090
/// valid UTF-8 (like numbers with ASCII digits).
91+
#[allow(clippy::expect_used)]
9192
fn to_string(&self) -> String {
9293
String::from_utf8(self.to_bytes()).expect("Invalid UTF-8 representation of FIX field.")
9394
}

crates/hotfix-message/src/encoding/field_types.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,12 @@ where
115115
{
116116
let serialized = item.to_bytes();
117117
let bytes = &serialized[..];
118-
let deserialized = T::deserialize(bytes).ok().unwrap();
119-
let deserialized_lossy = T::deserialize_lossy(bytes).ok().unwrap();
118+
let Some(deserialized) = T::deserialize(bytes).ok() else {
119+
return false;
120+
};
121+
let Some(deserialized_lossy) = T::deserialize_lossy(bytes).ok() else {
122+
return false;
123+
};
120124
deserialized == item && deserialized_lossy == item
121125
}
122126

crates/hotfix-message/src/encoding/field_types/tagu32.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ impl<'a> FieldType<'a> for TagU32 {
1414
B: Buffer,
1515
{
1616
let initial_len = buffer.len();
17-
write!(BufferWriter(buffer), "{self}").unwrap();
17+
let _ = write!(BufferWriter(buffer), "{self}");
1818
buffer.len() - initial_len
1919
}
2020

@@ -29,6 +29,7 @@ impl<'a> FieldType<'a> for TagU32 {
2929
#[inline]
3030
fn deserialize_lossy(data: &'a [u8]) -> Result<Self, Self::Error> {
3131
let n = u32::deserialize_lossy(data)?;
32-
Ok(TagU32::new(n.max(1)).unwrap())
32+
#[allow(clippy::expect_used)]
33+
Ok(TagU32::new(n.max(1)).expect("guaranteed to be non-zero"))
3334
}
3435
}

crates/hotfix-message/src/encoding/field_types/timestamp.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,20 @@ impl Timestamp {
2525
}
2626

2727
/// Returns the current UTC system time with millisecond precision.
28+
#[allow(clippy::expect_used)]
2829
pub fn utc_now() -> Self {
2930
use chrono::{Datelike, Timelike};
3031
let utc: chrono::DateTime<chrono::Utc> = chrono::Utc::now();
31-
let date = Date::new(utc.year() as u32, utc.month(), utc.day());
32+
let date = Date::new(utc.year() as u32, utc.month(), utc.day())
33+
.expect("chrono::Utc::now() always produces a valid date");
3234
let time = Time::from_hmsm(
3335
utc.hour(),
3436
utc.minute(),
3537
utc.second(),
3638
utc.nanosecond() / 1_000_000,
3739
)
38-
.unwrap();
39-
Self::new(date.unwrap(), time)
40+
.expect("chrono::Utc::now() always produces a valid time");
41+
Self::new(date, time)
4042
}
4143

4244
/// Returns the date of `self`.

crates/hotfix-message/src/encoding/field_types/tz.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ impl Tz {
5858
#[cfg(feature = "utils-chrono")]
5959
#[cfg_attr(doc_cfg, doc(cfg(feature = "utils-chrono")))]
6060
pub fn to_chrono_offset(&self) -> chrono::FixedOffset {
61-
// unwrap(): we already verified that the offset is within bounds during
62-
// deserialization
63-
chrono::FixedOffset::east_opt(self.offset().1.as_secs() as i32).unwrap()
61+
#[allow(clippy::expect_used)]
62+
chrono::FixedOffset::east_opt(self.offset().1.as_secs() as i32)
63+
.expect("we already verified that the offset is within bounds during deserialisation")
6464
}
6565

6666
/// Creates a [`Tz`] from a [`chrono::FixedOffset`].

0 commit comments

Comments
 (0)