refid

package module
v1.0.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 7, 2023 License: MIT Imports: 12 Imported by: 1

README

refid

Build Status GoDoc Go Report Card License

About

A refid (short for Reference Identifier) is a unique identifier, similar to UUIDv7, with a few difference.

There are two types of refids: TimePrefixed and RandomPrefixed.

TimePrefixed (type:0x00)
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           unix_ts_ms                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           unix_ts_ms          |    rand_a   |t|      tag      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             rand_b                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             rand_b                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
unix_ts_ms:
    48 bits big-endian unsigned number of Unix epoch timestamp milliseconds.
    (2284 years worth... until about year 4200 or so)
rand_a:
    7 bits random pad. fill with crypto/rand random.
t:
    1 bit for type. TimePrefixed (type:0)
tag:
    8 bits tag. 255 separate tags (0 being untagged).
rand_b:
    64 bits random pad. fill with crypto/rand random.
RandomPrefixed (type:0x01)
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             rand_a                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    rand_a                   |t|      tag      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             rand_b                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             rand_b                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
rand_a:
    55 bits random pad. fill with crypto/rand random.
t:
    1 bit for type. RandomPrefixed (type:1)
tag:
    8 bits tag. 255 separate tags (0 being untagged).
rand_b:
    64 bits random pad. fill with crypto/rand random.

Features

General:

  • tagging (support for 255 distinct tags)
  • supports go/sql scanner/valuer
  • multiple encodings supported: native (base32), base64, base16 (hex)
  • similar to UUIDv7, with different tradeoffs

TimePrefix:

  • unix timestamp with millisecond precision
  • sortable, db index friendly
  • Compared to UUIDv7
    • tagging support
    • 48 bits of Unix timestamp milliseconds from epoch (similar to UUIDv7)
    • slightly smaller random section (71 vs 74 bits), though still good collision resistance
    • not a standard

RandomPrefix:

  • not sortable, not db index friendly
  • Compared to UUIDv7
    • tagging support
    • slightly smaller random section (119 vs 122 bits), though still good collision resistance
    • not a standard

Non-Features

  • refids, like UUIDs, do not internally perform any signature verification. If the validity of the encoded timestamp and tag are required for any secure operations, the refid SHOULD be externally verified before parsing/decoding.
    An example of this could be a wrapping encoder/decoder doing hmac signing and verification.

Inspirations

Installation

go get -u github.com/dropwhile/refid

Usage

Simple
// generate a TimePrefixed refid
rID, err := refid.New()
// generate a TimePrefixed refid (or panic)
rID = refid.Must(refid.New())
// generate a RandomPrefixed refid (or panic)
rID = refid.Must(refid.NewRandom())

// encoding...
// encode to native encoding (base32 with Crockford alphabet)
s := rID.String() // "0r326xw2xbpga5tya7px89m7hw"
// encode to base64 encoding
s = rID.ToBase64String() // "BgYjd4Lq7QUXXlHt1CaHjw"
// encode to hex encoding
s = rID.ToHexString() // "0606237782eaed05175e51edd426878f"
// raw bytes
b := rID.Bytes()

// decoding...
// decode from native
rID, err := refid.Parse(s)
// decode from base64
rID, err = refid.FromBase64String(s)
// decode from hex
rID, err = refid.FromHexString(s)

// get the time out of a TimePrefixed refid (as a time.Time)
var ts time.Time = rID.Time()
Tagging

Simple tagging usage:

myTag := 2

// generate a refid with tag set to 1
rID = refid.Must(refid.NewTagged(1))
// you can also set it manually after generation
rID.SetTag(myTag)
// check if it is tagged
rID.Tagged() // true
// check if it has a specific tag
rID.HasTag(1) // false
rID.HasTag(2) // true


s := rID.String()
// require desired tag or fail parsing
r, err := refid.ParseTagged(1, s) // err != nil here, as refid was tagged 2
r, err = refid.ParseTagged(2, s) // err == nil here, as refid was tagged 2
What use is tagging?

Tag support ensures that a refid of a certain tag type can be made distinct from other refids -- those of a different tag type, or those with no tag type.

A hypothetical example is a refid url paramater for a type named "author", can be enforced as invalid when someone attempts to supply it as input for a different refid url parameter for a type named "book".

Making tagging usage easier with refid.IDTagger:

// AuthorID ensures it will only succesfully generate and parse tag=2 refids
AuthorIDT := refid.IDTagger(2)
// BookID ensures it will only succesfully generate and parse tag=3 refids
BookIDT := refid.IDTagger(3)

authorID := refid.Must(AuthorIDT.New()) // generated with a tag of 2
authorID.HasTag(2) // true
bookID := refid.Must(BookIDT.New()) // generated with a tag of 3
bookID.HasTag(3) // true

r, err := AuthorIDT.Parse(authorID.String()) // succeeds; err == nil
r, err = bookIDT.Parse(authorID.String()) // fails; err != nil
Tagging with Custom Types

A more complicated tagging example using embedded types:

import (
    "github.com/dropwhile/refid"
    "github.com/dropwhile/refid/reftag"
)

type NoteID struct {
    // the reftag.IDt* types are generated to provide a handy means to
    // wrap with your own struct type, to provide type constraints in function
    // parameters and struct members
    reftag.IDt8
}

// some helpful shortcut function aliases
var (
    // generate a new time-prefixed refid of tag type 8
    NewNoteID       = reftag.New[NoteID]
    // generate a new random-prefixed refid of tag type 8
    NewRandomNoteID = reftag.NewRandom[NoteID]
    // handy matcher for sql mock libraries (gomock, pgxmock, etc)
    NoteIDMatcher   = reftag.NewMatcher[NoteID]()
    // some handy parsing aliases
    NoteIDFromBytes = reftag.FromBytes[NoteID]
    ParseNoteID     = reftag.Parse[NoteID]
)

func ParseNote(ctx context.Context, db dbHandle, noteStr NoteID) (*NoteID, error) {
    noteID, err := ParseNoteID(noteStr)
    // error will be non-nil if the tag in the RefID does not match the expectation (`8`)
    ...
    return NoteID, err
}

func DBLookupNote(ctx context.Context, db dbHandle, noteID NoteID) (*DBNote, error) {
    // noteID is now a compile time checked type ensuring that RefIDs of a different
    // tag are not accidentally allowed.
    ...
}

refidtool CLI utility

Installation:

go install github.com/dropwhile/refid/cmd/refidtool@latest
# generate a refi.ID with a tag of 5
% refidtool generate -t 5
native enc:   0r326xw2xbpga5tya7px89m7hw
hex enc:      0606237782eaed05175e51edd426878f
base64 enc:   BgYjd4Lq7QUXXlHt1CaHjw
tag value:    5
type:         TimePrefixed
time(string): 2023-09-24T23:47:38.954477Z
time(millis): 1695599258954477

# generate a refid with a tag of 5, and only output the native(base32) encoding
% refidtool generate -t 5 -o native
0r34ky6h51r012an8skhbsvxt0

# generate a refid with a tag of 5, and only output the hex encoding
% refidtool generate -t 5 -o hex
060649f82794f10039169e91d0696763

# generate a random-prefixed refid with a tag of 4, and only output the base64 encoding
% refidtool generate -r -t 4 -o base64
DxBK5ksxywRfCMUUTIw2mw

# genrate a refid with a tag of 2, at a specific timestamp
% refidtool generate -t 2 -w "2023-01-01T00:00:11.123456Z"
native enc:   0qrjh15pzc004nzrkbpcp2v0wm
hex enc:      05f12884b6fb000257f89aeccb0b60e5
base64 enc:   BfEohLb7AAJX-Jrsywtg5Q
tag value:    2
type:         TimePrefixed
time(string): 2023-01-01T00:00:11.123456Z
time(millis): 1672531211123456

# decode a refid and display
% refidtool decode 0qrjh15pzc004nzrkbpcp2v0wm
native enc:   0qrjh15pzc004nzrkbpcp2v0wm
hex enc:      05f12884b6fb000257f89aeccb0b60e5
base64 enc:   BfEohLb7AAJX-Jrsywtg5Q
tag value:    2
type:         TimePrefixed
time(string): 2023-01-01T00:00:11.123456Z
time(millis): 1672531211123456

# here is what a random-prefixed refid looks like
# note the time is the zero value for time.Time
% refidtool parse DxBK5ksxywRfCMUUTIw2mw
native enc:   1w84nsjb675g8qr8rma4s31pkc
hex enc:      0f104ae64b31cb045f08c5144c8c369b
base64 enc:   DxBK5ksxywRfCMUUTIw2mw
tag value:    4
type:         RandomPrefixed
time(string): 1970-01-01T00:00:00Z
time(millis): 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (

	// Nil is the nil ID, that has all 128 bits set to zero.
	Nil = ID{}
)

Functions

func Must

func Must[T any](r T, err error) T

Must is a helper that wraps a call to a function returning (any, error) and panics if the error is non-nil. It is intended for use in variable initializations such as

var (
	refA = refid.Must(refid.New())
	refB = refid.Must(refid.NewTagged(2))
	refC = refid.Must(refid.Parse("0r2nbq0wqhjg186167t0gcd1gw"))
	refD = refid.Must(refid.ParseTagged("0r2nbq0wqhjg186167t0gcd1gw", 2))
)

Types

type AnyMatcher

type AnyMatcher struct {
	// contains filtered or unexported fields
}

A matcher that supports the following interfaces:

func MatchAny

func MatchAny(tag byte) AnyMatcher

Create a AnyMatcher matcher that matches that matches against a specific Tag. Any valid IDs that do not match the tag specified, will be considered not matching.

If tag is 0, will support matching any ID (tag is then ignored)

Example usage:

mock.ExpectQuery("^INSERT INTO some_table (.+)").
 WithArgs(refid.MatchAny(1), 1).
 WillReturnRows(rows)

func (AnyMatcher) Match

func (a AnyMatcher) Match(v interface{}) bool

type ID added in v1.0.6

type ID [size]byte

A ID is a 16 byte identifier that has:

  • tagging support (support for 255 distinct tag types)
  • go/sql scanner/valuer support
  • multiple encodings supported: native (base32), base64, base16 (hex)

func FromBytes

func FromBytes(input []byte) (ID, error)

FromBytes creates a new ID from a byte slice. Returns an error if the slice does not have a length of 16. The bytes are copied from the slice.

func FromString

func FromString(s string) (ID, error)

FromString is an alias of Parse.

func New

func New() (ID, error)

New returns a new TimePrefixed type ID.

If random bytes cannot be generated, it will return an error.

func NewRandom added in v1.0.2

func NewRandom() (ID, error)

NewRandom returns a new RandomPrefixed type ID.

If random bytes cannot be generated, it will return an error.

func NewRandomTagged added in v1.0.2

func NewRandomTagged(tag byte) (ID, error)

NewRandomTagged returns a new RandomPrefixed type ID tagged with tag.

If random bytes cannot be generated, it will return an error.

func NewTagged

func NewTagged(tag byte) (ID, error)

NewTagged returns a new TimePrefixed type ID tagged with tag.

If random bytes cannot be generated, it will return an error.

func Parse

func Parse(s string) (ID, error)

Parse parses a textual ID representation, and returns a ID. Supports parsing the following text formats:

  • native/base32 (Crockford's alphabet)
  • base64
  • base16/hex

Will return an error on parse failure.

func ParseWithRequire added in v1.0.2

func ParseWithRequire(s string, reqs ...Requirement) (ID, error)

ParseWithRequire parses a textual ID representation (same formats as Parse), while additionally requiring each reqs Requirement to pass, and returns a ID.

Returns an error if ID fails to parse or if any of the reqs Requirements fail.

Example:

ParseWithRequire("afd661f4f2tg2vr3dca92qp6k8", HasType(RandomPrefix))

func (ID) Bytes added in v1.0.6

func (r ID) Bytes() []byte

Bytes returns a slice of a copy of the current ID underlying data.

func (*ID) ClearTag added in v1.0.6

func (r *ID) ClearTag() *ID

ClearTag clears the ID tag.

func (ID) Equal added in v1.0.6

func (r ID) Equal(other ID) bool

Equal compares a ID to another ID to see if they have the same underlying bytes.

func (ID) Format added in v1.0.6

func (r ID) Format(f fmt.State, c rune)

Format implements the fmt.Formatter interface.

func (ID) HasTag added in v1.0.6

func (r ID) HasTag(tag byte) bool

IsTagged reports whether the ID is tagged and if so, if it is tagged with tag.

func (ID) HasType added in v1.0.6

func (r ID) HasType(t Type) bool

HasType reports whether the [RefId] is of type t.

func (ID) IsNil added in v1.0.6

func (r ID) IsNil() bool

IsNil reports if the ID is the nil value ID.

func (ID) IsTagged added in v1.0.6

func (r ID) IsTagged() bool

IsTagged reports whether the ID is tagged.

func (ID) MarshalBinary added in v1.0.6

func (r ID) MarshalBinary() ([]byte, error)

MarshalBinary implements the encoding.BinaryMarshaler interface.

Purposefully a value receiver for flexibility (from EffectiveGo): "The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.""

func (ID) MarshalJSON added in v1.0.6

func (r ID) MarshalJSON() ([]byte, error)

MarshalJson implements the json.Marshaler interface.

Purposefully a value receiver for flexibility (from EffectiveGo): "The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.""

func (ID) MarshalText added in v1.0.6

func (r ID) MarshalText() ([]byte, error)

MarshalText implements the encoding.TextMarshaler interface.

func (*ID) Scan added in v1.0.6

func (r *ID) Scan(src interface{}) error

Scan implements the sql.Scanner interface. A 16-byte slice will be handled by ID.UnmarshalBinary, while a longer byte slice or a string will be handled by ID.UnmarshalText.

func (*ID) SetTag added in v1.0.6

func (r *ID) SetTag(tag byte) *ID

SetTag sets the ID tag to the specified value.

func (*ID) SetTime added in v1.0.6

func (r *ID) SetTime(ts time.Time) error

SetTime sets the time component of a ID to the time specified by ts.

func (ID) String added in v1.0.6

func (r ID) String() string

String returns the native (base32 w/Crockford alphabet) textual represenation of a ID

func (ID) Tag added in v1.0.6

func (r ID) Tag() byte

Tag returns the current tag of the ID. If the ID is untagged, it will retrun 0.

func (ID) Time added in v1.0.6

func (r ID) Time() time.Time

Time returns the timestamp portion of a ID as a time.Time

func (ID) ToBase32String added in v1.0.6

func (r ID) ToBase32String() string

ToBase32String is an alias of [String]

func (ID) ToBase64String added in v1.0.6

func (r ID) ToBase64String() string

String returns the base64 textual represenation of a ID

func (ID) ToHexString added in v1.0.6

func (r ID) ToHexString() string

String returns the base16/hex textual represenation of a ID

func (ID) ToString added in v1.0.6

func (r ID) ToString() string

ToString is an alias of [String]

func (ID) Type added in v1.0.6

func (r ID) Type() Type

Type returns the type of the ID.

func (*ID) UnmarshalBinary added in v1.0.6

func (r *ID) UnmarshalBinary(data []byte) error

UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. It will return an error if the slice isn't of appropriate size.

func (*ID) UnmarshalJSON added in v1.0.6

func (r *ID) UnmarshalJSON(b []byte) error

UnmarshalJson implements the json.Unmarshaler interface.

func (*ID) UnmarshalText added in v1.0.6

func (r *ID) UnmarshalText(b []byte) error

UnmarshalText implements the encoding.TextUnmarshaler interface. It will return an error if the slice isn't of appropriate size.

func (ID) Value added in v1.0.6

func (r ID) Value() (driver.Value, error)

Value implements the sql/driver.Valuer interface.

type NullID added in v1.0.6

type NullID struct {
	ID    ID
	Valid bool
}

NullID can be used with the standard sql package to represent a ID value that can be NULL in the database.

func (NullID) MarshalJSON added in v1.0.6

func (u NullID) MarshalJSON() ([]byte, error)

MarshalJSON marshals the NullID as null or the nested ID

func (*NullID) Scan added in v1.0.6

func (u *NullID) Scan(src interface{}) error

Scan implements the sql.Scanner interface.

func (*NullID) UnmarshalJSON added in v1.0.6

func (u *NullID) UnmarshalJSON(b []byte) error

UnmarshalJSON unmarshals a NullID

func (NullID) Value added in v1.0.6

func (u NullID) Value() (driver.Value, error)

Value implements the sql/driver.Valuer interface.

type NullRefID added in v1.0.1

type NullRefID = NullID

type RefID added in v1.0.1

type RefID = ID

Alias for backwards compat

type Requirement added in v1.0.2

type Requirement func(ID) error

func HasTag added in v1.0.2

func HasTag(tag byte) Requirement

func HasType added in v1.0.2

func HasType(t Type) Requirement

type Tagger

type Tagger byte

A Tagger is a conveniece container for encoding and parsing ID's of a specific tag.

func NewTagger

func NewTagger(tag byte) Tagger

NewTagger returns a new Tagger with tag

func (Tagger) AnyMatcher

func (t Tagger) AnyMatcher() AnyMatcher

AnyMather returns an AnyMatcher, which will match only against a ID tagged with the same tag as the Tagger

func (Tagger) HasCorrectTag

func (t Tagger) HasCorrectTag(r ID) bool

HasTag reports whether a ID is tagged with the same tag as the Tagger

func (Tagger) HasTag

func (t Tagger) HasTag(r ID, tag byte) bool

HasTag reports whether a ID is tagged with a given tag

func (Tagger) IsTagged

func (t Tagger) IsTagged(r ID) bool

IsTagged reports wheater a ID is tagged at all. Note: This only checks that the ID is tagged, not that it is tagged with the same tag as Tagger. For that functionality use Tagger.HasCorrectTag.

func (Tagger) New

func (t Tagger) New() (ID, error)

New generates a new [TimePrefix] type ID with tag set to the tag of the Tagger

func (Tagger) NewRandom added in v1.0.2

func (t Tagger) NewRandom() (ID, error)

NewRandom generates a new [RandomPrefix] type ID with tag set to the tag of the Tagger

func (Tagger) Parse

func (t Tagger) Parse(s string) (ID, error)

Parse parses a ID, additionally enforcing that it is tagged with the same tag as the Tagger

func (Tagger) ParseWithRequire added in v1.0.2

func (t Tagger) ParseWithRequire(s string, reqs ...Requirement) (ID, error)

ParseWithRequire parses a textual ID representation (same formats as Parse), enforcing that it is tagged with the same tag as the Tagger, while additionally requiring each reqs Requirement to pass, and returns a ID.

Returns an error if ID fails to parse, is not tagged with the same tag as Tagger, or if any of the reqs Requirements fail.

Example:

ParseWithRequire("afd661f4f2tg2vr3dca92qp6k8", HasType(RandomPrefix))

func (Tagger) Tag

func (t Tagger) Tag() byte

Tag returns the tag of the Tagger

type Type added in v1.0.2

type Type byte
const (
	TimePrefixed   Type = 0x00
	RandomPrefixed Type = 0x01
)

func (Type) String added in v1.0.2

func (i Type) String() string

Directories

Path Synopsis
cmd
reftool Module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL