adding quickfix library
This commit is contained in:
467
quickfix/validation.go
Normal file
467
quickfix/validation.go
Normal file
@ -0,0 +1,467 @@
|
||||
// Copyright (c) quickfixengine.org All rights reserved.
|
||||
//
|
||||
// This file may be distributed under the terms of the quickfixengine.org
|
||||
// license as defined by quickfixengine.org and appearing in the file
|
||||
// LICENSE included in the packaging of this file.
|
||||
//
|
||||
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
|
||||
// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
|
||||
// PARTICULAR PURPOSE.
|
||||
//
|
||||
// See http://www.quickfixengine.org/LICENSE for licensing information.
|
||||
//
|
||||
// Contact ask@quickfixengine.org if any conditions of this licensing
|
||||
// are not clear to you.
|
||||
|
||||
package quickfix
|
||||
|
||||
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
|
||||
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,
|
||||
}
|
||||
|
||||
type fixValidator struct {
|
||||
dataDictionary *datadictionary.DataDictionary
|
||||
settings ValidatorSettings
|
||||
}
|
||||
|
||||
type fixtValidator struct {
|
||||
transportDataDictionary *datadictionary.DataDictionary
|
||||
appDataDictionary *datadictionary.DataDictionary
|
||||
settings ValidatorSettings
|
||||
}
|
||||
|
||||
// NewValidator creates a FIX message validator from the given data dictionaries.
|
||||
func NewValidator(settings ValidatorSettings, appDataDictionary, transportDataDictionary *datadictionary.DataDictionary) Validator {
|
||||
if transportDataDictionary != nil {
|
||||
return &fixtValidator{
|
||||
transportDataDictionary: transportDataDictionary,
|
||||
appDataDictionary: appDataDictionary,
|
||||
settings: settings,
|
||||
}
|
||||
}
|
||||
return &fixValidator{
|
||||
dataDictionary: appDataDictionary,
|
||||
settings: settings,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate tests the message against the provided data dictionary.
|
||||
func (v *fixValidator) Validate(msg *Message) MessageRejectError {
|
||||
if !msg.Header.Has(tagMsgType) {
|
||||
return RequiredTagMissing(tagMsgType)
|
||||
}
|
||||
msgType, err := msg.Header.GetString(tagMsgType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return validateFIX(v.dataDictionary, v.settings, msgType, msg)
|
||||
}
|
||||
|
||||
// Validate tests the message against the provided transport and app data dictionaries.
|
||||
// If the message is an admin message, it will be validated against the transport data dictionary.
|
||||
func (v *fixtValidator) Validate(msg *Message) MessageRejectError {
|
||||
if !msg.Header.Has(tagMsgType) {
|
||||
return RequiredTagMissing(tagMsgType)
|
||||
}
|
||||
msgType, err := msg.Header.GetString(tagMsgType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isAdminMessageType([]byte(msgType)) {
|
||||
return validateFIX(v.transportDataDictionary, v.settings, msgType, msg)
|
||||
}
|
||||
return validateFIXT(v.transportDataDictionary, v.appDataDictionary, v.settings, msgType, msg)
|
||||
}
|
||||
|
||||
func validateFIX(d *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError {
|
||||
if d != nil {
|
||||
if err := validateMsgType(d, msgType, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateRequired(d, d, msgType, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if err := validateWalk(d, d, settings, msgType, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateFIXT(transportDD, appDD *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError {
|
||||
if appDD != nil && transportDD != nil {
|
||||
if err := validateMsgType(appDD, msgType, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateRequired(transportDD, appDD, msgType, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if err := validateWalk(transportDD, appDD, settings, msgType, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateMsgType(d *datadictionary.DataDictionary, msgType string, _ *Message) MessageRejectError {
|
||||
if _, validMsgType := d.Messages[msgType]; !validMsgType {
|
||||
return InvalidMessageType()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateWalk(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError {
|
||||
remainingFields := msg.fields
|
||||
iteratedTags := make(datadictionary.TagSet)
|
||||
|
||||
var messageDef *datadictionary.MessageDef
|
||||
var fieldDef *datadictionary.FieldDef
|
||||
var err MessageRejectError
|
||||
var ok bool
|
||||
|
||||
for len(remainingFields) > 0 {
|
||||
field := remainingFields[0]
|
||||
tag := field.tag
|
||||
|
||||
switch {
|
||||
case tag.IsHeader():
|
||||
messageDef = transportDD.Header
|
||||
case tag.IsTrailer():
|
||||
messageDef = transportDD.Trailer
|
||||
default: // is body
|
||||
messageDef = appDD.Messages[msgType]
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if len(remainingFields) != 0 {
|
||||
return TagNotDefinedForThisMessageType(remainingFields[0].tag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateVisitField(fieldDef *datadictionary.FieldDef, fields []TagValue) ([]TagValue, MessageRejectError) {
|
||||
if fieldDef.IsGroup() {
|
||||
var err MessageRejectError
|
||||
if fields, err = validateVisitGroupField(fieldDef, fields); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
return fields[1:], nil
|
||||
}
|
||||
|
||||
func validateVisitGroupField(fieldDef *datadictionary.FieldDef, fieldStack []TagValue) ([]TagValue, MessageRejectError) {
|
||||
numInGroupTag := fieldStack[0].tag
|
||||
var numInGroup FIXInt
|
||||
|
||||
if err := numInGroup.Read(fieldStack[0].value); err != nil {
|
||||
return nil, IncorrectDataFormatForValue(numInGroupTag)
|
||||
}
|
||||
|
||||
fieldStack = fieldStack[1:]
|
||||
|
||||
var childDefs []*datadictionary.FieldDef
|
||||
groupCount := 0
|
||||
|
||||
for len(fieldStack) > 0 {
|
||||
|
||||
// Start of repeating group.
|
||||
if int(fieldStack[0].tag) == fieldDef.Fields[0].Tag() {
|
||||
childDefs = fieldDef.Fields
|
||||
groupCount++
|
||||
}
|
||||
|
||||
// Group complete.
|
||||
if len(childDefs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if int(fieldStack[0].tag) == childDefs[0].Tag() {
|
||||
var err MessageRejectError
|
||||
if fieldStack, err = validateVisitField(childDefs[0], fieldStack); err != nil {
|
||||
return fieldStack, err
|
||||
}
|
||||
} else {
|
||||
if childDefs[0].Required() {
|
||||
return fieldStack, RequiredTagMissing(Tag(childDefs[0].Tag()))
|
||||
}
|
||||
}
|
||||
|
||||
childDefs = childDefs[1:]
|
||||
}
|
||||
|
||||
if groupCount != int(numInGroup) {
|
||||
return fieldStack, incorrectNumInGroupCountForRepeatingGroup(numInGroupTag)
|
||||
}
|
||||
|
||||
return fieldStack, nil
|
||||
}
|
||||
|
||||
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() && checkFieldsOutOfOrder:
|
||||
return tagSpecifiedOutOfRequiredOrder(t)
|
||||
case t.IsTrailer():
|
||||
inTrailer = true
|
||||
case inTrailer && !t.IsTrailer() && checkFieldsOutOfOrder:
|
||||
return tagSpecifiedOutOfRequiredOrder(t)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRequired(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, msgType string, message *Message) MessageRejectError {
|
||||
if err := validateRequiredFieldMap(message, transportDD.Header.RequiredTags, message.Header.FieldMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateRequiredFieldMap(message, appDD.Messages[msgType].RequiredTags, message.Body.FieldMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateRequiredFieldMap(message, transportDD.Trailer.RequiredTags, message.Trailer.FieldMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRequiredFieldMap(_ *Message, requiredTags map[int]struct{}, fieldMap FieldMap) MessageRejectError {
|
||||
for required := range requiredTags {
|
||||
requiredTag := Tag(required)
|
||||
if !fieldMap.Has(requiredTag) {
|
||||
return RequiredTagMissing(requiredTag)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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, settings, transportDD.Header.Tags, field); err != nil {
|
||||
return err
|
||||
}
|
||||
case field.tag.IsTrailer():
|
||||
if err := validateField(transportDD, settings, transportDD.Trailer.Tags, field); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if err := validateField(appDD, settings, appDD.Messages[msgType].Tags, field); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
var prototype FieldValue
|
||||
switch fieldType.Type {
|
||||
case "MULTIPLESTRINGVALUE", "MULTIPLEVALUESTRING":
|
||||
fallthrough
|
||||
case "MULTIPLECHARVALUE":
|
||||
fallthrough
|
||||
case "CHAR":
|
||||
fallthrough
|
||||
case "CURRENCY":
|
||||
fallthrough
|
||||
case "DATA":
|
||||
fallthrough
|
||||
case "MONTHYEAR":
|
||||
fallthrough
|
||||
case "LOCALMKTDATE", "DATE":
|
||||
fallthrough
|
||||
case "EXCHANGE":
|
||||
fallthrough
|
||||
case "LANGUAGE":
|
||||
fallthrough
|
||||
case "XMLDATA":
|
||||
fallthrough
|
||||
case "COUNTRY":
|
||||
fallthrough
|
||||
case "UTCTIMEONLY":
|
||||
fallthrough
|
||||
case "UTCDATEONLY", "UTCDATE":
|
||||
fallthrough
|
||||
case "TZTIMEONLY":
|
||||
fallthrough
|
||||
case "TZTIMESTAMP":
|
||||
fallthrough
|
||||
case "STRING":
|
||||
prototype = new(FIXString)
|
||||
|
||||
case "BOOLEAN":
|
||||
prototype = new(FIXBoolean)
|
||||
|
||||
case "LENGTH":
|
||||
fallthrough
|
||||
case "DAYOFMONTH":
|
||||
fallthrough
|
||||
case "NUMINGROUP":
|
||||
fallthrough
|
||||
case "SEQNUM":
|
||||
fallthrough
|
||||
case "INT":
|
||||
prototype = new(FIXInt)
|
||||
|
||||
case "UTCTIMESTAMP", "TIME":
|
||||
prototype = new(FIXUTCTimestamp)
|
||||
|
||||
case "QTY", "QUANTITY":
|
||||
fallthrough
|
||||
case "AMT":
|
||||
fallthrough
|
||||
case "PRICE":
|
||||
fallthrough
|
||||
case "PRICEOFFSET":
|
||||
fallthrough
|
||||
case "PERCENTAGE":
|
||||
fallthrough
|
||||
case "FLOAT":
|
||||
prototype = new(FIXFloat)
|
||||
|
||||
}
|
||||
|
||||
if err := prototype.Read(field.value); err != nil {
|
||||
return IncorrectDataFormatForValue(field.tag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user