Skip to content

Commit 27b4b41

Browse files
committed
feat: allow unknown user defined fields
1 parent b8cb424 commit 27b4b41

11 files changed

Lines changed: 458 additions & 67 deletions

File tree

crates/hotfix-message/src/builder.rs

Lines changed: 333 additions & 61 deletions
Large diffs are not rendered by default.

crates/hotfix-message/src/encoder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ mod tests {
7575
msg.set(fix44::PRICE, 150);
7676
msg.set(fix44::ORDER_QTY, 60);
7777

78-
let config = Config { separator: b'|' };
78+
let config = Config::with_separator(b'|');
7979
let raw_message = msg.encode(&config)?;
8080

8181
let builder = MessageBuilder::new(Dictionary::fix44(), config)?;
@@ -146,7 +146,7 @@ mod tests {
146146
party_2.store_field(Field::new(fix44::PARTY_ROLE.tag(), b"2".to_vec()));
147147

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

152152
let builder = MessageBuilder::new(Dictionary::fix44(), config)?;

crates/hotfix-message/src/message.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,16 +119,28 @@ impl Part for Message {
119119
#[derive(Clone, Copy)]
120120
pub struct Config {
121121
pub(crate) separator: u8,
122+
pub(crate) validate_user_defined_fields: bool,
122123
}
123124

124125
impl Config {
125126
pub const fn with_separator(separator: u8) -> Self {
126-
Self { separator }
127+
Self {
128+
separator,
129+
validate_user_defined_fields: true,
130+
}
131+
}
132+
133+
pub const fn validate_user_defined_fields(mut self, value: bool) -> Self {
134+
self.validate_user_defined_fields = value;
135+
self
127136
}
128137
}
129138

130139
impl Default for Config {
131140
fn default() -> Self {
132-
Self { separator: SOH }
141+
Self {
142+
separator: SOH,
143+
validate_user_defined_fields: true,
144+
}
133145
}
134146
}

crates/hotfix/src/config.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,63 @@ pub struct SessionConfig {
114114

115115
/// The schedule configuration for the session
116116
pub schedule: Option<ScheduleConfig>,
117+
118+
/// The validation configuration for the session
119+
#[serde(default)]
120+
pub validation: ValidationConfig,
121+
}
122+
123+
#[derive(Clone, Debug, Deserialize)]
124+
/// The configuration of validation rules.
125+
pub struct ValidationConfig {
126+
/// Specifies whether unknown user-defined tags (>= 5000) should cause the message to be rejected.
127+
#[serde(default = "default_true")]
128+
pub validate_user_defined_fields: bool,
129+
}
130+
131+
impl ValidationConfig {
132+
pub fn builder() -> VerificationConfigBuilder {
133+
VerificationConfigBuilder::default()
134+
}
135+
}
136+
137+
impl Default for ValidationConfig {
138+
fn default() -> Self {
139+
VerificationConfigBuilder::default().build()
140+
}
141+
}
142+
143+
pub struct VerificationConfigBuilder {
144+
validate_user_defined_fields: bool,
145+
}
146+
147+
impl Default for VerificationConfigBuilder {
148+
fn default() -> Self {
149+
Self {
150+
validate_user_defined_fields: true,
151+
}
152+
}
153+
}
154+
155+
impl VerificationConfigBuilder {
156+
pub fn new() -> Self {
157+
Self::default()
158+
}
159+
160+
pub fn validate_user_defined_fields(mut self, value: bool) -> Self {
161+
self.validate_user_defined_fields = value;
162+
self
163+
}
164+
165+
pub fn build(self) -> ValidationConfig {
166+
ValidationConfig {
167+
validate_user_defined_fields: self.validate_user_defined_fields,
168+
}
169+
}
170+
}
171+
172+
fn default_true() -> bool {
173+
true
117174
}
118175

119176
/// Errors that may occur when loading configuration.
@@ -170,6 +227,7 @@ reset_on_logon = false
170227
assert_eq!(session_config.tls_config, Some(expected_tls_config));
171228
assert_eq!(session_config.reconnect_interval, 30);
172229
assert_eq!(session_config.logon_timeout, 10);
230+
assert!(session_config.validation.validate_user_defined_fields);
173231
}
174232

175233
#[test]
@@ -439,6 +497,45 @@ end_day = "Friday"
439497
assert_eq!(session_config.reconnect_interval, 15);
440498
}
441499

500+
#[test]
501+
fn test_verification_config_defaults_when_omitted() {
502+
let config_contents = r#"
503+
[[sessions]]
504+
begin_string = "FIX.4.4"
505+
sender_comp_id = "send-comp-id"
506+
target_comp_id = "target-comp-id"
507+
connection_port = 443
508+
connection_host = "127.0.0.1"
509+
heartbeat_interval = 30
510+
"#;
511+
512+
let config: Config = toml::from_str(config_contents).unwrap();
513+
let session_config = config.sessions.first().unwrap();
514+
515+
assert!(session_config.validation.validate_user_defined_fields);
516+
}
517+
518+
#[test]
519+
fn test_verification_config_can_disable_user_defined_field_validation() {
520+
let config_contents = r#"
521+
[[sessions]]
522+
begin_string = "FIX.4.4"
523+
sender_comp_id = "send-comp-id"
524+
target_comp_id = "target-comp-id"
525+
connection_port = 443
526+
connection_host = "127.0.0.1"
527+
heartbeat_interval = 30
528+
529+
[sessions.validation]
530+
validate_user_defined_fields = false
531+
"#;
532+
533+
let config: Config = toml::from_str(config_contents).unwrap();
534+
let session_config = config.sessions.first().unwrap();
535+
536+
assert!(!session_config.validation.validate_user_defined_fields);
537+
}
538+
442539
#[test]
443540
fn test_load_from_path_success() {
444541
let config_contents = r#"

crates/hotfix/src/initiator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ mod tests {
309309
reconnect_interval: 1, // Short for tests
310310
reset_on_logon: false,
311311
schedule: None,
312+
validation: Default::default(),
312313
}
313314
}
314315

crates/hotfix/src/message/verification.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ fn check_target_comp_id(
238238
#[cfg(test)]
239239
mod tests {
240240
use super::{Message, SessionConfig, VerificationFlags, verify_message};
241+
use crate::config::ValidationConfig;
241242
use crate::message::sequence_reset::SequenceReset;
242243
use crate::message::verification_issue::{CompIdType, MessageError, VerificationIssue};
243244
use hotfix_message::field_types::Timestamp;
@@ -258,6 +259,7 @@ mod tests {
258259
reconnect_interval: 0,
259260
reset_on_logon: false,
260261
schedule: None,
262+
validation: ValidationConfig::default(),
261263
}
262264
}
263265

crates/hotfix/src/session.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ where
7979
let schedule_check_timer = sleep(Duration::from_secs(SCHEDULE_CHECK_INTERVAL));
8080

8181
let dictionary = Self::get_data_dictionary(&config)?;
82-
let message_config = MessageConfig::default();
82+
let message_config = MessageConfig::default()
83+
.validate_user_defined_fields(config.validation.validate_user_defined_fields);
8384
let message_builder = MessageBuilder::new(dictionary, message_config)?;
8485
let schedule = config.schedule.as_ref().try_into()?;
8586
let ctx = SessionCtx {
@@ -771,6 +772,7 @@ async fn run_session<App, Store>(
771772
mod tests {
772773
use super::*;
773774
use crate::application::{InboundDecision, OutboundDecision};
775+
use crate::config::ValidationConfig;
774776
use crate::message::OutboundMessage;
775777
use crate::store::{Result as StoreResult, StoreError};
776778
use chrono::{DateTime, Datelike, NaiveDate, NaiveTime, TimeDelta, Timelike};
@@ -907,6 +909,7 @@ mod tests {
907909
reconnect_interval: 30,
908910
reset_on_logon: false,
909911
schedule: None,
912+
validation: ValidationConfig::default(),
910913
}
911914
}
912915

crates/hotfix/src/session/test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ pub(crate) fn create_test_ctx(store: FakeMessageStore) -> SessionCtx<(), FakeMes
9191
reconnect_interval: 30,
9292
reset_on_logon: false,
9393
schedule: None,
94+
validation: Default::default(),
9495
},
9596
store,
9697
application: (),

crates/hotfix/tests/connection_test_cases/connect_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ fn create_session_config(host: &str, port: u16, tls_config: Option<TlsConfig>) -
2626
reconnect_interval: 30,
2727
reset_on_logon: false,
2828
schedule: None,
29+
validation: Default::default(),
2930
}
3031
}
3132

crates/hotfix/tests/session_test_cases/common/setup.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ pub fn create_session_config() -> SessionConfig {
119119
reconnect_interval: 30,
120120
reset_on_logon: false,
121121
schedule: None,
122+
validation: Default::default(),
122123
}
123124
}
124125

0 commit comments

Comments
 (0)