This repository has been archived by the owner on Oct 9, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 53
/
encoder.go
105 lines (86 loc) · 2.89 KB
/
encoder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package encoding
import (
"encoding/base32"
"fmt"
"hash"
"hash/fnv"
"strings"
)
const specialEncoderKey = "abcdefghijklmnopqrstuvwxyz123456"
var Base32Encoder = base32.NewEncoding(specialEncoderKey).WithPadding(base32.NoPadding)
// Algorithm defines an enum for the encoding algorithm to use.
type Algorithm uint32
const (
// Algorithm32 uses fnv32 bit encoder.
Algorithm32 Algorithm = iota
// Algorithm64 uses fnv64 bit encoder.
Algorithm64
// Algorithm128 uses fnv128 bit encoder.
Algorithm128
)
type Option interface {
option()
}
// AlgorithmOption defines a wrapper to pass the algorithm to encoding functions.
type AlgorithmOption struct {
Option
algo Algorithm
}
// NewAlgorithmOption wraps the Algorithm into an AlgorithmOption to pass to the encoding functions.
func NewAlgorithmOption(algo Algorithm) AlgorithmOption {
return AlgorithmOption{
algo: algo,
}
}
// FixedLengthUniqueID creates a new UniqueID that is based on the inputID and of a specified length, if the given id is
// longer than the maxLength.
func FixedLengthUniqueID(inputID string, maxLength int, options ...Option) (string, error) {
if len(inputID) <= maxLength {
return inputID, nil
}
var hasher hash.Hash
for _, option := range options {
if algoOption, casted := option.(AlgorithmOption); casted {
switch algoOption.algo {
case Algorithm32:
hasher = fnv.New32a()
case Algorithm64:
hasher = fnv.New64a()
case Algorithm128:
hasher = fnv.New128a()
}
}
}
if hasher == nil {
hasher = fnv.New32a()
}
// Using 32a/64a an error can never happen, so this will always remain not covered by a unit test
_, _ = hasher.Write([]byte(inputID)) // #nosec
b := hasher.Sum(nil)
// Encoding Length Calculation:
// Base32 Encoder will encode every 5 bits into an output character (2 ^ 5 = 32)
// output length = ciel(<input bits> / 5)
// for 32a hashing = ceil(32 / 5) = 7
// for 64a hashing = ceil(64 / 5) = 13
// We prefix with character `f` so the final output is 8 or 14
finalStr := "f" + Base32Encoder.EncodeToString(b)
if len(finalStr) > maxLength {
return finalStr, fmt.Errorf("max Length is too small, cannot create an encoded string that is so small")
}
return finalStr, nil
}
// FixedLengthUniqueIDForParts creates a new uniqueID using the parts concatenated using `-` and ensures that the
// uniqueID is not longer than the maxLength. In case a simple concatenation yields a longer string, a new hashed ID is
// created which is always around 8 characters in length.
func FixedLengthUniqueIDForParts(maxLength int, parts []string, options ...Option) (string, error) {
b := strings.Builder{}
for i, p := range parts {
if i > 0 && b.Len() > 0 {
// Ignoring the error as it always returns a nil error
_, _ = b.WriteRune('-') // #nosec
}
// Ignoring the error as this is always nil
_, _ = b.WriteString(p) // #nosec
}
return FixedLengthUniqueID(b.String(), maxLength, options...)
}