Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit abb25a6

Browse files
committedMay 4, 2022
decimal: add support decimal type in msgpack
This patch provides decimal support for all space operations and as function return result. Decimal type was introduced in Tarantool 2.2. See more about decimal type in [1] and [2]. According to BCD encoding/decoding specification sign is encoded by letters: '0x0a', '0x0c', '0x0e', '0x0f' stands for plus, and '0x0b' and '0x0d' for minus. Tarantool always uses '0x0c' for plus and '0x0d' for minus. Implementation in Golang follows the same rule and in all test samples sign encoded by '0x0d' and '0x0c' for simplification. Because 'c' used by Tarantool. To use decimal with github.com/shopspring/decimal in msgpack, import tarantool/decimal submodule. 1. https://www.tarantool.io/en/doc/latest/book/box/data_model/ 2. https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-decimal-type 3. https://github.com/douglascrockford/DEC64/blob/663f562a5f0621021b98bfdd4693571993316174/dec64_test.c#L62-L104 4. https://github.com/shopspring/decimal/blob/v1.3.1/decimal_test.go#L27-L64 5. https://github.com/tarantool/tarantool/blob/60fe9d14c1c7896aa7d961e4b68649eddb4d2d6c/test/unit/decimal.c#L154-L171 Lua snippet for encoding number to MsgPack representation: local decimal = require('decimal') local function mp_encode_dec(num) local dec = msgpack.encode(decimal.new(num)) return dec:gsub('.', function (c) return string.format('%02x', string.byte(c)) end) end print(mp_encode_dec(-12.34)) -- 0xd6010201234d Follows up tarantool/tarantool#692 Part of #96
1 parent de95e31 commit abb25a6

File tree

9 files changed

+894
-0
lines changed

9 files changed

+894
-0
lines changed
 

‎CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1717
- Go modules support (#91)
1818
- queue-utube handling (#85)
1919
- Master discovery (#113)
20+
- Support decimal type in msgpack (#96)
2021

2122
### Fixed
2223

‎Makefile

+6
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ test-main:
4545
go clean -testcache
4646
go test . -v -p 1
4747

48+
.PHONY: test-decimal
49+
test-decimal:
50+
@echo "Running tests in decimal package"
51+
go clean -testcache
52+
go test ./decimal/ -v -p 1
53+
4854
.PHONY: coverage
4955
coverage:
5056
go clean -testcache

‎decimal/bcd.go

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// Package decimal implements methods to encode and decode BCD.
2+
//
3+
// BCD (Binary-Coded Decimal) is a sequence of bytes representing decimal
4+
// digits of the encoded number (each byte has two decimal digits each encoded
5+
// using 4-bit nibbles), so byte >> 4 is the first digit and byte & 0x0f is the
6+
// second digit. The leftmost digit in the array is the most significant. The
7+
// rightmost digit in the array is the least significant.
8+
//
9+
// The first byte of the BCD array contains the first digit of the number,
10+
// represented as follows:
11+
//
12+
// | 4 bits | 4 bits |
13+
// = 0x = the 1st digit
14+
//
15+
// (The first nibble contains 0 if the decimal number has an even number of
16+
// digits). The last byte of the BCD array contains the last digit of the
17+
// number and the final nibble, represented as follows:
18+
//
19+
// | 4 bits | 4 bits |
20+
// = the last digit = nibble
21+
//
22+
// The final nibble represents the number's sign: 0x0a, 0x0c, 0x0e, 0x0f stand
23+
// for plus, 0x0b and 0x0d stand for minus.
24+
//
25+
// Examples:
26+
//
27+
// The decimal -12.34 will be encoded as 0xd6, 0x01, 0x02, 0x01, 0x23, 0x4d:
28+
//
29+
// |MP_EXT (fixext 4) | MP_DECIMAL | scale | 1 | 2,3 | 4 (minus) |
30+
// | 0xd6 | 0x01 | 0x02 | 0x01 | 0x23 | 0x4d |
31+
//
32+
// The decimal 0.000000000000000000000000000000000010 will be encoded as
33+
// 0xc7, 0x03, 0x01, 0x24, 0x01, 0x0c:
34+
//
35+
// | MP_EXT (ext 8) | length | MP_DECIMAL | scale | 1 | 0 (plus) |
36+
// | 0xc7 | 0x03 | 0x01 | 0x24 | 0x01 | 0x0c |
37+
//
38+
// See also:
39+
//
40+
// * MessagePack extensions https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/
41+
//
42+
// * An implementation in C language https://github.com/tarantool/decNumber/blob/master/decPacked.c
43+
package decimal
44+
45+
import (
46+
"fmt"
47+
"strconv"
48+
"strings"
49+
"unicode"
50+
)
51+
52+
var mapRuneToByteSign = map[rune]byte{
53+
'+': 0x0c,
54+
'-': 0x0d,
55+
}
56+
57+
var isNegative = map[byte]bool{
58+
0x0a: false,
59+
0x0b: true,
60+
0x0c: false,
61+
0x0d: true,
62+
0x0e: false,
63+
0x0f: false,
64+
}
65+
66+
// EncodeStringToBCD converts a string buffer to BCD Packed Decimal.
67+
//
68+
// The number is converted to a BCD packed decimal byte array, right aligned in
69+
// the BCD array, whose length is indicated by the second parameter. The final
70+
// 4-bit nibble in the array will be a sign nibble, 0x0a for "+" and 0x0b for
71+
// "-". Unused bytes and nibbles to the left of the number are set to 0. scale
72+
// is set to the scale of the number (this is the exponent, negated).
73+
func EncodeStringToBCD(buf string) ([]byte, error) {
74+
sign := '+' // By default number is positive.
75+
if res := strings.HasPrefix(buf, "-"); res {
76+
sign = '-'
77+
}
78+
79+
// Calculate a number of digits in the decimal number. Leading and
80+
// trailing zeros do not count.
81+
// Examples:
82+
// 0.0000000000000001 - 1 digit
83+
// 00012.34 - 4 digits
84+
// 0.340 - 2 digits
85+
s := strings.ReplaceAll(buf, "-", "") // Remove a sign.
86+
s = strings.ReplaceAll(s, "+", "") // Remove a sign.
87+
s = strings.ReplaceAll(s, ".", "") // Remove a dot.
88+
s = strings.TrimLeft(s, "0") // Remove leading zeros.
89+
c := len(s)
90+
91+
// Fix a case with a single 0.
92+
if c == 0 {
93+
s = "0" //nolint
94+
c = 1
95+
}
96+
97+
// The first nibble should contain 0, if the decimal number has an even
98+
// number of digits. Therefore highNibble is false when decimal number
99+
// is even.
100+
highNibble := true
101+
if c%2 == 0 {
102+
highNibble = false
103+
}
104+
scale := 0 // By default decimal number is integer.
105+
var byteBuf []byte
106+
for i, ch := range buf {
107+
// Skip leading zeros.
108+
if (len(byteBuf) == 0) && ch == '0' {
109+
continue
110+
}
111+
if (i == 0) && (ch == '-' || ch == '+') {
112+
continue
113+
}
114+
// Calculate a number of digits after the decimal point.
115+
if ch == '.' {
116+
scale = len(buf) - i - 1
117+
continue
118+
}
119+
if !unicode.IsDigit(ch) {
120+
return nil, fmt.Errorf("Symbol in position %d is not a digit: %c", i, ch)
121+
}
122+
123+
d, err := strconv.Atoi(string(ch))
124+
if err != nil {
125+
return nil, fmt.Errorf("Failed to convert symbol '%c' to a digit: %s", ch, err)
126+
}
127+
digit := byte(d)
128+
if highNibble {
129+
// Add a digit to a high nibble.
130+
digit = digit << 4
131+
byteBuf = append(byteBuf, digit)
132+
highNibble = false
133+
} else {
134+
if len(byteBuf) == 0 {
135+
byteBuf = make([]byte, 1)
136+
}
137+
// Add a digit to a low nibble.
138+
lowByteIdx := len(byteBuf) - 1
139+
byteBuf[lowByteIdx] = byteBuf[lowByteIdx] | digit
140+
highNibble = true
141+
}
142+
}
143+
if highNibble {
144+
// Put a sign to a high nibble.
145+
byteBuf = append(byteBuf, mapRuneToByteSign[sign])
146+
} else {
147+
// Put a sign to a low nibble.
148+
lowByteIdx := len(byteBuf) - 1
149+
byteBuf[lowByteIdx] = byteBuf[lowByteIdx] | mapRuneToByteSign[sign]
150+
}
151+
byteBuf = append([]byte{byte(scale)}, byteBuf...)
152+
153+
return byteBuf, nil
154+
}
155+
156+
// DecodeStringFromBCD converts a BCD Packed Decimal to a string buffer.
157+
//
158+
// The BCD packed decimal byte array, together with an associated scale, is
159+
// converted to a string. The BCD array is assumed full of digits, and must be
160+
// ended by a 4-bit sign nibble in the least significant four bits of the final
161+
// byte. The scale is used (negated) as the exponent of the decimal number.
162+
// Note that zeros may have a sign and/or a scale.
163+
func DecodeStringFromBCD(bcdBuf []byte) (string, error) {
164+
const scaleIdx = 0 // Index of a byte with scale.
165+
scale := int(bcdBuf[scaleIdx])
166+
// Get a BCD buffer without a byte with scale.
167+
bcdBuf = bcdBuf[scaleIdx+1:]
168+
length := len(bcdBuf)
169+
var digits []string
170+
for i, bcdByte := range bcdBuf {
171+
highNibble := int(bcdByte >> 4)
172+
if !(len(digits) == 0 && highNibble == 0) || len(bcdBuf) == 1 {
173+
digits = append(digits, strconv.Itoa(highNibble))
174+
}
175+
lowNibble := int(bcdByte & 0x0f)
176+
if !(len(digits) == 0 && lowNibble == 0) && i != length-1 {
177+
digits = append(digits, strconv.Itoa(int(lowNibble)))
178+
}
179+
}
180+
181+
// Add missing zeros when scale is less than current length.
182+
l := len(digits)
183+
if scale >= l {
184+
var zeros []string
185+
for i := 0; i <= scale-l; i++ {
186+
zeros = append(zeros, "0")
187+
digits = append(zeros, digits...)
188+
}
189+
}
190+
191+
// Add a dot when number is fractional.
192+
if scale != 0 {
193+
idx := len(digits) - scale
194+
digits = append(digits, "X") // [1 2 3 X]
195+
copy(digits[idx:], digits[idx-1:]) // [1 2 2 3]
196+
digits[idx] = "." // [1 . 2 3]
197+
}
198+
199+
// Add a sign, it is encoded in a low nibble of a last byte.
200+
lastByte := bcdBuf[length-1]
201+
sign := lastByte & 0x0f
202+
if isNegative[sign] {
203+
digits = append([]string{"-"}, digits...)
204+
}
205+
206+
// Merge slice to a single string.
207+
str := strings.Join(digits, "")
208+
209+
return str, nil
210+
}

‎decimal/config.lua

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
local decimal = require('decimal')
2+
local msgpack = require('msgpack')
3+
4+
-- Do not set listen for now so connector won't be
5+
-- able to send requests until everything is configured.
6+
box.cfg{
7+
work_dir = os.getenv("TEST_TNT_WORK_DIR"),
8+
}
9+
10+
box.schema.user.create('test', { password = 'test' , if_not_exists = true })
11+
box.schema.user.grant('test', 'execute', 'universe', nil, { if_not_exists = true })
12+
13+
local decimal_msgpack_supported = pcall(msgpack.encode, decimal.new(1))
14+
if not decimal_msgpack_supported then
15+
error('Decimal unsupported, use Tarantool 2.2 or newer')
16+
end
17+
18+
local s = box.schema.space.create('testDecimal', {
19+
id = 524,
20+
if_not_exists = true,
21+
})
22+
s:create_index('primary', {
23+
type = 'TREE',
24+
parts = {
25+
{
26+
field = 1,
27+
type = 'decimal',
28+
},
29+
},
30+
if_not_exists = true
31+
})
32+
s:truncate()
33+
34+
box.schema.user.grant('test', 'read,write', 'space', 'testDecimal', { if_not_exists = true })
35+
36+
-- Set listen only when every other thing is configured.
37+
box.cfg{
38+
listen = os.getenv("TEST_TNT_LISTEN"),
39+
}
40+
41+
require('console').start()

‎decimal/decimal.go

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Package decimal with support of Tarantool's decimal data type.
2+
//
3+
// Decimal data type supported in Tarantool since 2.2.
4+
//
5+
// Since: 1.6
6+
//
7+
// See also:
8+
//
9+
// * Tarantool MessagePack extensions https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-decimal-type
10+
//
11+
// * Tarantool data model https://www.tarantool.io/en/doc/latest/book/box/data_model/
12+
//
13+
// * Tarantool issue for support decimal type https://github.com/tarantool/tarantool/issues/692
14+
package decimal
15+
16+
import (
17+
"fmt"
18+
"reflect"
19+
20+
"github.com/shopspring/decimal"
21+
"gopkg.in/vmihailenco/msgpack.v2"
22+
)
23+
24+
// Decimal external type.
25+
const decimalExtID = 1
26+
27+
func encodeDecimal(e *msgpack.Encoder, v reflect.Value) error {
28+
number := v.Interface().(decimal.Decimal)
29+
strBuf := number.String()
30+
bcdBuf, err := EncodeStringToBCD(strBuf)
31+
if err != nil {
32+
return fmt.Errorf("msgpack: can't encode string (%s) to a BCD buffer: %w", strBuf, err)
33+
}
34+
if _, err = e.Writer().Write(bcdBuf); err != nil {
35+
return fmt.Errorf("msgpack: can't write bytes to encoder writer: %w", err)
36+
}
37+
38+
return nil
39+
}
40+
41+
// Decimal values can be encoded to fixext MessagePack, where buffer
42+
// has a fixed length encoded by first byte, and ext MessagePack, where
43+
// buffer length is not fixed and encoded by a number in a separate
44+
// field:
45+
//
46+
// +--------+-------------------+------------+===============+
47+
// | MP_EXT | length (optional) | MP_DECIMAL | PackedDecimal |
48+
// +--------+-------------------+------------+===============+
49+
//
50+
// Before reading a buffer with encoded decimal number (PackedDecimal) we need
51+
// to allocate it, but before reading we don't know it's exact size.
52+
// msgpack.Decoder in msgpack v2 package pass a buffer with PackedDecimal bytes
53+
// and there is no possibility to read length in advance. to obtain MessagePack
54+
// length in advance. For example on attempt to decode MessagePack buffer
55+
// c7030100088c (88) msgpack pass 00088a to decodeDecimal() and length field
56+
// value (03) is unavailable nor in buffer nor via decoder interface.
57+
//
58+
// To solve a problem we allocate a buffer with maximum size (it is 32 bytes
59+
// and it is corresponds to ext32 MessagePack), then read encoded decimal to a
60+
// buffer and finally cut off unused part.
61+
func decodeDecimal(d *msgpack.Decoder, v reflect.Value) error {
62+
var maxBytesCount int = 32
63+
b := make([]byte, maxBytesCount)
64+
65+
readBytes, err := d.Buffered().Read(b)
66+
if err != nil {
67+
return fmt.Errorf("msgpack: can't read bytes on decimal decode: %w", err)
68+
}
69+
b = b[:readBytes]
70+
digits, err := DecodeStringFromBCD(b)
71+
if err != nil {
72+
return fmt.Errorf("msgpack: can't decode string from BCD buffer (%x): %w", b, err)
73+
}
74+
dec, err := decimal.NewFromString(digits)
75+
if err != nil {
76+
return fmt.Errorf("msgpack: can't encode string (%s) to a decimal number: %w", digits, err)
77+
}
78+
79+
v.Set(reflect.ValueOf(dec))
80+
81+
return nil
82+
}
83+
84+
func init() {
85+
msgpack.Register(reflect.TypeOf((*decimal.Decimal)(nil)).Elem(), encodeDecimal, decodeDecimal)
86+
msgpack.RegisterExt(decimalExtID, (*decimal.Decimal)(nil))
87+
}

‎decimal/decimal_test.go

+480
Large diffs are not rendered by default.

‎decimal/example_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Run Tarantool instance before example execution:
2+
//
3+
// Terminal 1:
4+
// $ cd decimal
5+
// $ TEST_TNT_LISTEN=3013 TEST_TNT_WORK_DIR=$(mktemp -d -t 'tarantool.XXX') tarantool config.lua
6+
//
7+
// Terminal 2:
8+
// $ go test -v example_test.go
9+
package decimal_test
10+
11+
import (
12+
"log"
13+
"time"
14+
15+
"github.com/shopspring/decimal"
16+
"github.com/tarantool/go-tarantool"
17+
_ "github.com/tarantool/go-tarantool/decimal"
18+
)
19+
20+
// To enable support of decimal in msgpack with
21+
// https://github.com/shopspring/decimal,
22+
// import tarantool/decimal submodule.
23+
func Example() {
24+
server := "127.0.0.1:3013"
25+
opts := tarantool.Opts{
26+
Timeout: 500 * time.Millisecond,
27+
Reconnect: 1 * time.Second,
28+
MaxReconnects: 3,
29+
User: "test",
30+
Pass: "test",
31+
}
32+
client, err := tarantool.Connect(server, opts)
33+
if err != nil {
34+
log.Fatalf("Failed to connect: %s", err.Error())
35+
}
36+
37+
spaceNo := uint32(524)
38+
39+
number, err := decimal.NewFromString("-22.804")
40+
if err != nil {
41+
log.Fatalf("Failed to prepare test decimal: %s", err)
42+
}
43+
44+
resp, err := client.Replace(spaceNo, []interface{}{number})
45+
if err != nil {
46+
log.Fatalf("Decimal replace failed: %s", err)
47+
}
48+
if resp == nil {
49+
log.Fatalf("Response is nil after Replace")
50+
}
51+
52+
log.Println("Decimal tuple replace")
53+
log.Println("Error", err)
54+
log.Println("Code", resp.Code)
55+
log.Println("Data", resp.Data)
56+
}

‎go.mod

+4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ module github.com/tarantool/go-tarantool
33
go 1.11
44

55
require (
6+
github.com/google/go-cmp v0.5.7 // indirect
67
github.com/google/uuid v1.3.0
78
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
89
github.com/stretchr/testify v1.7.1 // indirect
10+
github.com/pkg/errors v0.9.1 // indirect
11+
github.com/shopspring/decimal v1.3.1
912
google.golang.org/appengine v1.6.7 // indirect
1013
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
1114
gopkg.in/vmihailenco/msgpack.v2 v2.9.2
15+
gotest.tools v2.2.0+incompatible
1216
)

‎go.sum

+9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
22
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
33
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
44
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
5+
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
6+
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
57
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
68
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
79
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -14,13 +16,18 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
1416
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
1517
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
1618
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
19+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
20+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
21+
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
22+
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
1723
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
1824
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
1925
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
2026
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
2127
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
2228
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
2329
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
30+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
2431
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
2532
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
2633
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -30,3 +37,5 @@ gopkg.in/vmihailenco/msgpack.v2 v2.9.2 h1:gjPqo9orRVlSAH/065qw3MsFCDpH7fa1KpiizX
3037
gopkg.in/vmihailenco/msgpack.v2 v2.9.2/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8=
3138
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
3239
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
40+
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
41+
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=

0 commit comments

Comments
 (0)
Please sign in to comment.