Skip to content
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

Implement fmt.Formatter #72

Merged
merged 8 commits into from Mar 20, 2019
61 changes: 61 additions & 0 deletions uuid.go
Expand Up @@ -33,6 +33,8 @@ import (
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"strings"
"time"
)

Expand Down Expand Up @@ -156,6 +158,65 @@ func (u UUID) String() string {
return string(buf)
}

// Format implements fmt.Formatter for UUID values.
//
// The behavior is as follows:
// The 'x' and 'X' verbs output only the hex digits of the UUID, using a-f for 'x' and A-F for 'X'.
// The 'v', '+v', 's' and 'q' verbs return the canonical RFC-4122 string representation.
// The 'S' verb returns the RFC-4122 format, but with capital hex digits.
// The '#v' verb returns the "Go syntax" representation, which is a 16 byte array initializer.
// All other verbs not handled directly by the fmt package (like '%p') are unsupported and will return
// "%!verb(uuid.UUID=value)" as recommended by the fmt package.
func (u UUID) Format(f fmt.State, c rune) {
switch c {
case 'x', 'X':
s := hex.EncodeToString(u.Bytes())
if c == 'X' {
s = strings.Map(toCapitalHexDigits, s)
}
_, _ = io.WriteString(f, s)
tariq1890 marked this conversation as resolved.
Show resolved Hide resolved
case 'v':
var s string
if f.Flag('#') {
s = fmt.Sprintf("%#v", [Size]byte(u))
} else {
s = u.String()
}
_, _ = io.WriteString(f, s)
case 's', 'S':
s := u.String()
if c == 'S' {
s = strings.Map(toCapitalHexDigits, s)
}
_, _ = io.WriteString(f, s)
case 'q':
acln0 marked this conversation as resolved.
Show resolved Hide resolved
_, _ = io.WriteString(f, `"`+u.String()+`"`)
default:
// invalid/unsupported format verb
fmt.Fprintf(f, "%%!%c(uuid.UUID=%s)", c, u.String())
}
}

func toCapitalHexDigits(ch rune) rune {
// convert a-f hex digits to A-F
switch ch {
case 'a':
return 'A'
case 'b':
return 'B'
case 'c':
return 'C'
case 'd':
return 'D'
case 'e':
return 'E'
case 'f':
return 'F'
default:
return ch
}
}

// SetVersion sets the version bits.
func (u *UUID) SetVersion(v byte) {
u[6] = (u[6] & 0x0f) | (v << 4)
Expand Down
38 changes: 38 additions & 0 deletions uuid_test.go
Expand Up @@ -35,6 +35,7 @@ func TestUUID(t *testing.T) {
t.Run("Variant", testUUIDVariant)
t.Run("SetVersion", testUUIDSetVersion)
t.Run("SetVariant", testUUIDSetVariant)
t.Run("Format", testUUIDFormat)
}

func testUUIDBytes(t *testing.T) {
Expand Down Expand Up @@ -114,6 +115,43 @@ func testUUIDSetVariant(t *testing.T) {
}
}

func testUUIDFormat(t *testing.T) {
val := Must(FromString("12345678-90ab-cdef-1234-567890abcdef"))
tests := []struct {
u UUID
f string
want string
}{
{u: val, f: "%s", want: "12345678-90ab-cdef-1234-567890abcdef"},
{u: val, f: "%S", want: "12345678-90AB-CDEF-1234-567890ABCDEF"},
{u: val, f: "%q", want: `"12345678-90ab-cdef-1234-567890abcdef"`},
{u: val, f: "%x", want: "1234567890abcdef1234567890abcdef"},
{u: val, f: "%X", want: "1234567890ABCDEF1234567890ABCDEF"},
{u: val, f: "%v", want: "12345678-90ab-cdef-1234-567890abcdef"},
{u: val, f: "%+v", want: "12345678-90ab-cdef-1234-567890abcdef"},
{u: val, f: "%#v", want: "[16]uint8{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}"},
{u: val, f: "%T", want: "uuid.UUID"},
{u: val, f: "%t", want: "%!t(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"},
{u: val, f: "%b", want: "%!b(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"},
{u: val, f: "%c", want: "%!c(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"},
{u: val, f: "%d", want: "%!d(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"},
{u: val, f: "%e", want: "%!e(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"},
{u: val, f: "%E", want: "%!E(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"},
{u: val, f: "%f", want: "%!f(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"},
{u: val, f: "%F", want: "%!F(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"},
{u: val, f: "%g", want: "%!g(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"},
{u: val, f: "%G", want: "%!G(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"},
{u: val, f: "%o", want: "%!o(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"},
{u: val, f: "%U", want: "%!U(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"},
}
for _, tt := range tests {
got := fmt.Sprintf(tt.f, tt.u)
if tt.want != got {
t.Errorf("Format(\"%s\") got %s, want %s", tt.f, got, tt.want)
dylan-bourque marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

func TestMust(t *testing.T) {
sentinel := fmt.Errorf("uuid: sentinel error")
defer func() {
Expand Down