1package entity
2
3import (
4 "crypto/sha256"
5 "fmt"
6 "io"
7 "strings"
8
9 "github.com/pkg/errors"
10)
11
12// sha-256
13const idLength = 64
14const HumanIdLength = 7
15
16const UnsetId = Id("unset")
17
18// Id is an identifier for an entity or part of an entity
19type Id string
20
21// DeriveId generate an Id from the serialization of the object or part of the object.
22func DeriveId(data []byte) Id {
23 // My understanding is that sha256 is enough to prevent collision (git use that, so ...?)
24 // If you read this code, I'd be happy to be schooled.
25
26 sum := sha256.Sum256(data)
27 return Id(fmt.Sprintf("%x", sum))
28}
29
30// String return the identifier as a string
31func (i Id) String() string {
32 return string(i)
33}
34
35// Human return the identifier, shortened for human consumption
36func (i Id) Human() string {
37 format := fmt.Sprintf("%%.%ds", HumanIdLength)
38 return fmt.Sprintf(format, i)
39}
40
41func (i Id) HasPrefix(prefix string) bool {
42 return strings.HasPrefix(string(i), prefix)
43}
44
45// UnmarshalGQL implement the Unmarshaler interface for gqlgen
46func (i *Id) UnmarshalGQL(v interface{}) error {
47 _, ok := v.(string)
48 if !ok {
49 return fmt.Errorf("IDs must be strings")
50 }
51
52 *i = v.(Id)
53
54 if err := i.Validate(); err != nil {
55 return errors.Wrap(err, "invalid ID")
56 }
57
58 return nil
59}
60
61// MarshalGQL implement the Marshaler interface for gqlgen
62func (i Id) MarshalGQL(w io.Writer) {
63 _, _ = w.Write([]byte(`"` + i.String() + `"`))
64}
65
66// Validate tell if the Id is valid
67func (i Id) Validate() error {
68 // Special case to detect outdated repo
69 if len(i) == 40 {
70 return fmt.Errorf("outdated repository format, please use https://github.com/git-bug/git-bug-migration to upgrade")
71 }
72 if len(i) != idLength {
73 return fmt.Errorf("invalid length")
74 }
75 for _, r := range i {
76 if (r < 'a' || r > 'z') && (r < '0' || r > '9') {
77 return fmt.Errorf("invalid character")
78 }
79 }
80 return nil
81}