adding quickfix library
This commit is contained in:
247
quickfix/datadictionary/build.go
Normal file
247
quickfix/datadictionary/build.go
Normal 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)
|
||||
}
|
||||
139
quickfix/datadictionary/build_test.go
Normal file
139
quickfix/datadictionary/build_test.go
Normal 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())
|
||||
}
|
||||
}
|
||||
80
quickfix/datadictionary/component_type_test.go
Normal file
80
quickfix/datadictionary/component_type_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
333
quickfix/datadictionary/datadictionary.go
Normal file
333
quickfix/datadictionary/datadictionary.go
Normal 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
|
||||
}
|
||||
191
quickfix/datadictionary/datadictionary_test.go
Normal file
191
quickfix/datadictionary/datadictionary_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
26
quickfix/datadictionary/field_def_test.go
Normal file
26
quickfix/datadictionary/field_def_test.go
Normal 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())
|
||||
}
|
||||
}
|
||||
17
quickfix/datadictionary/field_type_test.go
Normal file
17
quickfix/datadictionary/field_type_test.go
Normal 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)
|
||||
}
|
||||
17
quickfix/datadictionary/group_field_def_test.go
Normal file
17
quickfix/datadictionary/group_field_def_test.go
Normal 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())
|
||||
}
|
||||
85
quickfix/datadictionary/message_def_test.go
Normal file
85
quickfix/datadictionary/message_def_test.go
Normal 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())
|
||||
}
|
||||
}
|
||||
63
quickfix/datadictionary/xml.go
Normal file
63
quickfix/datadictionary/xml.go
Normal 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"
|
||||
}
|
||||
186
quickfix/datadictionary/xml_test.go
Normal file
186
quickfix/datadictionary/xml_test.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user