142 lines
3.4 KiB
Go
142 lines
3.4 KiB
Go
package influxdb
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"reflect"
|
|
"strconv"
|
|
"unsafe"
|
|
)
|
|
|
|
// IDLength is the exact length a string (or a byte slice representing it) must have in order to be decoded into a valid ID.
|
|
const IDLength = 16
|
|
|
|
var (
|
|
// ErrInvalidID signifies invalid IDs.
|
|
ErrInvalidID = &Error{
|
|
Code: EInvalid,
|
|
Msg: "invalid ID",
|
|
}
|
|
|
|
// ErrInvalidIDLength is returned when an ID has the incorrect number of bytes.
|
|
ErrInvalidIDLength = &Error{
|
|
Code: EInvalid,
|
|
Msg: "id must have a length of 16 bytes",
|
|
}
|
|
)
|
|
|
|
// ID is a unique identifier.
|
|
//
|
|
// Its zero value is not a valid ID.
|
|
type ID uint64
|
|
|
|
// IDGenerator represents a generator for IDs.
|
|
type IDGenerator interface {
|
|
// ID creates unique byte slice ID.
|
|
ID() ID
|
|
}
|
|
|
|
// IDFromString creates an ID from a given string.
|
|
//
|
|
// It errors if the input string does not match a valid ID.
|
|
func IDFromString(str string) (*ID, error) {
|
|
var id ID
|
|
err := id.DecodeFromString(str)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &id, nil
|
|
}
|
|
|
|
// InvalidID returns a zero ID.
|
|
func InvalidID() ID {
|
|
return 0
|
|
}
|
|
|
|
// Decode parses b as a hex-encoded byte-slice-string.
|
|
//
|
|
// It errors if the input byte slice does not have the correct length
|
|
// or if it contains all zeros.
|
|
func (i *ID) Decode(b []byte) error {
|
|
if len(b) != IDLength {
|
|
return ErrInvalidIDLength
|
|
}
|
|
|
|
res, err := strconv.ParseUint(unsafeBytesToString(b), 16, 64)
|
|
if err != nil {
|
|
return ErrInvalidID
|
|
}
|
|
|
|
if *i = ID(res); !i.Valid() {
|
|
return ErrInvalidID
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func unsafeBytesToString(in []byte) string {
|
|
src := *(*reflect.SliceHeader)(unsafe.Pointer(&in))
|
|
dst := reflect.StringHeader{
|
|
Data: src.Data,
|
|
Len: src.Len,
|
|
}
|
|
s := *(*string)(unsafe.Pointer(&dst))
|
|
return s
|
|
}
|
|
|
|
// DecodeFromString parses s as a hex-encoded string.
|
|
func (i *ID) DecodeFromString(s string) error {
|
|
return i.Decode([]byte(s))
|
|
}
|
|
|
|
// Encode converts ID to a hex-encoded byte-slice-string.
|
|
//
|
|
// It errors if the receiving ID holds its zero value.
|
|
func (i ID) Encode() ([]byte, error) {
|
|
if !i.Valid() {
|
|
return nil, ErrInvalidID
|
|
}
|
|
|
|
b := make([]byte, hex.DecodedLen(IDLength))
|
|
binary.BigEndian.PutUint64(b, uint64(i))
|
|
|
|
dst := make([]byte, hex.EncodedLen(len(b)))
|
|
hex.Encode(dst, b)
|
|
return dst, nil
|
|
}
|
|
|
|
// Valid checks whether the receiving ID is a valid one or not.
|
|
func (i ID) Valid() bool {
|
|
return i != 0
|
|
}
|
|
|
|
// String returns the ID as a hex encoded string.
|
|
//
|
|
// Returns an empty string in the case the ID is invalid.
|
|
func (i ID) String() string {
|
|
enc, _ := i.Encode()
|
|
return string(enc)
|
|
}
|
|
|
|
// GoString formats the ID the same as the String method.
|
|
// Without this, when using the %#v verb, an ID would be printed as a uint64,
|
|
// so you would see e.g. 0x2def021097c6000 instead of 02def021097c6000
|
|
// (note the leading 0x, which means the former doesn't show up in searches for the latter).
|
|
func (i ID) GoString() string {
|
|
return `"` + i.String() + `"`
|
|
}
|
|
|
|
// MarshalText encodes i as text.
|
|
// Providing this method is a fallback for json.Marshal,
|
|
// with the added benefit that IDs encoded as map keys will be the expected string encoding,
|
|
// rather than the effective fmt.Sprintf("%d", i) that json.Marshal uses by default for integer types.
|
|
func (i ID) MarshalText() ([]byte, error) {
|
|
return i.Encode()
|
|
}
|
|
|
|
// UnmarshalText decodes i from a byte slice.
|
|
// Providing this method is also a fallback for json.Unmarshal,
|
|
// also relevant when IDs are used as map keys.
|
|
func (i *ID) UnmarshalText(b []byte) error {
|
|
return i.Decode(b)
|
|
}
|