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}