tlog.app/go/tlog@v0.23.1/id.go (about)

     1  package tlog
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"math/rand"
     7  	"sync"
     8  	"time"
     9  
    10  	"tlog.app/go/errors"
    11  
    12  	"tlog.app/go/tlog/low"
    13  )
    14  
    15  type (
    16  	ID [16]byte
    17  
    18  	// ShortIDError is an ID parsing error.
    19  	ShortIDError struct {
    20  		Bytes int // Bytes successfully parsed
    21  	}
    22  
    23  	concurrentRand struct {
    24  		mu sync.Mutex
    25  		r  *rand.Rand
    26  	}
    27  )
    28  
    29  var rnd = &concurrentRand{r: rand.New(rand.NewSource(time.Now().UnixNano()))} //nolint:gosec
    30  
    31  // String returns short string representation.
    32  //
    33  // It's not supposed to be able to recover it back to the same value as it was.
    34  func (id ID) String() string {
    35  	var b [8]byte
    36  	id.FormatTo(b[:], 0, 'v')
    37  	return string(b[:])
    38  }
    39  
    40  // StringFull returns full id represented as string.
    41  func (id ID) StringFull() string {
    42  	var b [32]byte
    43  	id.FormatTo(b[:], 0, 'v')
    44  	return string(b[:])
    45  }
    46  
    47  func (id ID) StringUUID() string {
    48  	var b [36]byte
    49  	id.FormatTo(b[:], 0, 'u')
    50  	return string(b[:])
    51  }
    52  
    53  // IDFromBytes decodes ID from bytes slice.
    54  //
    55  // If byte slice is shorter than type length result is returned as is and ShortIDError as error value.
    56  // You may use result if you expected short ID prefix.
    57  func IDFromBytes(b []byte) (id ID, err error) {
    58  	n := copy(id[:], b)
    59  
    60  	if n < len(id) {
    61  		err = ShortIDError{Bytes: n}
    62  	}
    63  
    64  	return
    65  }
    66  
    67  // IDFromString parses ID from string.
    68  //
    69  // If parsed string is shorter than type length result is returned as is and ShortIDError as error value.
    70  // You may use result if you expected short ID prefix (profuced by ID.String, for example).
    71  func IDFromString(s string) (id ID, err error) {
    72  	var j int
    73  
    74  	for i := 0; i < len(s); i++ {
    75  		var c byte
    76  
    77  		switch {
    78  		case '0' <= s[i] && s[i] <= '9':
    79  			c = s[i] - '0'
    80  		case 'a' <= s[i] && s[i] <= 'f':
    81  			c = s[i] - 'a' + 10
    82  		case 'A' <= s[i] && s[i] <= 'F':
    83  			c = s[i] - 'A' + 10
    84  		case s[i] == '-':
    85  			continue
    86  		case s[i] == '_':
    87  			c = 0
    88  		default:
    89  			err = hex.InvalidByteError(s[i])
    90  			return
    91  		}
    92  
    93  		c <<= (4 * (1 - j&1))
    94  
    95  		id[j/2] |= c
    96  		j++
    97  	}
    98  
    99  	if j != 2*len(id) {
   100  		err = ShortIDError{Bytes: j / 2}
   101  	}
   102  
   103  	return
   104  }
   105  
   106  // IDFromStringAsBytes is the same as IDFromString. It avoids alloc in IDFromString(string(b)).
   107  func IDFromStringAsBytes(s []byte) (id ID, err error) {
   108  	return IDFromString(low.UnsafeBytesToString(s))
   109  }
   110  
   111  // ShouldID wraps IDFrom* call and skips error if any.
   112  func ShouldID(id ID, err error) ID {
   113  	return id
   114  }
   115  
   116  // MustID wraps IDFrom* call and panics if error occurred.
   117  func MustID(id ID, err error) ID {
   118  	if err != nil {
   119  		panic(err)
   120  	}
   121  
   122  	return id
   123  }
   124  
   125  // Error is an error interface implementation.
   126  func (e ShortIDError) Error() string {
   127  	return fmt.Sprintf("too short id: %d bytes, wanted %d", e.Bytes, len(ID{}))
   128  }
   129  
   130  // Format is fmt.Formatter interface implementation.
   131  // It supports width. '+' flag sets width to full ID length.
   132  func (id ID) Format(s fmt.State, c rune) {
   133  	var buf0 [36]byte
   134  	buf := low.NoEscapeBuffer(buf0[:])
   135  
   136  	w := 8
   137  
   138  	if W, ok := s.Width(); ok {
   139  		w = W
   140  	}
   141  
   142  	if s.Flag('+') {
   143  		w = 2 * len(id)
   144  	}
   145  
   146  	if s.Flag('+') && (c == 'u' || c == 'U') {
   147  		w = 2*len(id) + 4
   148  	}
   149  
   150  	i := id.FormatTo(buf[:w], 0, c)
   151  
   152  	_, _ = s.Write(buf[:i])
   153  }
   154  
   155  // FormatTo is alloc free Format alternative.
   156  func (id ID) FormatTo(b []byte, i int, f rune) int {
   157  	if id == (ID{}) {
   158  		switch f {
   159  		case 'v', 'V':
   160  			i += copy(b, "________________________________")
   161  		case 'u', 'U':
   162  			i += copy(b, "00000000-0000-0000-0000-000000000000")
   163  		default:
   164  			i += copy(b, "00000000000000000000000000000000")
   165  		}
   166  
   167  		return i
   168  	}
   169  
   170  	const digitsx = "0123456789abcdef"
   171  	const digitsX = "0123456789ABCDEF"
   172  
   173  	dg := digitsx
   174  	if f == 'X' || f == 'V' || f == 'U' {
   175  		dg = digitsX
   176  	}
   177  
   178  	uuid := f == 'u' || f == 'U'
   179  	limit := len(b[i:])
   180  
   181  	if uuid {
   182  		if limit > 36 {
   183  			limit = 36
   184  		}
   185  	} else {
   186  		if limit > 32 {
   187  			limit = 32
   188  		}
   189  	}
   190  
   191  	limit += i
   192  
   193  	j := 0
   194  
   195  	for i < limit {
   196  		if uuid && (j == 4 || j == 6 || j == 8 || j == 10) {
   197  			b[i] = '-'
   198  			i++
   199  		}
   200  
   201  		if i == limit {
   202  			break
   203  		}
   204  
   205  		b[i] = dg[id[j]>>4]
   206  		i++
   207  
   208  		if i == limit {
   209  			break
   210  		}
   211  
   212  		b[i] = dg[id[j]&0xf]
   213  		i++
   214  
   215  		j++
   216  	}
   217  
   218  	return i
   219  }
   220  
   221  func (id ID) MarshalJSON() ([]byte, error) {
   222  	var b [38]byte
   223  
   224  	i := 0
   225  
   226  	b[i] = '"'
   227  	i++
   228  
   229  	i = id.FormatTo(b[:], i, 'u')
   230  
   231  	b[i] = '"'
   232  	i++
   233  
   234  	return b[:i], nil
   235  }
   236  
   237  func (id *ID) UnmarshalJSON(b []byte) error {
   238  	if len(b) < 4 {
   239  		return errors.New("bad id")
   240  	}
   241  
   242  	if b[0] != '"' || b[len(b)-1] != '"' {
   243  		return errors.New("bad id encoding")
   244  	}
   245  
   246  	x, err := IDFromStringAsBytes(b[1 : len(b)-1])
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	*id = x
   252  
   253  	return nil
   254  }
   255  
   256  func MathRandID() (id ID) {
   257  	rnd.mu.Lock()
   258  
   259  	for id == (ID{}) {
   260  		_, _ = rnd.r.Read(id[:])
   261  	}
   262  
   263  	rnd.mu.Unlock()
   264  
   265  	return
   266  }
   267  
   268  func RandIDFromReader(read func(p []byte) (int, error)) func() ID {
   269  	return func() (id ID) {
   270  		n, err := read(id[:])
   271  		if err != nil {
   272  			panic(err)
   273  		}
   274  		if n != len(id) {
   275  			panic(n)
   276  		}
   277  
   278  		return id
   279  	}
   280  }
   281  
   282  /* will repeat at most after 2 ** (32 - 2) ids
   283  func FastRandID() (id ID) {
   284  	*(*uint32)(unsafe.Pointer(&id[0])) = fastrand()
   285  	*(*uint32)(unsafe.Pointer(&id[4])) = fastrand()
   286  	*(*uint32)(unsafe.Pointer(&id[8])) = fastrand()
   287  	*(*uint32)(unsafe.Pointer(&id[12])) = fastrand()
   288  	return
   289  }
   290  */
   291  
   292  // UUID creates ID generation function.
   293  // read is a random Read method. Function panics on Read error.
   294  // read must be safe for concurrent use.
   295  //
   296  // It's got from github.com/google/uuid.
   297  func UUID(read func(p []byte) (int, error)) func() ID {
   298  	return func() (uuid ID) {
   299  		n, err := read(uuid[:])
   300  		if err != nil {
   301  			panic(err)
   302  		}
   303  		if n != len(uuid) {
   304  			panic(n)
   305  		}
   306  
   307  		uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
   308  		uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
   309  
   310  		return uuid
   311  	}
   312  }