Skip to content

Support udecimal library in code generation #700

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
with:
version: v1.57.2
version: v1.64.6

build:
name: Build Source
Expand Down Expand Up @@ -101,6 +101,13 @@ jobs:
STORE_TYPE: memory
ACCEPTANCE_SET: server
run: make generate-ci && make build && make $FIX_TEST
- name: Acceptance test with udecimal
env:
GO111MODULE: on
FIX_TEST: ${{ matrix.fix-version }}
STORE_TYPE: memory
ACCEPTANCE_SET: server
run: make generate-ci-udecimal && make build && make $FIX_TEST

serverfile:
name: Server FileStore Suite
Expand Down Expand Up @@ -135,6 +142,13 @@ jobs:
STORE_TYPE: file
ACCEPTANCE_SET: server
run: make generate-ci && make build && make $FIX_TEST
- name: Acceptance test with udecimal
env:
GO111MODULE: on
FIX_TEST: ${{ matrix.fix-version }}
STORE_TYPE: file
ACCEPTANCE_SET: server
run: make generate-ci-udecimal && make build && make $FIX_TEST

servermongo:
name: Server MongoStore Suite
Expand Down Expand Up @@ -174,6 +188,13 @@ jobs:
STORE_TYPE: mongo
ACCEPTANCE_SET: server
run: make generate-ci && make build && make $FIX_TEST
- name: Acceptance test with udecimal
env:
GO111MODULE: on
FIX_TEST: ${{ matrix.fix-version }}
STORE_TYPE: mongo
ACCEPTANCE_SET: server
run: make generate-ci-udecimal && make build && make $FIX_TEST

resendreqchunksize:
name: ResendRequestChunkSize Suite
Expand Down Expand Up @@ -208,6 +229,13 @@ jobs:
STORE_TYPE: memory
ACCEPTANCE_SET: resendreqchunksize
run: make generate-ci && make build && make $FIX_TEST
- name: Acceptance test with udecimal
env:
GO111MODULE: on
FIX_TEST: ${{ matrix.fix-version }}
STORE_TYPE: memory
ACCEPTANCE_SET: resendreqchunksize
run: make generate-ci-udecimal && make build && make $FIX_TEST

lastseqnumprocessed:
name: LastSeqNumProcessed Suite
Expand Down Expand Up @@ -240,6 +268,13 @@ jobs:
STORE_TYPE: memory
ACCEPTANCE_SET: lastseqnumprocessed
run: make generate-ci && make build && make $FIX_TEST
- name: Acceptance test with udecimal
env:
GO111MODULE: on
FIX_TEST: ${{ matrix.fix-version }}
STORE_TYPE: memory
ACCEPTANCE_SET: lastseqnumprocessed
run: make generate-ci-udecimal && make build && make $FIX_TEST

nextexpectedseqnum:
name: NextExpectedSeqNum Suite
Expand Down Expand Up @@ -269,4 +304,11 @@ jobs:
FIX_TEST: ${{ matrix.fix-version }}
STORE_TYPE: memory
ACCEPTANCE_SET: nextexpectedseqnum
run: make generate-ci && make build && make $FIX_TEST
run: make generate-ci && make build && make $FIX_TEST
- name: Acceptance test with udecimal
env:
GO111MODULE: on
FIX_TEST: ${{ matrix.fix-version }}
STORE_TYPE: memory
ACCEPTANCE_SET: nextexpectedseqnum
run: make generate-ci-udecimal && make build && make $FIX_TEST
12 changes: 9 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ clean:
generate: clean
mkdir -p gen; cd gen; go run ../cmd/generate-fix/generate-fix.go -pkg-root=github.com/quickfixgo/quickfix/gen ../spec/*.xml

generate-udecimal: clean
mkdir -p gen; cd gen; go run ../cmd/generate-fix/generate-fix.go -use-udecimal=true -pkg-root=github.com/quickfixgo/quickfix/gen ../spec/*.xml

fmt:
gofmt -l -w -s $(shell find . -type f -name '*.go')

Expand All @@ -19,14 +22,14 @@ test:
linters-install:
@golangci-lint --version >/dev/null 2>&1 || { \
echo "installing linting tools..."; \
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.57.2; \
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.64.6; \
}

lint: linters-install
golangci-lint run

# An easy way to run the linter without going through the install process -
# docker run -t --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.57.2 golangci-lint run -v
# docker run -t --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.64.6 golangci-lint run -v
# See https://golangci-lint.run/welcome/install/ for more details.

# ---------------------------------------------------------------
Expand Down Expand Up @@ -81,6 +84,9 @@ test-ci:
go test -v -cover `go list ./... | grep -v quickfix/gen`

generate-ci: clean
mkdir -p gen; cd gen; go run ../cmd/generate-fix/generate-fix.go -pkg-root=github.com/quickfixgo/quickfix/gen ../spec/$(shell echo $(FIX_TEST) | tr '[:lower:]' '[:upper:]').xml;
mkdir -p gen; cd gen; go run ../cmd/generate-fix/generate-fix.go -pkg-root=github.com/quickfixgo/quickfix/gen ../spec/$(shell echo $(FIX_TEST) | tr '[:lower:]' '[:upper:]').xml;

generate-ci-udecimal: clean
mkdir -p gen; cd gen; go run ../cmd/generate-fix/generate-fix.go -use-udecimal=true -pkg-root=github.com/quickfixgo/quickfix/gen ../spec/$(shell echo $(FIX_TEST) | tr '[:lower:]' '[:upper:]').xml;

# ---------------------------------------------------------------
1 change: 1 addition & 0 deletions cmd/generate-fix/internal/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

var (
useFloat = flag.Bool("use-float", false, "By default, FIX float fields are represented as arbitrary-precision fixed-point decimal numbers. Set to 'true' to instead generate FIX float fields as float64 values.")
useUDecimal = flag.Bool("use-udecimal", false, "By default, FIX uses the shopspring/decimal library for fixed-point decimal numbers. Set to 'true' to instead use the quagmt/udecimal library.")
pkgRoot = flag.String("pkg-root", "github.com/quickfixgo", "Set a string here to provide a custom import path for generated packages.")
tabWidth = 8
printerMode = printer.UseSpaces | printer.TabIndent
Expand Down
23 changes: 20 additions & 3 deletions cmd/generate-fix/internal/template_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import (
"github.com/quickfixgo/quickfix/datadictionary"
)

func isDecimalType(quickfixType string) bool {
switch quickfixType {
case "FIXDecimal", "FIXUDecimal":
return true
default:
return false
}
}

func checkIfDecimalImportRequiredForFields(fTypes []*datadictionary.FieldType) (ok bool, err error) {
var t string
for _, fType := range fTypes {
Expand All @@ -14,7 +23,7 @@ func checkIfDecimalImportRequiredForFields(fTypes []*datadictionary.FieldType) (
return
}

if t == "FIXDecimal" {
if isDecimalType(t) {
return true, nil
}
}
Expand Down Expand Up @@ -54,7 +63,7 @@ func checkFieldDecimalRequired(f *datadictionary.FieldDef) (required bool, err e
return
}

if t == "FIXDecimal" {
if isDecimalType(t) {
required = true
return
}
Expand Down Expand Up @@ -121,6 +130,10 @@ func collectStandardImports(m *datadictionary.MessageDef) (imports []string, err

func collectExtraImports(m *datadictionary.MessageDef) (imports []string, err error) {
var decimalRequired bool
importPath := "github.com/shopspring/decimal"
if *useUDecimal {
importPath = "github.com/quagmt/udecimal"
}
for _, f := range m.Fields {
if !decimalRequired {
if decimalRequired, err = checkFieldDecimalRequired(f); err != nil {
Expand All @@ -134,7 +147,7 @@ func collectExtraImports(m *datadictionary.MessageDef) (imports []string, err er
}

if decimalRequired {
imports = append(imports, "github.com/shopspring/decimal")
imports = append(imports, importPath)
}

return
Expand Down Expand Up @@ -191,6 +204,8 @@ func quickfixValueType(quickfixType string) (goType string, err error) {
goType = "float64"
case "FIXDecimal":
goType = "decimal.Decimal"
case "FIXUDecimal":
goType = "udecimal.Decimal"
default:
err = fmt.Errorf("Unknown QuickFIX Type: %v", quickfixType)
}
Expand Down Expand Up @@ -275,6 +290,8 @@ func quickfixType(field *datadictionary.FieldType) (quickfixType string, err err
case "FLOAT":
if *useFloat {
quickfixType = "FIXFloat"
} else if *useUDecimal {
quickfixType = "FIXUDecimal"
} else {
quickfixType = "FIXDecimal"
}
Expand Down
25 changes: 22 additions & 3 deletions cmd/generate-fix/internal/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@ func init() {
"collectStandardImports": collectStandardImports,
"collectExtraImports": collectExtraImports,
"checkIfDecimalImportRequiredForFields": checkIfDecimalImportRequiredForFields,
"checkIfTimeImportRequiredForFields": checkIfTimeImportRequiredForFields,
"checkIfEnumImportRequired": checkIfEnumImportRequired,
"decimalImport": func() string {
if *useUDecimal {
return "github.com/quagmt/udecimal"
}
return "github.com/shopspring/decimal"
},
"checkIfTimeImportRequiredForFields": checkIfTimeImportRequiredForFields,
"checkIfEnumImportRequired": checkIfEnumImportRequired,
}

baseTemplate := template.Must(template.New("Base").Funcs(tmplFuncs).Parse(`
Expand All @@ -45,6 +51,10 @@ Set{{ .Name }}(v enum.{{ .Name }}) {
Set{{ .Name }}(value decimal.Decimal, scale int32) {
{{ template "receiver" }}.Set(field.New{{ .Name }}(value, scale))
}
{{- else if eq $qfix_type "FIXUDecimal" -}}
Set{{ .Name }}(value udecimal.Decimal, scale uint8) {
{{ template "receiver" }}.Set(field.New{{ .Name }}(value, scale))
}
{{- else -}}
Set{{ .Name }}(v {{ quickfixValueType $qfix_type }}) {
{{ template "receiver" }}.Set(field.New{{ .Name }}(v))
Expand Down Expand Up @@ -77,6 +87,8 @@ Get{{ .Name }}() (f field.{{ .Name }}Field, err quickfix.MessageRejectError) {
Get{{ .Name }}() (v enum.{{ .Name }}, err quickfix.MessageRejectError) {
{{- else if eq $bt "FIXDecimal" -}}
Get{{ .Name }}() (v decimal.Decimal, err quickfix.MessageRejectError) {
{{- else if eq $bt "FIXUDecimal" -}}
Get{{ .Name }}() (v udecimal.Decimal, err quickfix.MessageRejectError) {
{{- else -}}
Get{{ .Name }}() (v {{ quickfixValueType $bt }}, err quickfix.MessageRejectError) {
{{- end }}
Expand Down Expand Up @@ -345,7 +357,7 @@ package field
import (
{{ if checkIfTimeImportRequiredForFields . }}"time"{{ end }}

{{ if checkIfDecimalImportRequiredForFields . }}"github.com/shopspring/decimal"{{ end }}
{{ if checkIfDecimalImportRequiredForFields . }}"{{ decimalImport }}"{{ end }}

"github.com/quickfixgo/quickfix"
"{{ importRootPath }}/enum"
Expand Down Expand Up @@ -391,6 +403,11 @@ func New{{ .Name }}(val enum.{{ .Name }}) {{ .Name }}Field {
func New{{ .Name }}(val decimal.Decimal, scale int32) {{ .Name }}Field {
return {{ .Name }}Field{ quickfix.FIXDecimal{ Decimal: val, Scale: scale} }
}
{{ else if eq $base_type "FIXUDecimal" }}
// New{{ .Name }} returns a new {{ .Name }}Field initialized with val and scale.
func New{{ .Name }}(val udecimal.Decimal, scale uint8) {{ .Name }}Field {
return {{ .Name }}Field{ quickfix.FIXUDecimal{ Decimal: val, Scale: scale} }
}
{{ else }}
// New{{ .Name }} returns a new {{ .Name }}Field initialized with val.
func New{{ .Name }}(val {{ quickfixValueType $base_type }}) {{ .Name }}Field {
Expand All @@ -402,6 +419,8 @@ func New{{ .Name }}(val {{ quickfixValueType $base_type }}) {{ .Name }}Field {
func (f {{ .Name }}Field) Value() enum.{{ .Name }} { return enum.{{ .Name }}(f.String()) }
{{ else if eq $base_type "FIXDecimal" }}
func (f {{ .Name }}Field) Value() (val decimal.Decimal) { return f.Decimal }
{{ else if eq $base_type "FIXUDecimal" }}
func (f {{ .Name }}Field) Value() (val udecimal.Decimal) { return f.Decimal }
{{ else }}
func (f {{ .Name }}Field) Value() ({{ quickfixValueType $base_type }}) {
{{- if eq $base_type "FIXString" -}}
Expand Down
23 changes: 23 additions & 0 deletions fix_decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,29 @@ import (
"github.com/stretchr/testify/require"
)

func BenchmarkFIXDecimalRead(b *testing.B) {
var dec FIXDecimal
byt := []byte("-124.3456")
for i := 0; i < b.N; i++ {
if err := dec.Read(byt); err != nil {
b.FailNow()
}
}
}

func BenchmarkFIXDecimalWrite(b *testing.B) {
decValue, err := decimal.NewFromString("-124.3456")
if err != nil {
b.FailNow()
}
dec := FIXDecimal{Decimal: decValue, Scale: 5}
for i := 0; i < b.N; i++ {
if byt := dec.Write(); len(byt) == 0 {
b.FailNow()
}
}
}

func TestFIXDecimalWrite(t *testing.T) {
var tests = []struct {
decimal FIXDecimal
Expand Down
35 changes: 35 additions & 0 deletions fix_udecimal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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/quagmt/udecimal"

// FIXUDecimal is a FIX Float Value that implements an arbitrary precision fixed-point udecimal. Implements FieldValue.
type FIXUDecimal struct {
udecimal.Decimal

// Scale is the number of digits after the decimal point when Writing the field value as a FIX value.
Scale uint8
}

func (d FIXUDecimal) Write() []byte {
return []byte(d.Decimal.Trunc(d.Scale).StringFixed(d.Scale))
}

func (d *FIXUDecimal) Read(bytes []byte) (err error) {
d.Decimal, err = udecimal.Parse(string(bytes))
return
}
Loading
Loading