adding quickfix library

This commit is contained in:
Ramiro Paz
2026-03-12 12:08:34 -03:00
parent 9e55c5c562
commit c09a1fd21a
1311 changed files with 1887342 additions and 2 deletions

View File

@ -0,0 +1,247 @@
package datadictionary
import (
"errors"
"fmt"
"strconv"
)
type builder struct {
doc *XMLDoc
dict *DataDictionary
componentByName map[string]*XMLComponent
}
func (b *builder) build(doc *XMLDoc) (*DataDictionary, error) {
if doc.Type != "FIX" && doc.Type != "FIXT" {
return nil, errors.New("type attribute must be FIX or FIXT")
}
b.doc = doc
b.dict = &DataDictionary{FIXType: doc.Type, ServicePack: doc.ServicePack}
var err error
if b.dict.Major, err = strconv.Atoi(doc.Major); err != nil {
return nil, errors.New("major attribute not valid on <fix>")
}
if b.dict.Minor, err = strconv.Atoi(doc.Minor); err != nil {
return nil, errors.New("minor attribute not valid on <fix>")
}
b.componentByName = make(map[string]*XMLComponent)
for _, c := range doc.Components {
b.componentByName[c.Name] = c
}
b.buildFieldTypes()
if err := b.buildComponents(); err != nil {
return nil, err
}
if err := b.buildMessageDefs(); err != nil {
return nil, err
}
if b.doc.Header != nil {
if b.dict.Header, err = b.buildMessageDef(b.doc.Header); err != nil {
return nil, err
}
}
if b.doc.Trailer != nil {
if b.dict.Trailer, err = b.buildMessageDef(b.doc.Trailer); err != nil {
return nil, err
}
}
return b.dict, nil
}
func (b builder) findOrBuildComponentType(xmlMember *XMLComponentMember) (*ComponentType, error) {
if comp, preBuilt := b.dict.ComponentTypes[xmlMember.Name]; preBuilt {
return comp, nil
}
var xmlComp *XMLComponent
var ok bool
if xmlComp, ok = b.componentByName[xmlMember.Name]; !ok {
return nil, newUnknownComponent(xmlMember.Name)
}
var comp *ComponentType
var err error
if comp, err = b.buildComponentType(xmlComp); err != nil {
return nil, err
}
b.dict.ComponentTypes[xmlMember.Name] = comp
return comp, nil
}
func (b builder) buildComponentType(xmlComponent *XMLComponent) (*ComponentType, error) {
var parts []MessagePart
for _, member := range xmlComponent.Members {
if member.isComponent() {
var childComponentType *ComponentType
var err error
if childComponentType, err = b.findOrBuildComponentType(member); err != nil {
return nil, err
}
childComponent := Component{
ComponentType: childComponentType,
required: member.isRequired(),
}
parts = append(parts, childComponent)
} else {
var field *FieldDef
var err error
if field, err = b.buildFieldDef(member); err != nil {
return nil, err
}
parts = append(parts, field)
}
}
return NewComponentType(xmlComponent.Name, parts), nil
}
func (b builder) buildComponents() error {
b.dict.ComponentTypes = make(map[string]*ComponentType)
for _, c := range b.doc.Components {
if _, allReadyBuilt := b.dict.ComponentTypes[c.Name]; !allReadyBuilt {
var builtComponent *ComponentType
var err error
if builtComponent, err = b.buildComponentType(c); err != nil {
return err
}
b.dict.ComponentTypes[c.Name] = builtComponent
}
}
return nil
}
func (b builder) buildMessageDefs() error {
b.dict.Messages = make(map[string]*MessageDef)
var err error
for _, m := range b.doc.Messages {
if b.dict.Messages[m.MsgType], err = b.buildMessageDef(m); err != nil {
break
}
}
return err
}
func (b builder) buildMessageDef(xmlMessage *XMLComponent) (*MessageDef, error) {
var parts []MessagePart
for _, member := range xmlMessage.Members {
if member.isComponent() {
var ok bool
var comp *ComponentType
if comp, ok = b.dict.ComponentTypes[member.Name]; !ok {
return nil, newUnknownComponent(member.Name)
}
parts = append(parts, Component{ComponentType: comp, required: member.isRequired()})
} else {
var field *FieldDef
var err error
if field, err = b.buildFieldDef(member); err != nil {
return nil, err
}
parts = append(parts, field)
}
}
return NewMessageDef(xmlMessage.Name, xmlMessage.MsgType, parts), nil
}
func (b builder) buildGroupFieldDef(xmlField *XMLComponentMember, groupFieldType *FieldType) (*FieldDef, error) {
var parts []MessagePart
for _, member := range xmlField.Members {
if member.isComponent() {
var err error
var compType *ComponentType
if compType, err = b.findOrBuildComponentType(member); err != nil {
return nil, err
}
comp := Component{
ComponentType: compType,
required: member.isRequired(),
}
parts = append(parts, comp)
} else {
var f *FieldDef
var err error
if f, err = b.buildFieldDef(member); err != nil {
return nil, err
}
parts = append(parts, f)
}
}
return NewGroupFieldDef(groupFieldType, xmlField.isRequired(), parts), nil
}
func (b builder) buildFieldDef(xmlField *XMLComponentMember) (*FieldDef, error) {
var fieldType *FieldType
var ok bool
if fieldType, ok = b.dict.FieldTypeByName[xmlField.Name]; !ok {
return nil, newUnknownField(xmlField.Name)
}
if xmlField.isGroup() {
f, err := b.buildGroupFieldDef(xmlField, fieldType)
return f, err
}
return NewFieldDef(fieldType, xmlField.isRequired()), nil
}
func (b builder) buildFieldTypes() {
b.dict.FieldTypeByTag = make(map[int]*FieldType)
b.dict.FieldTypeByName = make(map[string]*FieldType)
for _, f := range b.doc.Fields {
field := buildFieldType(f)
b.dict.FieldTypeByTag[field.Tag()] = field
b.dict.FieldTypeByName[field.Name()] = field
}
}
func buildFieldType(xmlField *XMLField) *FieldType {
field := NewFieldType(xmlField.Name, xmlField.Number, xmlField.Type)
if len(xmlField.Values) > 0 {
field.Enums = make(map[string]Enum)
for _, enum := range xmlField.Values {
field.Enums[enum.Enum] = Enum{Value: enum.Enum, Description: enum.Description}
}
}
return field
}
func newUnknownComponent(name string) error {
return fmt.Errorf("unknown component %v", name)
}
func newUnknownField(name string) error {
return fmt.Errorf("unknown field %v", name)
}

View File

@ -0,0 +1,139 @@
package datadictionary
import (
"encoding/xml"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type BuildSuite struct {
suite.Suite
doc *XMLDoc
*builder
}
func TestBuildSuite(t *testing.T) {
suite.Run(t, new(BuildSuite))
}
func (s *BuildSuite) SetupTest() {
s.doc = &XMLDoc{Type: "FIX", Major: "4", Minor: "5"}
s.builder = new(builder)
}
func (s *BuildSuite) TestValidTypes() {
var tests = []string{
"FIX",
"FIXT",
}
for _, test := range tests {
s.doc.Type = test
dict, err := s.builder.build(s.doc)
s.Nil(err)
s.NotNil(dict)
s.Equal(test, dict.FIXType)
}
}
func (s *BuildSuite) TestInvalidTypes() {
var tests = []string{
"",
"invalid",
}
for _, test := range tests {
s.doc.Type = test
_, err := s.builder.build(s.doc)
s.NotNil(err)
}
}
func (s *BuildSuite) TestValidMajor() {
var tests = []int{
4,
5,
}
for _, test := range tests {
s.doc.Major = strconv.Itoa(test)
dict, err := s.builder.build(s.doc)
s.Nil(err)
s.NotNil(dict)
s.Equal(test, dict.Major)
}
}
func (s *BuildSuite) TestInvalidMajor() {
var tests = []string{
"",
"notanumber",
}
for _, test := range tests {
s.doc.Major = test
_, err := s.builder.build(s.doc)
s.NotNil(err)
}
}
func (s *BuildSuite) TestValidMinor() {
var tests = []int{
4,
5,
}
for _, test := range tests {
s.doc.Minor = strconv.Itoa(test)
dict, err := s.builder.build(s.doc)
s.Nil(err)
s.NotNil(dict)
s.Equal(test, dict.Minor)
}
}
func (s *BuildSuite) TestInvalidMinor() {
var tests = []string{
"",
"notanumber",
}
for _, test := range tests {
s.doc.Minor = test
_, err := s.builder.build(s.doc)
s.NotNil(err)
}
}
func TestBuildFieldDef(t *testing.T) {
var tests = []struct {
element string
}{
{"field"},
{"group"},
}
for _, test := range tests {
xmlField := &XMLComponentMember{XMLName: xml.Name{Local: test.element}, Name: "myfield", Members: []*XMLComponentMember{}}
fieldTypeByName := make(map[string]*FieldType)
fieldTypeByName["myfield"] = NewFieldType("some field", 11, "INT")
dict := &DataDictionary{FieldTypeByName: fieldTypeByName}
b := &builder{doc: nil, dict: dict}
f, err := b.buildFieldDef(xmlField)
assert.Nil(t, err)
assert.Equal(t, 11, f.Tag())
assert.Empty(t, f.childTags())
}
}

View File

@ -0,0 +1,80 @@
package datadictionary_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/quickfixgo/quickfix/datadictionary"
)
func TestNewComponentType(t *testing.T) {
ft1 := datadictionary.NewFieldType("aname1", 11, "INT")
ft2 := datadictionary.NewFieldType("aname2", 12, "INT")
optionalField1 := datadictionary.NewFieldDef(ft1, false)
requiredField1 := datadictionary.NewFieldDef(ft1, true)
optionalField2 := datadictionary.NewFieldDef(ft2, false)
requiredField2 := datadictionary.NewFieldDef(ft2, true)
requiredComp1 := datadictionary.NewComponent(
datadictionary.NewComponentType("comp1", []datadictionary.MessagePart{requiredField1}),
true)
optionalComp1 := datadictionary.NewComponent(
datadictionary.NewComponentType("comp1", []datadictionary.MessagePart{requiredField1}),
false)
var tests = []struct {
testName string
parts []datadictionary.MessagePart
expectedFields []*datadictionary.FieldDef
expectedRequiredParts []datadictionary.MessagePart
expectedRequiredFields []*datadictionary.FieldDef
}{
{
testName: "test1",
parts: []datadictionary.MessagePart{optionalField1},
expectedFields: []*datadictionary.FieldDef{optionalField1},
},
{
testName: "test2",
parts: []datadictionary.MessagePart{requiredField1},
expectedFields: []*datadictionary.FieldDef{requiredField1},
expectedRequiredFields: []*datadictionary.FieldDef{requiredField1},
expectedRequiredParts: []datadictionary.MessagePart{requiredField1},
},
{
testName: "test3",
parts: []datadictionary.MessagePart{requiredField1, optionalField2},
expectedFields: []*datadictionary.FieldDef{requiredField1, optionalField2},
expectedRequiredFields: []*datadictionary.FieldDef{requiredField1},
expectedRequiredParts: []datadictionary.MessagePart{requiredField1},
},
{
testName: "test4",
parts: []datadictionary.MessagePart{requiredField2, optionalComp1},
expectedFields: []*datadictionary.FieldDef{requiredField2, requiredField1},
expectedRequiredFields: []*datadictionary.FieldDef{requiredField2},
expectedRequiredParts: []datadictionary.MessagePart{requiredField2},
},
{
testName: "test5",
parts: []datadictionary.MessagePart{requiredField2, requiredComp1},
expectedFields: []*datadictionary.FieldDef{requiredField2, requiredField1},
expectedRequiredFields: []*datadictionary.FieldDef{requiredField2, requiredField1},
expectedRequiredParts: []datadictionary.MessagePart{requiredField2, requiredComp1},
},
}
for _, test := range tests {
ct := datadictionary.NewComponentType("cname", test.parts)
assert.NotNil(t, ct, test.testName)
assert.Equal(t, "cname", ct.Name(), test.testName)
assert.Equal(t, test.expectedFields, ct.Fields(), test.testName)
assert.Equal(t, test.parts, ct.Parts(), test.testName)
assert.Equal(t, test.expectedRequiredFields, ct.RequiredFields(), test.testName)
assert.Equal(t, test.expectedRequiredParts, ct.RequiredParts(), test.testName)
}
}

View File

@ -0,0 +1,333 @@
// Package datadictionary provides support for parsing and organizing FIX Data Dictionaries
package datadictionary
import (
"encoding/xml"
"io"
"os"
"github.com/pkg/errors"
)
// DataDictionary models FIX messages, components, and fields.
type DataDictionary struct {
FIXType string
Major int
Minor int
ServicePack int
FieldTypeByTag map[int]*FieldType
FieldTypeByName map[string]*FieldType
Messages map[string]*MessageDef
ComponentTypes map[string]*ComponentType
Header *MessageDef
Trailer *MessageDef
}
// MessagePart can represent a Field, Repeating Group, or Component.
type MessagePart interface {
Name() string
Required() bool
}
// messagePartWithFields is a MessagePart with multiple Fields.
type messagePartWithFields interface {
MessagePart
Fields() []*FieldDef
RequiredFields() []*FieldDef
}
// ComponentType is a grouping of fields.
type ComponentType struct {
name string
parts []MessagePart
fields []*FieldDef
requiredFields []*FieldDef
requiredParts []MessagePart
}
// NewComponentType returns an initialized component type.
func NewComponentType(name string, parts []MessagePart) *ComponentType {
comp := ComponentType{
name: name,
parts: parts,
}
for _, part := range parts {
if part.Required() {
comp.requiredParts = append(comp.requiredParts, part)
}
switch f := part.(type) {
case messagePartWithFields:
comp.fields = append(comp.fields, f.Fields()...)
if f.Required() {
comp.requiredFields = append(comp.requiredFields, f.RequiredFields()...)
}
case *FieldDef:
comp.fields = append(comp.fields, f)
if f.Required() {
comp.requiredFields = append(comp.requiredFields, f)
}
}
}
return &comp
}
// Name returns the name of this component type.
func (c ComponentType) Name() string { return c.name }
// Fields returns all fields contained in this component. Includes fields
// encapsulated in components of this component.
func (c ComponentType) Fields() []*FieldDef { return c.fields }
// RequiredFields returns those fields that are required for this component.
func (c ComponentType) RequiredFields() []*FieldDef { return c.requiredFields }
// RequiredParts returns those parts that are required for this component.
func (c ComponentType) RequiredParts() []MessagePart { return c.requiredParts }
// Parts returns all parts in declaration order contained in this component.
func (c ComponentType) Parts() []MessagePart { return c.parts }
// TagSet is set for tags.
type TagSet map[int]struct{}
// Add adds a tag to the tagset.
func (t TagSet) Add(tag int) {
t[tag] = struct{}{}
}
// Component is a Component as it appears in a given MessageDef.
type Component struct {
*ComponentType
required bool
}
// NewComponent returns an initialized Component instance.
func NewComponent(ct *ComponentType, required bool) *Component {
return &Component{
ComponentType: ct,
required: required,
}
}
// Required returns true if this component is required for the containing
// MessageDef.
func (c Component) Required() bool { return c.required }
// Field models a field or repeating group in a message.
type Field interface {
Tag() int
}
// FieldDef models a field belonging to a message.
type FieldDef struct {
*FieldType
required bool
Parts []MessagePart
Fields []*FieldDef
requiredParts []MessagePart
requiredFields []*FieldDef
}
// NewFieldDef returns an initialized FieldDef.
func NewFieldDef(fieldType *FieldType, required bool) *FieldDef {
return &FieldDef{
FieldType: fieldType,
required: required,
}
}
// NewGroupFieldDef returns an initialized FieldDef for a repeating group.
func NewGroupFieldDef(fieldType *FieldType, required bool, parts []MessagePart) *FieldDef {
field := FieldDef{
FieldType: fieldType,
required: required,
Parts: parts,
}
for _, part := range parts {
if part.Required() {
field.requiredParts = append(field.requiredParts, part)
}
if comp, ok := part.(Component); ok {
field.Fields = append(field.Fields, comp.Fields()...)
if comp.required {
field.requiredFields = append(field.requiredFields, comp.requiredFields...)
}
} else {
if child, ok := part.(*FieldDef); ok {
field.Fields = append(field.Fields, child)
if child.required {
field.requiredFields = append(field.requiredFields, child)
}
} else {
panic("unknown part")
}
}
}
return &field
}
// Required returns true if this FieldDef is required for the containing
// MessageDef.
func (f FieldDef) Required() bool { return f.required }
// IsGroup is true if the field is a repeating group.
func (f FieldDef) IsGroup() bool {
return len(f.Fields) > 0
}
// RequiredParts returns those parts that are required for this FieldDef. IsGroup
// must return true.
func (f FieldDef) RequiredParts() []MessagePart { return f.requiredParts }
// RequiredFields returns those fields that are required for this FieldDef. IsGroup
// must return true.
func (f FieldDef) RequiredFields() []*FieldDef { return f.requiredFields }
func (f FieldDef) childTags() []int {
tags := make([]int, 0, len(f.Fields))
for _, f := range f.Fields {
tags = append(tags, f.Tag())
tags = append(tags, f.childTags()...)
}
return tags
}
// FieldType holds information relating to a field. Includes Tag, type, and enums, if defined.
type FieldType struct {
name string
tag int
Type string
Enums map[string]Enum
}
// NewFieldType returns a pointer to an initialized FieldType.
func NewFieldType(name string, tag int, fixType string) *FieldType {
return &FieldType{
name: name,
tag: tag,
Type: fixType,
}
}
// Name returns the name for this FieldType.
func (f FieldType) Name() string { return f.name }
// Tag returns the tag for this fieldType.
func (f FieldType) Tag() int { return f.tag }
// Enum is a container for value and description.
type Enum struct {
Value string
Description string
}
// MessageDef can apply to header, trailer, or body of a FIX Message.
type MessageDef struct {
Name string
MsgType string
Fields map[int]*FieldDef
// Parts are the MessageParts of contained in this MessageDef in declaration order.
Parts []MessagePart
requiredParts []MessagePart
RequiredTags TagSet
Tags TagSet
}
// RequiredParts returns those parts that are required for this Message.
func (m MessageDef) RequiredParts() []MessagePart { return m.requiredParts }
// NewMessageDef returns a pointer to an initialized MessageDef.
func NewMessageDef(name, msgType string, parts []MessagePart) *MessageDef {
msg := MessageDef{
Name: name,
MsgType: msgType,
Fields: make(map[int]*FieldDef),
RequiredTags: make(TagSet),
Tags: make(TagSet),
Parts: parts,
}
processField := func(field *FieldDef, allowRequired bool) {
msg.Fields[field.Tag()] = field
msg.Tags.Add(field.Tag())
for _, t := range field.childTags() {
msg.Tags.Add(t)
}
if allowRequired && field.Required() {
msg.RequiredTags.Add(field.Tag())
}
}
for _, part := range parts {
if part.Required() {
msg.requiredParts = append(msg.requiredParts, part)
}
switch pType := part.(type) {
case messagePartWithFields:
for _, f := range pType.Fields() {
// Field if required in component is required in message only if
// component is required.
processField(f, pType.Required())
}
case *FieldDef:
processField(pType, true)
default:
panic("Unknown Part")
}
}
return &msg
}
// Parse loads and build a datadictionary instance from an xml file.
func Parse(path string) (*DataDictionary, error) {
var xmlFile *os.File
var err error
xmlFile, err = os.Open(path)
if err != nil {
return nil, errors.Wrapf(err, "problem opening file: %v", path)
}
defer xmlFile.Close()
return ParseSrc(xmlFile)
}
// ParseSrc loads and build a datadictionary instance from an xml source.
func ParseSrc(xmlSrc io.Reader) (*DataDictionary, error) {
doc := new(XMLDoc)
decoder := xml.NewDecoder(xmlSrc)
decoder.CharsetReader = func(_ string, input io.Reader) (io.Reader, error) {
return input, nil
}
if err := decoder.Decode(doc); err != nil {
return nil, errors.Wrapf(err, "problem parsing XML file")
}
b := new(builder)
dict, err := b.build(doc)
if err != nil {
return nil, err
}
return dict, nil
}

View File

@ -0,0 +1,191 @@
package datadictionary
import (
"testing"
)
func TestParseBadPath(t *testing.T) {
if _, err := Parse("../spec/bogus.xml"); err == nil {
t.Error("Expected err")
}
}
func TestParseRecursiveComponents(t *testing.T) {
dict, err := Parse("../spec/FIX44.xml")
if dict == nil {
t.Error("Dictionary is nil")
}
if err != nil {
t.Errorf("Unexpected err: %v", err)
}
}
var cachedDataDictionary *DataDictionary
func dict() (*DataDictionary, error) {
if cachedDataDictionary != nil {
return cachedDataDictionary, nil
}
var err error
cachedDataDictionary, err = Parse("../spec/FIX43.xml")
return cachedDataDictionary, err
}
func TestComponents(t *testing.T) {
d, _ := dict()
if _, ok := d.ComponentTypes["SpreadOrBenchmarkCurveData"]; ok != true {
t.Error("Component not found")
}
}
func TestFieldsByTag(t *testing.T) {
d, _ := dict()
var tests = []struct {
Tag int
Name string
Type string
EnumsAreNil bool
}{
{655, "ContraLegRefID", "STRING", true},
{658, "QuoteRequestRejectReason", "INT", false},
}
for _, test := range tests {
f, ok := d.FieldTypeByTag[test.Tag]
if !ok {
t.Errorf("%v not found", test.Tag)
}
if f.Name() != test.Name {
t.Errorf("Expected %v got %v", test.Name, f.Name())
}
if f.Type != test.Type {
t.Errorf("Expected %v got %v", test.Type, f.Type)
}
switch {
case f.Enums != nil && test.EnumsAreNil:
t.Errorf("Expected no enums")
case f.Enums == nil && !test.EnumsAreNil:
t.Errorf("Expected enums")
}
}
}
func TestEnumFieldsByTag(t *testing.T) {
d, _ := dict()
f := d.FieldTypeByTag[658]
var tests = []struct {
Value string
Description string
}{
{"1", "UNKNOWN_SYMBOL"},
{"2", "EXCHANGE"},
{"3", "QUOTE_REQUEST_EXCEEDS_LIMIT"},
{"4", "TOO_LATE_TO_ENTER"},
{"5", "INVALID_PRICE"},
{"6", "NOT_AUTHORIZED_TO_REQUEST_QUOTE"},
}
if len(f.Enums) != len(tests) {
t.Errorf("Expected %v enums got %v", len(tests), len(f.Enums))
}
for _, test := range tests {
if enum, ok := f.Enums[test.Value]; !ok {
t.Errorf("Expected Enum %v", test.Value)
} else {
if enum.Value != test.Value {
t.Errorf("Expected value %v got %v", test.Value, enum.Value)
}
if enum.Description != test.Description {
t.Errorf("Expected description %v got %v", test.Description, enum.Description)
}
}
}
}
func TestDataDictionaryMessages(t *testing.T) {
d, _ := dict()
_, ok := d.Messages["D"]
if !ok {
t.Error("Did not find message")
}
}
func TestDataDictionaryHeader(t *testing.T) {
d, _ := dict()
if d.Header == nil {
t.Error("Did not find header")
}
}
func TestDataDictionaryTrailer(t *testing.T) {
d, _ := dict()
if d.Trailer == nil {
t.Error("Did not find trailer")
}
}
func TestMessageRequiredTags(t *testing.T) {
d, _ := dict()
nos := d.Messages["D"]
var tests = []struct {
*MessageDef
Tag int
Required bool
}{
{nos, 11, true},
{nos, 526, false},
{d.Header, 8, true},
{d.Header, 115, false},
{d.Trailer, 10, true},
{d.Trailer, 89, false},
}
for _, test := range tests {
switch _, required := test.MessageDef.RequiredTags[test.Tag]; {
case required && !test.Required:
t.Errorf("%v should not be required", test.Tag)
case !required && test.Required:
t.Errorf("%v should not required", test.Tag)
}
}
}
func TestMessageTags(t *testing.T) {
d, _ := dict()
nos := d.Messages["D"]
var tests = []struct {
*MessageDef
Tag int
}{
{nos, 11},
{nos, 526},
{d.Header, 8},
{d.Header, 115},
{d.Trailer, 10},
{d.Trailer, 89},
}
for _, test := range tests {
if _, known := test.MessageDef.Tags[test.Tag]; !known {
t.Errorf("%v is not known", test.Tag)
}
}
}

View File

@ -0,0 +1,26 @@
package datadictionary_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/quickfixgo/quickfix/datadictionary"
)
func TestNewFieldDef(t *testing.T) {
ft := datadictionary.NewFieldType("aname", 11, "INT")
var tests = []struct {
required bool
}{
{false},
}
for _, test := range tests {
fd := datadictionary.NewFieldDef(ft, test.required)
assert.False(t, fd.IsGroup(), "field def is not a group")
assert.Equal(t, "aname", fd.Name())
assert.Equal(t, test.required, fd.Required())
}
}

View File

@ -0,0 +1,17 @@
package datadictionary_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/quickfixgo/quickfix/datadictionary"
)
func TestNewFieldType(t *testing.T) {
ft := datadictionary.NewFieldType("myname", 10, "STRING")
assert.NotNil(t, ft)
assert.Equal(t, "myname", ft.Name())
assert.Equal(t, 10, ft.Tag())
assert.Equal(t, "STRING", ft.Type)
}

View File

@ -0,0 +1,17 @@
package datadictionary_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/quickfixgo/quickfix/datadictionary"
)
func TestNewGroupField(t *testing.T) {
ft := datadictionary.NewFieldType("aname", 11, "INT")
fg := datadictionary.NewGroupFieldDef(ft, true, []datadictionary.MessagePart{})
assert.NotNil(t, fg)
assert.Equal(t, "aname", fg.Name())
assert.Equal(t, true, fg.Required())
}

View File

@ -0,0 +1,85 @@
package datadictionary_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/quickfixgo/quickfix/datadictionary"
)
func TestNewMessageDef(t *testing.T) {
ft1 := datadictionary.NewFieldType("type1", 11, "STRING")
ft2 := datadictionary.NewFieldType("type2", 12, "STRING")
ft3 := datadictionary.NewFieldType("type3", 13, "INT")
optionalfd1 := datadictionary.NewFieldDef(ft1, false)
requiredfd1 := datadictionary.NewFieldDef(ft1, true)
optionalfd2 := datadictionary.NewFieldDef(ft2, false)
// requiredfd2 := datadictionary.NewFieldDef(ft2, true)
optionalGroup1 := datadictionary.NewGroupFieldDef(ft3, false, []datadictionary.MessagePart{requiredfd1, optionalfd2})
requiredGroup1 := datadictionary.NewGroupFieldDef(ft3, true, []datadictionary.MessagePart{requiredfd1, optionalfd2})
ct1 := datadictionary.NewComponentType("ct1", []datadictionary.MessagePart{requiredGroup1})
optionalComp1 := datadictionary.NewComponent(ct1, false)
var tests = []struct {
parts []datadictionary.MessagePart
expectedTags datadictionary.TagSet
expectedRequiredTags datadictionary.TagSet
expectedRequiredParts []datadictionary.MessagePart
}{
{
parts: []datadictionary.MessagePart{},
expectedTags: datadictionary.TagSet{},
expectedRequiredTags: datadictionary.TagSet{},
expectedRequiredParts: []datadictionary.MessagePart(nil),
},
{
parts: []datadictionary.MessagePart{optionalfd1},
expectedTags: datadictionary.TagSet{11: struct{}{}},
expectedRequiredTags: datadictionary.TagSet{},
expectedRequiredParts: []datadictionary.MessagePart(nil),
},
{
parts: []datadictionary.MessagePart{requiredfd1, optionalfd2},
expectedTags: datadictionary.TagSet{11: struct{}{}, 12: struct{}{}},
expectedRequiredTags: datadictionary.TagSet{11: struct{}{}},
expectedRequiredParts: []datadictionary.MessagePart{requiredfd1},
},
{
parts: []datadictionary.MessagePart{optionalGroup1},
expectedTags: datadictionary.TagSet{11: struct{}{}, 12: struct{}{}, 13: struct{}{}},
expectedRequiredTags: datadictionary.TagSet{},
expectedRequiredParts: []datadictionary.MessagePart(nil),
},
{
parts: []datadictionary.MessagePart{requiredGroup1},
expectedTags: datadictionary.TagSet{11: struct{}{}, 12: struct{}{}, 13: struct{}{}},
expectedRequiredTags: datadictionary.TagSet{13: struct{}{}},
expectedRequiredParts: []datadictionary.MessagePart{requiredGroup1},
},
{
parts: []datadictionary.MessagePart{optionalComp1},
expectedTags: datadictionary.TagSet{11: struct{}{}, 12: struct{}{}, 13: struct{}{}},
expectedRequiredTags: datadictionary.TagSet{},
expectedRequiredParts: []datadictionary.MessagePart(nil),
},
}
for _, test := range tests {
md := datadictionary.NewMessageDef("some message", "X", test.parts)
assert.NotNil(t, md)
assert.Equal(t, "some message", md.Name)
assert.Equal(t, "X", md.MsgType)
assert.Equal(t, test.expectedTags, md.Tags)
assert.Equal(t, test.expectedRequiredTags, md.RequiredTags)
assert.Equal(t, test.parts, md.Parts)
assert.Equal(t, test.expectedRequiredParts, md.RequiredParts())
}
}

View File

@ -0,0 +1,63 @@
package datadictionary
import (
"encoding/xml"
)
// XMLDoc is the unmarshalled root of a FIX Dictionary.
type XMLDoc struct {
Type string `xml:"type,attr"`
Major string `xml:"major,attr"`
Minor string `xml:"minor,attr"`
ServicePack int `xml:"servicepack,attr"`
Header *XMLComponent `xml:"header"`
Trailer *XMLComponent `xml:"trailer"`
Messages []*XMLComponent `xml:"messages>message"`
Components []*XMLComponent `xml:"components>component"`
Fields []*XMLField `xml:"fields>field"`
}
// XMLComponent can represent header, trailer, messages/message, or components/component xml elements.
type XMLComponent struct {
Name string `xml:"name,attr"`
MsgCat string `xml:"msgcat,attr"`
MsgType string `xml:"msgtype,attr"`
Members []*XMLComponentMember `xml:",any"`
}
// XMLField represents the fields/field xml element.
type XMLField struct {
Number int `xml:"number,attr"`
Name string `xml:"name,attr"`
Type string `xml:"type,attr"`
Values []*XMLValue `xml:"value"`
}
// XMLValue represents the fields/field/value xml element.
type XMLValue struct {
Enum string `xml:"enum,attr"`
Description string `xml:"description,attr"`
}
// XMLComponentMember represents child elements of header, trailer, messages/message, and components/component elements.
type XMLComponentMember struct {
XMLName xml.Name
Name string `xml:"name,attr"`
Required string `xml:"required,attr"`
Members []*XMLComponentMember `xml:",any"`
}
func (member XMLComponentMember) isComponent() bool {
return member.XMLName.Local == "component"
}
func (member XMLComponentMember) isGroup() bool {
return member.XMLName.Local == "group"
}
func (member XMLComponentMember) isRequired() bool {
return member.Required == "Y"
}

View File

@ -0,0 +1,186 @@
package datadictionary
import (
"encoding/xml"
"reflect"
"testing"
)
var cachedXMLDoc *XMLDoc
func xmlDoc() (*XMLDoc, error) {
if cachedXMLDoc != nil {
return cachedXMLDoc, nil
}
data := `
<fix major='4' type='FIX' servicepack='0' minor='3'>
<header>
<field name='BeginString' required='Y' />
<group name='NoHops' required='N'>
<field name='HopCompID' required='N' />
<field name='HopSendingTime' required='N' />
<field name='HopRefID' required='N' />
</group>
</header>
<messages>
<message name='Heartbeat' msgcat='admin' msgtype='0'>
<field name='TestReqID' required='N' />
</message>
<message name='IOI' msgcat='app' msgtype='6'>
<field name='IOIid' required='Y' />
<field name='IOITransType' required='Y' />
<field name='IOIRefID' required='N' />
<component name='Instrument' required='Y' />
<group name='NoRoutingIDs' required='N'>
<field name='RoutingType' required='N' />
<field name='RoutingID' required='N' />
</group>
</message>
<message name='NewOrderSingle' msgcat='app' msgtype='D'>
<field name='ClOrdID' required='Y' />
<field name='SecondaryClOrdID' required='N' />
<field name='ClOrdLinkID' required='N' />
<component name='Parties' required='N' />
<field name='TradeOriginationDate' required='N' />
<field name='Account' required='N' />
<field name='AccountType' required='N' />
<field name='DayBookingInst' required='N' />
<field name='BookingUnit' required='N' />
<field name='PreallocMethod' required='N' />
<group name='NoAllocs' required='N'>
<field name='AllocAccount' required='N' />
<field name='IndividualAllocID' required='N' />
<component name='NestedParties' required='N' />
<field name='AllocQty' required='N' />
</group>
<field name='SettlmntTyp' required='N' />
<field name='FutSettDate' required='N' />
<field name='CashMargin' required='N' />
<field name='ClearingFeeIndicator' required='N' />
<field name='HandlInst' required='Y' />
<field name='ExecInst' required='N' />
<field name='MinQty' required='N' />
<field name='MaxFloor' required='N' />
<field name='ExDestination' required='N' />
<group name='NoTradingSessions' required='N'>
<field name='TradingSessionID' required='N' />
<field name='TradingSessionSubID' required='N' />
</group>
</message>
</messages>
<trailer>
<field name='SignatureLength' required='N' />
<field name='Signature' required='N' />
<field name='CheckSum' required='Y' />
</trailer>
</fix>
`
cachedXMLDoc = new(XMLDoc)
err := xml.Unmarshal([]byte(data), cachedXMLDoc)
return cachedXMLDoc, err
}
func TestBoilerPlate(t *testing.T) {
doc, err := xmlDoc()
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
var tests = []struct {
Value interface{}
ExpectedValue interface{}
}{
{doc.Type, "FIX"},
{doc.Major, "4"},
{doc.Minor, "3"},
{doc.ServicePack, 0},
}
for _, test := range tests {
if !reflect.DeepEqual(test.Value, test.ExpectedValue) {
t.Errorf("Expected %v got %v", test.ExpectedValue, test.Value)
}
}
}
func TestComponentMembers(t *testing.T) {
doc, err := xmlDoc()
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if doc.Header == nil {
t.Fatal("Header is nil")
}
var tests = []struct {
Value *XMLComponentMember
XMLNameLocal string
Name string
Required bool
}{
{doc.Header.Members[0], "field", "BeginString", true},
{doc.Header.Members[1], "group", "NoHops", false},
{doc.Header.Members[1].Members[0], "field", "HopCompID", false},
{doc.Trailer.Members[0], "field", "SignatureLength", false},
{doc.Messages[0].Members[0], "field", "TestReqID", false},
{doc.Messages[1].Members[3], "component", "Instrument", true},
{doc.Messages[1].Members[4], "group", "NoRoutingIDs", false},
{doc.Messages[1].Members[4].Members[0], "field", "RoutingType", false},
{doc.Messages[1].Members[4].Members[1], "field", "RoutingID", false},
}
for _, test := range tests {
if test.Value.XMLName.Local != test.XMLNameLocal {
t.Errorf("%v: Expected XMLName Local %v got %v", test.Name, test.XMLNameLocal, test.Value.XMLName.Local)
}
if test.Value.Name != test.Name {
t.Errorf("%v: Expected Name %v got %v", test.Name, test.Name, test.Value.Name)
}
if test.Value.isRequired() != test.Required {
t.Errorf("%v: Expected Required %v got %v", test.Name, test.Required, test.Value.isRequired())
}
}
}
func TestMessages(t *testing.T) {
doc, err := xmlDoc()
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
var tests = []struct {
Value *XMLComponent
Name string
MsgCat string
MsgType string
}{
{doc.Messages[0], "Heartbeat", "admin", "0"},
}
for _, test := range tests {
if test.Value.Name != test.Name {
t.Errorf("Expected Name %v got %v", test.Name, test.Value.Name)
}
if test.Value.MsgCat != test.MsgCat {
t.Errorf("Expected MsgCat %v got %v", test.MsgCat, test.Value.MsgCat)
}
if test.Value.MsgType != test.MsgType {
t.Errorf("Expected MsgType %v got %v", test.MsgType, test.Value.MsgType)
}
}
}