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

Add UUID representation for ULID type #92

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Usage: ulid [-hlqz] [-f <format>] [parameters ...]
-h, --help print this help text
-l, --local when parsing, show local time instead of UTC
-q, --quick when generating, use non-crypto-grade entropy
-u, --uuid when parsing or generating, print as UUID string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is a bit ambiguous. First, "UUID string" is I think not sufficiently qualified — the specific format implemented in this PR I think has a more precise name? Second, if the tool can output UUIDs, then I would expect it could also input UUIDs, i.e. I would expect that

$ ulid 01D78XZ44G0000000000000000
$ ulid [something] 0169d1df-9090-0000-0000-000000000000

would parse to the same thing? So like if we do this then --uuid should I guess not be just an expression of output format but also should be able to control parse behavior. Which gets tricky!

Copy link
Author

@elipavlov elipavlov Dec 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. First, "UUID string" is I think not sufficiently qualified — the specific format implemented in this PR I think has a more precise name?

I think we can speak about UUID format, considering ULID library and CLI tool. I'm talking about that:
https://en.wikipedia.org/wiki/Universally_unique_identifier#Format

Yes, there are five (existing) different versions of UUID.
But, my point is that ULID interoperability with UUID should only work with the format, and leave UUID version interpretation to the users' code.

  1. Second, if the tool can output UUIDs, then I would expect it could also input UUID

I agree 100%.
Here I wanted to solve only my particular use-case "generate uuids by ulids".
I will consider this point closer ant try to provide some solution for parsing UUIDs

  1. As for tests - OK, it's not a problem.

  2. As for ambiguity - I rather agree, but i couldn't come up with better flag naming. If you have some thoughts please share them.

Copy link
Author

@elipavlov elipavlov Dec 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4] Ambiguity. My first thought was to add one more variant to the --format flag, but when I looked it closer, I'd figured that the --format flag works with the dates, and also in the parsing function. I'd decided to leave it as is and made another flag.

-z, --zero when generating, fix entropy to all-zeroes
```

Expand All @@ -134,6 +135,10 @@ $ ulid 01D78XZ44G0000000000000000
Sun Mar 31 03:51:23.536 UTC 2019
$ ulid --format=rfc3339 --local 01D78XZ44G0000000000000000
2019-03-31T05:51:23.536+02:00
$ ulid -u 01D78XZ44G0000000000000000
0169d1df-9090-0000-0000-000000000000
$ ulid --uuid
0184ceda-2982-6f83-9645-7fc810743a30
```

## Specification
Expand Down
24 changes: 19 additions & 5 deletions cmd/ulid/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func main() {
local = fs.BoolLong("local", 'l', "when parsing, show local time instead of UTC")
quick = fs.BoolLong("quick", 'q', "when generating, use non-crypto-grade entropy")
zero = fs.BoolLong("zero", 'z', "when generating, fix entropy to all-zeroes")
asUUID = fs.BoolLong("uuid", 'u', "when parsing or generating, print as UUID string")
help = fs.BoolLong("help", 'h', "print this help text")
)
if err := fs.Getopt(os.Args, nil); err != nil {
Expand Down Expand Up @@ -56,13 +57,13 @@ func main() {

switch args := fs.Args(); len(args) {
case 0:
generate(*quick, *zero)
generate(*quick, *zero, *asUUID)
default:
parse(args[0], *local, formatFunc)
parse(args[0], *local, *asUUID, formatFunc)
}
}

func generate(quick, zero bool) {
func generate(quick, zero, asUUID bool) {
entropy := cryptorand.Reader
if quick {
seed := time.Now().UnixNano()
Expand All @@ -79,16 +80,21 @@ func generate(quick, zero bool) {
os.Exit(1)
}

fmt.Fprintf(os.Stdout, "%s\n", id)
printID(id, asUUID)
}

func parse(s string, local bool, f func(time.Time) string) {
func parse(s string, local, asUUID bool, f func(time.Time) string) {
id, err := ulid.Parse(s)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}

if asUUID {
printID(id, true)
return
}

t := ulid.Time(id.Time())
if !local {
t = t.UTC()
Expand All @@ -104,3 +110,11 @@ func (zeroReader) Read(p []byte) (int, error) {
}
return len(p), nil
}

func printID(id ulid.ULID, asUUID bool) {
if !asUUID {
fmt.Fprintf(os.Stdout, "%s\n", id)
return
}
fmt.Fprintf(os.Stdout, "%s\n", id.UUIDString())
}
20 changes: 20 additions & 0 deletions ulid.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"bytes"
"database/sql/driver"
"encoding/binary"
"encoding/hex"
"errors"
"io"
"math"
Expand Down Expand Up @@ -282,6 +283,25 @@ func (id ULID) String() string {
return string(ulid)
}

// UUIDString returns an UUID representation of the ULID
// (36 characters, 32 hex encoded repr plus 4 dashes)
// e.g. 01AN4Z07BY79KA1307SR9X4MV3 will be 015549f0-1d7e-3a66-a08c-07ce13d25363
func (id ULID) UUIDString() string {
var buf [36]byte

hex.Encode(buf[:], id[:4])
buf[8] = '-'
hex.Encode(buf[9:13], id[4:6])
buf[13] = '-'
hex.Encode(buf[14:18], id[6:8])
buf[18] = '-'
hex.Encode(buf[19:23], id[8:10])
buf[23] = '-'
hex.Encode(buf[24:], id[10:])

return string(buf[:])
}

// MarshalBinary implements the encoding.BinaryMarshaler interface by
// returning the ULID as a byte slice.
func (id ULID) MarshalBinary() ([]byte, error) {
Expand Down