Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
200 lines
3.0 KiB
Go
200 lines
3.0 KiB
Go
package id
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
)
|
|
|
|
type (
|
|
ID struct {
|
|
Num uint64
|
|
Str [maxStrIdentifierLength]rune
|
|
}
|
|
)
|
|
|
|
const (
|
|
maxStrIdentifierLength = 36
|
|
)
|
|
|
|
var (
|
|
isNumberRegex = regexp.MustCompile(`^(0|[1-9][0-9]{0,19})$`)
|
|
)
|
|
|
|
func MustNumID(n uint64) ID {
|
|
id, err := NumID(n)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to create NumID: %v", err))
|
|
}
|
|
return id
|
|
}
|
|
|
|
func NumID(n uint64) (ID, error) {
|
|
return ID{Num: n}, nil
|
|
}
|
|
|
|
func MustStrID(s string) ID {
|
|
id, err := StrID(s)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to create StrID: %v", err))
|
|
}
|
|
return id
|
|
}
|
|
|
|
func StrID(s string) (ID, error) {
|
|
if len(s) > maxStrIdentifierLength {
|
|
return ID{}, fmt.Errorf("string too long: %s", s)
|
|
}
|
|
|
|
var str [maxStrIdentifierLength]rune
|
|
for i, r := range s {
|
|
if i < maxStrIdentifierLength {
|
|
str[i] = r
|
|
}
|
|
}
|
|
return ID{Str: str}, nil
|
|
}
|
|
|
|
func MustByteID(data []byte) (id ID) {
|
|
id, err := ByteID(data)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to parse ID: %v", err))
|
|
}
|
|
|
|
return id
|
|
}
|
|
|
|
func ByteID(data []byte) (id ID, err error) {
|
|
// numeric
|
|
if isNumberRegex.MatchString(string(data)) {
|
|
num, err := strconv.ParseUint(string(data), 10, 64)
|
|
if err != nil {
|
|
return id, err
|
|
}
|
|
id, err = NumID(num)
|
|
return id, err
|
|
}
|
|
|
|
// check if strings fit
|
|
if len(data) > maxStrIdentifierLength {
|
|
return id, fmt.Errorf("string too long: %s", data)
|
|
}
|
|
|
|
id, err = StrID(string(data))
|
|
return
|
|
}
|
|
|
|
func (a ID) Equal(b ID) bool {
|
|
return a == b
|
|
}
|
|
|
|
func (id ID) IsZero() bool {
|
|
return !id.isNum() && !id.isStr()
|
|
}
|
|
|
|
func (id ID) isNum() bool {
|
|
return id.Num != 0
|
|
}
|
|
|
|
func (id ID) isStr() bool {
|
|
return id.Str != [maxStrIdentifierLength]rune{}
|
|
}
|
|
|
|
func InSlice(needle ID, slice ...ID) bool {
|
|
for _, v := range slice {
|
|
if v.Equal(needle) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func RemoveFromSlice(needle ID, slice ...ID) (out []ID) {
|
|
if len(slice) == 0 {
|
|
return
|
|
}
|
|
|
|
out = make([]ID, 0, len(slice)-1)
|
|
|
|
for _, i := range slice {
|
|
if i.Equal(needle) {
|
|
continue
|
|
}
|
|
|
|
out = append(out, i)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (id ID) Value() string {
|
|
if id.isStr() {
|
|
return `"` + string(id.String()) + `"`
|
|
}
|
|
|
|
if id.isNum() {
|
|
return `"` + String(id.Number()) + `"`
|
|
}
|
|
|
|
// Backward compatibility for empty ID
|
|
return `"0"`
|
|
}
|
|
|
|
func (id ID) String() string {
|
|
hasValid := false
|
|
i := 0
|
|
|
|
for _, r := range id.Str {
|
|
if !hasValid && r != 0 {
|
|
hasValid = true
|
|
continue
|
|
}
|
|
|
|
if hasValid && r == 0 {
|
|
return string(id.Str[:i+1])
|
|
}
|
|
|
|
i++
|
|
}
|
|
|
|
if !hasValid {
|
|
return ""
|
|
}
|
|
|
|
return string(id.Str[:])
|
|
}
|
|
|
|
func (id ID) Number() uint64 {
|
|
return id.Num
|
|
}
|
|
|
|
// marshal/unmarshal JSON
|
|
func (id ID) MarshalJSON() ([]byte, error) {
|
|
return []byte(id.Value()), nil
|
|
}
|
|
|
|
func (id *ID) UnmarshalJSON(data []byte) (err error) {
|
|
// regex to check if data contains a uint64 or smaller number
|
|
if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
|
|
return nil
|
|
}
|
|
|
|
data = data[1 : len(data)-1]
|
|
|
|
auxID, err := ByteID(data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
*id = auxID
|
|
return
|
|
}
|
|
|
|
func StringifySlice(bb ...ID) (out []string) {
|
|
for _, b := range bb {
|
|
out = append(out, b.Value())
|
|
}
|
|
|
|
return
|
|
}
|