diff --git a/go.mod b/go.mod index 81af4658e..3dc147bed 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ require ( github.com/cockroachdb/apd/v2 v2.0.1 github.com/emicklei/proto v1.6.15 github.com/google/go-cmp v0.4.0 + github.com/google/uuid v1.2.0 github.com/kr/pretty v0.1.0 github.com/kylelemons/godebug v1.1.0 github.com/lib/pq v1.0.0 // indirect diff --git a/go.sum b/go.sum index 8c5d68a83..cfe3f2bd9 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= diff --git a/pkg/register.go b/pkg/register.go index 69bdb0cb3..fdfc1c35e 100644 --- a/pkg/register.go +++ b/pkg/register.go @@ -45,4 +45,5 @@ import ( _ "cuelang.org/go/pkg/tool/file" _ "cuelang.org/go/pkg/tool/http" _ "cuelang.org/go/pkg/tool/os" + _ "cuelang.org/go/pkg/uuid" ) diff --git a/pkg/uuid/pkg.go b/pkg/uuid/pkg.go new file mode 100644 index 000000000..ae2be7e79 --- /dev/null +++ b/pkg/uuid/pkg.go @@ -0,0 +1,159 @@ +// Code generated by go generate. DO NOT EDIT. + +//go:generate rm pkg.go +//go:generate go run ../gen/gen.go + +package uuid + +import ( + "cuelang.org/go/internal/core/adt" + "cuelang.org/go/pkg/internal" +) + +func init() { + internal.Register("uuid", pkg) +} + +var _ = adt.TopKind // in case the adt package isn't used + +var pkg = &internal.Package{ + Native: []*internal.Builtin{{ + Name: "Valid", + Params: []internal.Param{ + {Kind: adt.StringKind}, + }, + Result: adt.BottomKind, + Func: func(c *internal.CallCtxt) { + s := c.String(0) + if c.Do() { + c.Ret = Valid(s) + } + }, + }, { + Name: "Parse", + Params: []internal.Param{ + {Kind: adt.StringKind}, + }, + Result: adt.StringKind, + Func: func(c *internal.CallCtxt) { + s := c.String(0) + if c.Do() { + c.Ret, c.Err = Parse(s) + } + }, + }, { + Name: "ToString", + Params: []internal.Param{ + {Kind: adt.StringKind}, + }, + Result: adt.StringKind, + Func: func(c *internal.CallCtxt) { + x := c.String(0) + if c.Do() { + c.Ret = ToString(x) + } + }, + }, { + Name: "URN", + Params: []internal.Param{ + {Kind: adt.StringKind}, + }, + Result: adt.StringKind, + Func: func(c *internal.CallCtxt) { + x := c.String(0) + if c.Do() { + c.Ret, c.Err = URN(x) + } + }, + }, { + Name: "FromInt", + Params: []internal.Param{ + {Kind: adt.IntKind}, + }, + Result: adt.StringKind, + Func: func(c *internal.CallCtxt) { + i := c.BigInt(0) + if c.Do() { + c.Ret, c.Err = FromInt(i) + } + }, + }, { + Name: "ToInt", + Params: []internal.Param{ + {Kind: adt.StringKind}, + }, + Result: adt.IntKind, + Func: func(c *internal.CallCtxt) { + x := c.String(0) + if c.Do() { + c.Ret = ToInt(x) + } + }, + }, { + Name: "Variant", + Params: []internal.Param{ + {Kind: adt.StringKind}, + }, + Result: adt.IntKind, + Func: func(c *internal.CallCtxt) { + x := c.String(0) + if c.Do() { + c.Ret, c.Err = Variant(x) + } + }, + }, { + Name: "Version", + Params: []internal.Param{ + {Kind: adt.StringKind}, + }, + Result: adt.IntKind, + Func: func(c *internal.CallCtxt) { + x := c.String(0) + if c.Do() { + c.Ret, c.Err = Version(x) + } + }, + }, { + Name: "SHA1", + Params: []internal.Param{ + {Kind: adt.StringKind}, + {Kind: adt.BytesKind | adt.StringKind}, + }, + Result: adt.StringKind, + Func: func(c *internal.CallCtxt) { + space, data := c.String(0), c.Bytes(1) + if c.Do() { + c.Ret, c.Err = SHA1(space, data) + } + }, + }, { + Name: "MD5", + Params: []internal.Param{ + {Kind: adt.StringKind}, + {Kind: adt.BytesKind | adt.StringKind}, + }, + Result: adt.StringKind, + Func: func(c *internal.CallCtxt) { + space, data := c.String(0), c.Bytes(1) + if c.Do() { + c.Ret, c.Err = MD5(space, data) + } + }, + }}, + CUE: `{ + ns: { + DNS: "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + URL: "6ba7b811-9dad-11d1-80b4-00c04fd430c8" + OID: "6ba7b812-9dad-11d1-80b4-00c04fd430c8" + X500: "6ba7b814-9dad-11d1-80b4-00c04fd430c8" + Nil: "00000000-0000-0000-0000-000000000000" + } + variants: { + Invalid: 0 + RFC4122: 1 + Reserved: 2 + Microsoft: 3 + Future: 4 + } +}`, +} diff --git a/pkg/uuid/testdata/uuid.txtar b/pkg/uuid/testdata/uuid.txtar new file mode 100644 index 000000000..bcae1eb93 --- /dev/null +++ b/pkg/uuid/testdata/uuid.txtar @@ -0,0 +1,69 @@ +-- in.cue -- +import "uuid" + +sha1: a: uuid.SHA1(uuid.ns.DNS, "cuelang.org") + +md5: a: uuid.MD5(uuid.ns.URL, "https://cuelang.org") + +valid: { + a: uuid.Valid + a: "052ef62d-7223-58b6-a551-c1deee46d401" +} + +invalid: { + a: uuid.Valid + a: "052EF62D-7223-58B6-A551-C1DEEE46D401" + + b: uuid.Valid + b: "052ef62d_7223_58b6_a551_c1deee46d401" +} + +parse: a: uuid.Parse("052ef62d722358b6a551c1deee46d401") + +fromInt: a: uuid.FromInt(0x052ef62d_7223_58b6_a551_c1deee46d401) + +variant: a: uuid.Variant(sha1.a) +version: a: uuid.Version(sha1.a) +urn: a: uuid.URN(sha1.a) +toInt: a: uuid.ToInt(sha1.a) + +-- out/uuid -- +Errors: +invalid.a: invalid value "052EF62D-7223-58B6-A551-C1DEEE46D401" (does not satisfy uuid.Valid): invalid UUID "052EF62D-7223-58B6-A551-C1DEEE46D401": + ./in.cue:14:8 +invalid.b: invalid value "052ef62d_7223_58b6_a551_c1deee46d401" (does not satisfy uuid.Valid): invalid UUID "052ef62d_7223_58b6_a551_c1deee46d401": + ./in.cue:17:8 + +Result: +sha1: { + a: "052ef62d-7223-58b6-a551-c1deee46d401" +} +md5: { + a: "d891d69e-ae5c-39e0-9ead-164abd207f1f" +} +valid: { + a: "052ef62d-7223-58b6-a551-c1deee46d401" +} +invalid: { + a: _|_ // invalid.a: invalid value "052EF62D-7223-58B6-A551-C1DEEE46D401" (does not satisfy uuid.Valid): invalid.a: invalid UUID "052EF62D-7223-58B6-A551-C1DEEE46D401" + b: _|_ // invalid.b: invalid value "052ef62d_7223_58b6_a551_c1deee46d401" (does not satisfy uuid.Valid): invalid.b: invalid UUID "052ef62d_7223_58b6_a551_c1deee46d401" +} +parse: { + a: "052ef62d-7223-58b6-a551-c1deee46d401" +} +fromInt: { + a: "052ef62d-7223-58b6-a551-c1deee46d401" +} +variant: { + a: 1 +} +version: { + a: 5 +} +urn: { + a: "urn:uuid:052ef62d-7223-58b6-a551-c1deee46d401" +} +toInt: { + a: 93651793875903522077150095950593860979557386807737776869062002310283964632724204171313 +} + diff --git a/pkg/uuid/uuid.cue b/pkg/uuid/uuid.cue new file mode 100644 index 000000000..4efd256ce --- /dev/null +++ b/pkg/uuid/uuid.cue @@ -0,0 +1,35 @@ +// Copyright 2021 CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package uuid + +// Predefined namespaces +ns: { + DNS: "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + URL: "6ba7b811-9dad-11d1-80b4-00c04fd430c8" + OID: "6ba7b812-9dad-11d1-80b4-00c04fd430c8" + X500: "6ba7b814-9dad-11d1-80b4-00c04fd430c8" + Nil: "00000000-0000-0000-0000-000000000000" +} + +// Invalid UUID +variants: Invalid: 0 +// The variant specified in RFC4122 +variants: RFC4122: 1 +// Reserved, NCS backward compatibility. +variants: Reserved: 2 +// Reserved, Microsoft Corporation backward compatibility. +variants: Microsoft: 3 +// Reserved for future definition. +variants: Future: 4 diff --git a/pkg/uuid/uuid.go b/pkg/uuid/uuid.go new file mode 100644 index 000000000..9d5551798 --- /dev/null +++ b/pkg/uuid/uuid.go @@ -0,0 +1,121 @@ +// Copyright 2021 CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package uuid defines functionality for creating UUIDs as defined in RFC 4122. +// +// Currently only Version 5 (SHA1) and Version 3 (MD5) are supported. +package uuid + +import ( + "fmt" + "math/big" + "regexp" + + "github.com/google/uuid" +) + +var valid = regexp.MustCompile( + "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$") + +// Valid can be used to define a valid Valid. +func Valid(s string) error { + if !valid.MatchString(string(s)) { + return fmt.Errorf("invalid UUID %q", s) + } + return nil +} + +// Parse decodes s into a UUID or returns an error. Both the standard UUID forms +// of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the +// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex +// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. +func Parse(s string) (string, error) { + x, err := uuid.Parse(s) + return string(x.String()), err +} + +// String represents a 128-bit UUID value as a string. +func ToString(x string) string { + return string(x) +} + +// URN reports the canonical URN of a UUID. +func URN(x string) (string, error) { + u, err := uuid.Parse(string(x)) + if err != nil { + return "", err + } + return u.URN(), nil +} + +// FromInt creates a UUID from an integer. +// +// DNS: uuid.FromInt(0x6ba7b810_9dad_11d1_80b4_00c04fd430c8) +// +func FromInt(i *big.Int) (string, error) { + // must be uint128 + var buf [16]byte + b := i.Bytes() + if len(b) < 16 { + copy(buf[16-len(b):], b) + b = buf[:] + } + u, err := uuid.FromBytes(b) + return string(u.String()), err +} + +// ToInt represents a UUID string as a 128-bit value. +func ToInt(x string) *big.Int { + var i big.Int + i.SetBytes([]byte(x[:])) + return &i +} + +// Variant reports the UUID variant. +func Variant(x string) (int, error) { + u, err := uuid.Parse(string(x)) + if err != nil { + return 0, err + } + return int(u.Variant()), nil +} + +// Version reports the UUID version. +func Version(x string) (int, error) { + u, err := uuid.Parse(string(x)) + if err != nil { + return 0, err + } + return int(u.Version()), nil +} + +// SHA1 generates a version 5 UUID based on the supplied name space and data. +func SHA1(space string, data []byte) (string, error) { + u, err := uuid.Parse(string(space)) + if err != nil { + return "", err + } + return string(uuid.NewSHA1(u, data).String()), nil +} + +// MD5 generates a version 3 UUID based on the supplied name space and data. +// Use SHA1 instead if you can. +func MD5(space string, data []byte) (string, error) { + u, err := uuid.Parse(string(space)) + if err != nil { + return "", err + } + return string(uuid.NewMD5(u, data).String()), nil +} diff --git a/pkg/uuid/uuid_test.go b/pkg/uuid/uuid_test.go new file mode 100644 index 000000000..70c2dc1c6 --- /dev/null +++ b/pkg/uuid/uuid_test.go @@ -0,0 +1,25 @@ +// Copyright 2020 CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package uuid_test + +import ( + "testing" + + "cuelang.org/go/pkg/internal/builtintest" +) + +func TestBuiltin(t *testing.T) { + builtintest.Run("uuid", t) +}