From 9d351836fa34823a7e4e5e6a41d132cbe314332a Mon Sep 17 00:00:00 2001 From: Michael Wilner Date: Tue, 21 Oct 2025 14:03:44 -0500 Subject: [PATCH 1/2] ValidateFieldsHaveValues --- config/configuration.go | 13 ++++++++++ session_factory.go | 8 ++++++ validation.go | 54 +++++++++++++++++++++++------------------ validation_test.go | 42 ++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 23 deletions(-) diff --git a/config/configuration.go b/config/configuration.go index 1425cde7b..4b9d322d4 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -454,6 +454,19 @@ const ( // - N ValidateFieldsOutOfOrder string = "ValidateFieldsOutOfOrder" + // ValidateFieldsHaveValues if set to N, fields without values (i.e. |11=| for an empty ClOrdID) + // will not be rejected, even if RejectInvalidMessage is set to N. + // Useful for connecting to systems that improperly send empty tags. + // + // Required: No + // + // Default: Y + // + // Valid Values: + // - Y + // - N + ValidateFieldsHaveValues string = "ValidateFieldsHaveValues" + // CheckLatency if set to Y, messages must be received from the counter-party within a defined number of seconds. // It is useful to turn this off if a system uses localtime for it's timestamps instead of GMT. // diff --git a/session_factory.go b/session_factory.go index a2291cad7..613bb2069 100644 --- a/session_factory.go +++ b/session_factory.go @@ -99,6 +99,12 @@ func (f sessionFactory) newSession( } } + if settings.HasSetting(config.ValidateFieldsHaveValues) { + if validatorSettings.CheckFieldsHaveValues, err = settings.BoolSetting(config.ValidateFieldsHaveValues); err != nil { + return + } + } + if settings.HasSetting(config.RejectInvalidMessage) { if validatorSettings.RejectInvalidMessage, err = settings.BoolSetting(config.RejectInvalidMessage); err != nil { return @@ -117,6 +123,8 @@ func (f sessionFactory) newSession( } } + // Always use a default message validator without data dictionaries + s.Validator = NewValidator(validatorSettings, nil, nil) if sessionID.IsFIXT() { if s.DefaultApplVerID, err = settings.Setting(config.DefaultApplVerID); err != nil { return diff --git a/validation.go b/validation.go index e9b718138..82b535aca 100644 --- a/validation.go +++ b/validation.go @@ -34,12 +34,14 @@ type ValidatorSettings struct { RejectInvalidMessage bool AllowUnknownMessageFields bool CheckUserDefinedFields bool + CheckFieldsHaveValues bool } // Default configuration for message validation. // See http://www.quickfixengine.org/quickfix/doc/html/configuration.html. var defaultValidatorSettings = ValidatorSettings{ CheckFieldsOutOfOrder: true, + CheckFieldsHaveValues: true, RejectInvalidMessage: true, AllowUnknownMessageFields: false, CheckUserDefinedFields: true, @@ -102,21 +104,21 @@ func (v *fixtValidator) Validate(msg *Message) MessageRejectError { } func validateFIX(d *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError { - if err := validateMsgType(d, msgType, msg); err != nil { - return err - } - - if err := validateRequired(d, d, msgType, msg); err != nil { - return err - } + if d != nil { + if err := validateMsgType(d, msgType, msg); err != nil { + return err + } - if settings.CheckFieldsOutOfOrder { - if err := validateOrder(msg); err != nil { + if err := validateRequired(d, d, msgType, msg); err != nil { return err } } - if settings.RejectInvalidMessage { + if err := validateFieldContent(msg, settings.CheckFieldsHaveValues, settings.CheckFieldsOutOfOrder); err != nil { + return err + } + + if settings.RejectInvalidMessage && d != nil { if err := validateFields(d, d, settings, msgType, msg); err != nil { return err } @@ -130,21 +132,21 @@ func validateFIX(d *datadictionary.DataDictionary, settings ValidatorSettings, m } func validateFIXT(transportDD, appDD *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError { - if err := validateMsgType(appDD, msgType, msg); err != nil { - return err - } - - if err := validateRequired(transportDD, appDD, msgType, msg); err != nil { - return err - } + if appDD != nil && transportDD != nil { + if err := validateMsgType(appDD, msgType, msg); err != nil { + return err + } - if settings.CheckFieldsOutOfOrder { - if err := validateOrder(msg); err != nil { + if err := validateRequired(transportDD, appDD, msgType, msg); err != nil { return err } } - if settings.RejectInvalidMessage { + if err := validateFieldContent(msg, settings.CheckFieldsHaveValues, settings.CheckFieldsOutOfOrder); err != nil { + return err + } + + if settings.RejectInvalidMessage && appDD != nil && transportDD != nil { if err := validateFields(transportDD, appDD, settings, msgType, msg); err != nil { return err } @@ -270,20 +272,26 @@ func validateVisitGroupField(fieldDef *datadictionary.FieldDef, fieldStack []Tag return fieldStack, nil } -func validateOrder(msg *Message) MessageRejectError { +func validateFieldContent(msg *Message, checkFieldsHaveValues, checkFieldsOutOfOrder bool) MessageRejectError { + if !checkFieldsHaveValues && !checkFieldsOutOfOrder { + return nil + } inHeader := true inTrailer := false for _, field := range msg.fields { t := field.tag + if checkFieldsHaveValues && len(field.value) == 0 { + return TagSpecifiedWithoutAValue(t) + } switch { case inHeader && t.IsHeader(): case inHeader && !t.IsHeader(): inHeader = false - case !inHeader && t.IsHeader(): + case !inHeader && t.IsHeader() && checkFieldsOutOfOrder: return tagSpecifiedOutOfRequiredOrder(t) case t.IsTrailer(): inTrailer = true - case inTrailer && !t.IsTrailer(): + case inTrailer && !t.IsTrailer() && checkFieldsOutOfOrder: return tagSpecifiedOutOfRequiredOrder(t) } } diff --git a/validation_test.go b/validation_test.go index 1647dc94e..f1096c2bf 100644 --- a/validation_test.go +++ b/validation_test.go @@ -44,6 +44,8 @@ func TestValidate(t *testing.T) { tcInvalidTagNumberTrailerFixT(), tcTagSpecifiedWithoutAValue(), tcTagSpecifiedWithoutAValueFixT(), + tcTagSpecifiedWithoutAValueValidateHasValues(), + tcTagSpecifiedWithoutAValueFixTValidateHasValues(), tcInvalidMsgType(), tcInvalidMsgTypeFixT(), tcValueIsIncorrect(), @@ -512,6 +514,27 @@ func tcTagSpecifiedWithoutAValue() validateTest { } } +func tcTagSpecifiedWithoutAValueValidateHasValues() validateTest { + dict, _ := datadictionary.Parse("spec/FIX40.xml") + settings := defaultValidatorSettings + settings.RejectInvalidMessage = false + settings.CheckFieldsHaveValues = true + validator := NewValidator(settings, dict, nil) + builder := createFIX40NewOrderSingle() + + bogusTag := Tag(109) + builder.Body.SetField(bogusTag, FIXString("")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Tag SpecifiedWithoutAValue", + Validator: validator, + MessageBytes: msgBytes, + ExpectedRejectReason: rejectReasonTagSpecifiedWithoutAValue, + ExpectedRefTagID: &bogusTag, + } +} + func tcTagSpecifiedWithoutAValueFixT() validateTest { tDict, _ := datadictionary.Parse("spec/FIXT11.xml") appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml") @@ -531,6 +554,25 @@ func tcTagSpecifiedWithoutAValueFixT() validateTest { } } +func tcTagSpecifiedWithoutAValueFixTValidateHasValues() validateTest { + tDict, _ := datadictionary.Parse("spec/FIXT11.xml") + appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml") + validator := NewValidator(defaultValidatorSettings, appDict, tDict) + builder := createFIX50SP2NewOrderSingle() + + bogusTag := Tag(109) + builder.Body.SetField(bogusTag, FIXString("")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Tag SpecifiedWithoutAValue FIXT", + Validator: validator, + MessageBytes: msgBytes, + ExpectedRejectReason: rejectReasonTagSpecifiedWithoutAValue, + ExpectedRefTagID: &bogusTag, + } +} + func tcInvalidMsgType() validateTest { dict, _ := datadictionary.Parse("spec/FIX40.xml") validator := NewValidator(defaultValidatorSettings, dict, nil) From 8e124b4de44df1e73bd935c5744e0cdad9f231ff Mon Sep 17 00:00:00 2001 From: Michael Wilner Date: Tue, 21 Oct 2025 14:27:11 -0500 Subject: [PATCH 2/2] Additional test for validation without a data dictionary --- validation_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/validation_test.go b/validation_test.go index f1096c2bf..7973464d1 100644 --- a/validation_test.go +++ b/validation_test.go @@ -46,6 +46,7 @@ func TestValidate(t *testing.T) { tcTagSpecifiedWithoutAValueFixT(), tcTagSpecifiedWithoutAValueValidateHasValues(), tcTagSpecifiedWithoutAValueFixTValidateHasValues(), + tcTagSpecifiedWithoutAValueValidateHasValuesNoDataDictionary(), tcInvalidMsgType(), tcInvalidMsgTypeFixT(), tcValueIsIncorrect(), @@ -535,6 +536,26 @@ func tcTagSpecifiedWithoutAValueValidateHasValues() validateTest { } } +func tcTagSpecifiedWithoutAValueValidateHasValuesNoDataDictionary() validateTest { + settings := defaultValidatorSettings + settings.RejectInvalidMessage = false + settings.CheckFieldsHaveValues = true + validator := NewValidator(settings, nil, nil) + builder := createFIX40NewOrderSingle() + + bogusTag := Tag(109) + builder.Body.SetField(bogusTag, FIXString("")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Tag SpecifiedWithoutAValue", + Validator: validator, + MessageBytes: msgBytes, + ExpectedRejectReason: rejectReasonTagSpecifiedWithoutAValue, + ExpectedRefTagID: &bogusTag, + } +} + func tcTagSpecifiedWithoutAValueFixT() validateTest { tDict, _ := datadictionary.Parse("spec/FIXT11.xml") appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")