github.com/nikandfor/tlog@v0.21.5-0.20231108111739-3ef89426a96d/id.go (about)

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