Skip to content

Commit

Permalink
Merge pull request #632 from haoyang1994/tag_check_options
Browse files Browse the repository at this point in the history
feat: AllowUnknownMessageFields & CheckUserDefinedFields
  • Loading branch information
ackleymi committed Apr 26, 2024
2 parents 2c25e08 + 74fd67b commit c3c49cb
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 20 deletions.
2 changes: 2 additions & 0 deletions config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ const (
MaxLatency string = "MaxLatency"
PersistMessages string = "PersistMessages"
RejectInvalidMessage string = "RejectInvalidMessage"
AllowUnknownMessageFields string = "AllowUnknownMsgFields"
CheckUserDefinedFields string = "ValidateUserDefinedFields"
DynamicSessions string = "DynamicSessions"
DynamicQualifier string = "DynamicQualifier"
)
12 changes: 12 additions & 0 deletions session_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ func (f sessionFactory) newSession(
}
}

if settings.HasSetting(config.AllowUnknownMessageFields) {
if validatorSettings.AllowUnknownMessageFields, err = settings.BoolSetting(config.AllowUnknownMessageFields); err != nil {
return
}
}

if settings.HasSetting(config.CheckUserDefinedFields) {
if validatorSettings.CheckUserDefinedFields, err = settings.BoolSetting(config.CheckUserDefinedFields); err != nil {
return
}
}

if sessionID.IsFIXT() {
if s.DefaultApplVerID, err = settings.Setting(config.DefaultApplVerID); err != nil {
return
Expand Down
80 changes: 60 additions & 20 deletions validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,30 @@ import (
"github.com/quickfixgo/quickfix/datadictionary"
)

const (
UserDefinedTagMin int = 5000
)

// Validator validates a FIX message.
type Validator interface {
Validate(*Message) MessageRejectError
}

// ValidatorSettings describe validation behavior.
type ValidatorSettings struct {
CheckFieldsOutOfOrder bool
RejectInvalidMessage bool
CheckFieldsOutOfOrder bool
RejectInvalidMessage bool
AllowUnknownMessageFields bool
CheckUserDefinedFields bool
}

// Default configuration for message validation.
// See http://www.quickfixengine.org/quickfix/doc/html/configuration.html.
var defaultValidatorSettings = ValidatorSettings{
CheckFieldsOutOfOrder: true,
RejectInvalidMessage: true,
CheckFieldsOutOfOrder: true,
RejectInvalidMessage: true,
AllowUnknownMessageFields: false,
CheckUserDefinedFields: true,
}

type fixValidator struct {
Expand Down Expand Up @@ -109,11 +117,11 @@ func validateFIX(d *datadictionary.DataDictionary, settings ValidatorSettings, m
}

if settings.RejectInvalidMessage {
if err := validateFields(d, d, msgType, msg); err != nil {
if err := validateFields(d, d, settings, msgType, msg); err != nil {
return err
}

if err := validateWalk(d, d, msgType, msg); err != nil {
if err := validateWalk(d, d, settings, msgType, msg); err != nil {
return err
}
}
Expand All @@ -137,11 +145,11 @@ func validateFIXT(transportDD, appDD *datadictionary.DataDictionary, settings Va
}

if settings.RejectInvalidMessage {
if err := validateFields(transportDD, appDD, msgType, msg); err != nil {
if err := validateFields(transportDD, appDD, settings, msgType, msg); err != nil {
return err
}

if err := validateWalk(transportDD, appDD, msgType, msg); err != nil {
if err := validateWalk(transportDD, appDD, settings, msgType, msg); err != nil {
return err
}
}
Expand All @@ -156,7 +164,7 @@ func validateMsgType(d *datadictionary.DataDictionary, msgType string, _ *Messag
return nil
}

func validateWalk(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, msgType string, msg *Message) MessageRejectError {
func validateWalk(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError {
remainingFields := msg.fields
iteratedTags := make(datadictionary.TagSet)

Expand All @@ -178,15 +186,19 @@ func validateWalk(transportDD *datadictionary.DataDictionary, appDD *datadiction
messageDef = appDD.Messages[msgType]
}

if fieldDef, ok = messageDef.Fields[int(tag)]; !ok {
return TagNotDefinedForThisMessageType(tag)
}

if _, duplicate := iteratedTags[int(tag)]; duplicate {
return tagAppearsMoreThanOnce(tag)
}
iteratedTags.Add(int(tag))

if fieldDef, ok = messageDef.Fields[int(tag)]; !ok {
if !checkFieldNotDefined(settings, tag) {
return TagNotDefinedForThisMessageType(tag)
}
remainingFields = remainingFields[1:]
continue
}

if remainingFields, err = validateVisitField(fieldDef, remainingFields); err != nil {
return err
}
Expand Down Expand Up @@ -306,19 +318,24 @@ func validateRequiredFieldMap(_ *Message, requiredTags map[int]struct{}, fieldMa
return nil
}

func validateFields(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, msgType string, message *Message) MessageRejectError {
func validateFields(transportDD *datadictionary.DataDictionary,
appDD *datadictionary.DataDictionary,
settings ValidatorSettings,
msgType string,
message *Message,
) MessageRejectError {
for _, field := range message.fields {
switch {
case field.tag.IsHeader():
if err := validateField(transportDD, transportDD.Header.Tags, field); err != nil {
if err := validateField(transportDD, settings, transportDD.Header.Tags, field); err != nil {
return err
}
case field.tag.IsTrailer():
if err := validateField(transportDD, transportDD.Trailer.Tags, field); err != nil {
if err := validateField(transportDD, settings, transportDD.Trailer.Tags, field); err != nil {
return err
}
default:
if err := validateField(appDD, appDD.Messages[msgType].Tags, field); err != nil {
if err := validateField(appDD, settings, appDD.Messages[msgType].Tags, field); err != nil {
return err
}
}
Expand All @@ -327,23 +344,46 @@ func validateFields(transportDD *datadictionary.DataDictionary, appDD *datadicti
return nil
}

func validateField(d *datadictionary.DataDictionary, _ datadictionary.TagSet, field TagValue) MessageRejectError {
func getFieldType(d *datadictionary.DataDictionary, field int) (*datadictionary.FieldType, bool) {
fieldType, isMessageField := d.FieldTypeByTag[field]
return fieldType, isMessageField
}

func checkFieldNotDefined(settings ValidatorSettings, field Tag) bool {
fail := false
if int(field) < UserDefinedTagMin {
fail = !settings.AllowUnknownMessageFields
} else {
fail = settings.CheckUserDefinedFields
}
return !fail
}

func validateField(d *datadictionary.DataDictionary,
settings ValidatorSettings,
_ datadictionary.TagSet,
field TagValue,
) MessageRejectError {
if len(field.value) == 0 {
return TagSpecifiedWithoutAValue(field.tag)
}

if _, valid := d.FieldTypeByTag[int(field.tag)]; !valid {
fieldType, isMessageField := getFieldType(d, int(field.tag))
if !isMessageField && !checkFieldNotDefined(settings, field.tag) {
return InvalidTagNumber(field.tag)
}

if !isMessageField {
return nil
}

allowedValues := d.FieldTypeByTag[int(field.tag)].Enums
if len(allowedValues) != 0 {
if _, validValue := allowedValues[string(field.value)]; !validValue {
return ValueIsIncorrect(field.tag)
}
}

fieldType := d.FieldTypeByTag[int(field.tag)]
var prototype FieldValue
switch fieldType.Type {
case "MULTIPLESTRINGVALUE", "MULTIPLEVALUESTRING":
Expand Down
170 changes: 170 additions & 0 deletions validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ func TestValidate(t *testing.T) {
tcInvalidTagCheckDisabledFixT(),
tcInvalidTagCheckEnabled(),
tcInvalidTagCheckEnabledFixT(),
tcAllowUnknownMessageFieldsEnabled(),
tcAllowUnknownMessageFieldsEnabledFixT(),
tcAllowUnknownMessageFieldsDisabled(),
tcAllowUnknownMessageFieldsDisabledFixT(),
tcCheckUserDefinedFieldsEnabled(),
tcCheckUserDefinedFieldsEnabledFixT(),
tcCheckUserDefinedFieldsDisabled(),
tcCheckUserDefinedFieldsDisabledFixT(),
tcMultipleRepeatingGroupFields(),
}

Expand Down Expand Up @@ -786,6 +794,168 @@ func tcInvalidTagCheckEnabledFixT() validateTest {
}
}

func tcAllowUnknownMessageFieldsEnabled() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
customValidatorSettings := defaultValidatorSettings
customValidatorSettings.AllowUnknownMessageFields = true
validator := NewValidator(customValidatorSettings, dict, nil)

builder := createFIX40NewOrderSingle()
tag := Tag(41)
builder.Body.SetField(tag, FIXString("hello"))
msgBytes := builder.build()

return validateTest{
TestName: "Allow Unknown Message Fields - Enabled",
Validator: validator,
MessageBytes: msgBytes,
DoNotExpectReject: true,
}
}

func tcAllowUnknownMessageFieldsEnabledFixT() validateTest {
tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
customValidatorSettings := defaultValidatorSettings
customValidatorSettings.AllowUnknownMessageFields = true
validator := NewValidator(customValidatorSettings, appDict, tDict)

builder := createFIX50SP2NewOrderSingle()
tag := Tag(41)
builder.Body.SetField(tag, FIXString("hello"))
msgBytes := builder.build()

return validateTest{
TestName: "Allow Unknown Message Fields - Enabled FIXT",
Validator: validator,
MessageBytes: msgBytes,
DoNotExpectReject: true,
}
}

func tcAllowUnknownMessageFieldsDisabled() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
customValidatorSettings := defaultValidatorSettings
customValidatorSettings.AllowUnknownMessageFields = false
validator := NewValidator(customValidatorSettings, dict, nil)

builder := createFIX40NewOrderSingle()
tag := Tag(41)
builder.Body.SetField(tag, FIXString("hello"))
msgBytes := builder.build()

return validateTest{
TestName: "Allow Unknown Message Fields - Disabled",
Validator: validator,
MessageBytes: msgBytes,
DoNotExpectReject: false,
ExpectedRejectReason: rejectReasonTagNotDefinedForThisMessageType,
ExpectedRefTagID: &tag,
}
}

func tcAllowUnknownMessageFieldsDisabledFixT() validateTest {
tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
customValidatorSettings := defaultValidatorSettings
customValidatorSettings.RejectInvalidMessage = true
validator := NewValidator(customValidatorSettings, appDict, tDict)

builder := createFIX50SP2NewOrderSingle()
tag := Tag(41)
builder.Body.SetField(tag, FIXString("hello"))
msgBytes := builder.build()

return validateTest{
TestName: "Allow Unknown Message Fields - Disabled FIXT",
Validator: validator,
MessageBytes: msgBytes,
DoNotExpectReject: false,
ExpectedRejectReason: rejectReasonTagNotDefinedForThisMessageType,
ExpectedRefTagID: &tag,
}
}

func tcCheckUserDefinedFieldsEnabled() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
customValidatorSettings := defaultValidatorSettings
customValidatorSettings.CheckUserDefinedFields = true
validator := NewValidator(customValidatorSettings, dict, nil)

builder := createFIX40NewOrderSingle()
tag := Tag(9999)
builder.Body.SetField(tag, FIXString("hello"))
msgBytes := builder.build()

return validateTest{
TestName: "Check User Defined Fields - Enabled",
Validator: validator,
MessageBytes: msgBytes,
DoNotExpectReject: false,
ExpectedRefTagID: &tag,
}
}

func tcCheckUserDefinedFieldsEnabledFixT() validateTest {
tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
customValidatorSettings := defaultValidatorSettings
customValidatorSettings.RejectInvalidMessage = true
validator := NewValidator(customValidatorSettings, appDict, tDict)

builder := createFIX50SP2NewOrderSingle()
tag := Tag(9999)
builder.Body.SetField(tag, FIXString("hello"))
msgBytes := builder.build()

return validateTest{
TestName: "Check User Defined Fields - Enabled FIXT",
Validator: validator,
MessageBytes: msgBytes,
DoNotExpectReject: false,
ExpectedRefTagID: &tag,
}
}

func tcCheckUserDefinedFieldsDisabled() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
customValidatorSettings := defaultValidatorSettings
customValidatorSettings.CheckUserDefinedFields = false
validator := NewValidator(customValidatorSettings, dict, nil)

builder := createFIX40NewOrderSingle()
tag := Tag(9999)
builder.Body.SetField(tag, FIXString("hello"))
msgBytes := builder.build()

return validateTest{
TestName: "Check User Defined Fields - Disabled",
Validator: validator,
MessageBytes: msgBytes,
DoNotExpectReject: true,
}
}

func tcCheckUserDefinedFieldsDisabledFixT() validateTest {
tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
customValidatorSettings := defaultValidatorSettings
customValidatorSettings.CheckUserDefinedFields = false
validator := NewValidator(customValidatorSettings, appDict, tDict)

builder := createFIX50SP2NewOrderSingle()
tag := Tag(9999)
builder.Body.SetField(tag, FIXString("hello"))
msgBytes := builder.build()

return validateTest{
TestName: "Check User Defined Fields - Disabled FIXT",
Validator: validator,
MessageBytes: msgBytes,
DoNotExpectReject: true,
}
}

func tcTagSpecifiedOutOfRequiredOrderDisabledHeader() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
customValidatorSettings := defaultValidatorSettings
Expand Down

0 comments on commit c3c49cb

Please sign in to comment.