From 44f067ec2f6503a82b62653db7bd31bc84b1c938 Mon Sep 17 00:00:00 2001 From: noahwill Date: Mon, 12 Sep 2022 15:26:37 -0600 Subject: [PATCH] on encode/decode of uuid, ensure validity use uuid.Parse() to ensure that a string received in a field with logicalType 'uuid' is truly a valid uuid --- codec.go | 9 +++++++++ go.mod | 1 + go.sum | 3 +++ logical_type.go | 38 ++++++++++++++++++++++++++++++++++++++ logical_type_test.go | 7 +++++++ 5 files changed, 58 insertions(+) diff --git a/codec.go b/codec.go index ee5bda1..7dfce5c 100644 --- a/codec.go +++ b/codec.go @@ -301,6 +301,15 @@ func newSymbolTable() map[string]*Codec { }, // Start of compiled logical types using format typeName.logicalType where there is // no dependence on schema. + "string.uuid": { + typeName: &name{"string.uuid", nullNamespace}, + schemaOriginal: "string", + schemaCanonical: "string", + binaryFromNative: uuidFromNative(stringBinaryFromNative), + nativeFromBinary: nativeFromUUID(stringNativeFromBinary), + textualFromNative: uuidFromNative(stringTextualFromNative), + nativeFromTextual: nativeFromUUID(stringNativeFromTextual), + }, "long.timestamp-millis": { typeName: &name{"long.timestamp-millis", nullNamespace}, schemaOriginal: "long", diff --git a/go.mod b/go.mod index f282a30..433d8b8 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,6 @@ go 1.12 require ( github.com/golang/snappy v0.0.1 + github.com/google/uuid v1.3.0 github.com/stretchr/testify v1.7.5 ) diff --git a/go.sum b/go.sum index 9532d30..dcc8487 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -10,6 +12,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/logical_type.go b/logical_type.go index 80e9dc2..5f2c046 100644 --- a/logical_type.go +++ b/logical_type.go @@ -16,6 +16,8 @@ import ( "regexp" "strings" "time" + + "github.com/google/uuid" ) type toNativeFn func([]byte) (interface{}, []byte, error) @@ -23,6 +25,42 @@ type fromNativeFn func([]byte, interface{}) ([]byte, error) var reFromPattern = make(map[string]*regexp.Regexp) +// /////////////////////////////////////////////////////////////////////////////////////////// +// uuid logical-type - string - to/from string, string +// /////////////////////////////////////////////////////////////////////////////////////////// +func nativeFromUUID(fn toNativeFn) toNativeFn { + return func(bytes []byte) (interface{}, []byte, error) { + l, b, err := fn(bytes) + if err != nil { + return l, b, err + } + + switch val := l.(type) { + case string: + if _, err := uuid.Parse(val); err != nil { + return l, b, fmt.Errorf("cannot transform native uuid, expected valid string uuid, received %q", val) + } + return val, b, nil + default: + return l, b, fmt.Errorf("cannot transform native uuid, expected string, received %T", l) + } + } +} + +func uuidFromNative(fn fromNativeFn) fromNativeFn { + return func(b []byte, d interface{}) ([]byte, error) { + switch val := d.(type) { + case string: + if _, err := uuid.Parse(val); err != nil { + return nil, fmt.Errorf("cannot transform to binary uuid, expected valid uuid, received %q", val) + } + return fn(b, val) + default: + return nil, fmt.Errorf("cannot transform to binary uuid, expected string, received %T", d) + } + } +} + // //////////////////////////////////////////////////////////////////////////////////////////// // date logical type - to/from time.Time, time.UTC location // //////////////////////////////////////////////////////////////////////////////////////////// diff --git a/logical_type_test.go b/logical_type_test.go index 20f7903..6983bbc 100644 --- a/logical_type_test.go +++ b/logical_type_test.go @@ -40,6 +40,13 @@ func TestLongLogicalTypeFallback(t *testing.T) { testBinaryCodecPass(t, schema, 12345, []byte("\xf2\xc0\x01")) } +func TestUUIDLogicalTypeEncode(t *testing.T) { + schema := `{"type": "string", "logicalType": "uuid"}` + testBinaryDecodeFail(t, schema, []byte(""), "short buffer") + testBinaryEncodeFail(t, schema, "test", "cannot transform to binary uuid, expected valid uuid, received \"test\"") + testBinaryCodecPass(t, schema, "fec2c005-8c57-44c0-89bd-b39ab94afc7e", []byte("\x48\x66\x65\x63\x32\x63\x30\x30\x35\x2d\x38\x63\x35\x37\x2d\x34\x34\x63\x30\x2d\x38\x39\x62\x64\x2d\x62\x33\x39\x61\x62\x39\x34\x61\x66\x63\x37\x65")) +} + func TestTimeStampMillisLogicalTypeEncode(t *testing.T) { schema := `{"type": "long", "logicalType": "timestamp-millis"}` testBinaryDecodeFail(t, schema, []byte(""), "short buffer")