bare.go

  1package identity
  2
  3import (
  4	"crypto/sha256"
  5	"encoding/json"
  6	"fmt"
  7	"strings"
  8
  9	"github.com/MichaelMure/git-bug/entity"
 10	"github.com/MichaelMure/git-bug/repository"
 11	"github.com/MichaelMure/git-bug/util/lamport"
 12	"github.com/MichaelMure/git-bug/util/text"
 13	"github.com/MichaelMure/git-bug/util/timestamp"
 14)
 15
 16var _ Interface = &Bare{}
 17var _ entity.Interface = &Bare{}
 18
 19// Bare is a very minimal identity, designed to be fully embedded directly along
 20// other data.
 21//
 22// in particular, this identity is designed to be compatible with the handling of
 23// identities in the early version of git-bug.
 24type Bare struct {
 25	id        entity.Id
 26	name      string
 27	email     string
 28	login     string
 29	avatarUrl string
 30}
 31
 32func NewBare(name string, email string) *Bare {
 33	return &Bare{id: entity.UnsetId, name: name, email: email}
 34}
 35
 36func NewBareFull(name string, email string, login string, avatarUrl string) *Bare {
 37	return &Bare{id: entity.UnsetId, name: name, email: email, login: login, avatarUrl: avatarUrl}
 38}
 39
 40func deriveId(data []byte) entity.Id {
 41	sum := sha256.Sum256(data)
 42	return entity.Id(fmt.Sprintf("%x", sum))
 43}
 44
 45type bareIdentityJSON struct {
 46	Name      string `json:"name,omitempty"`
 47	Email     string `json:"email,omitempty"`
 48	Login     string `json:"login,omitempty"`
 49	AvatarUrl string `json:"avatar_url,omitempty"`
 50}
 51
 52func (i *Bare) MarshalJSON() ([]byte, error) {
 53	data, err := json.Marshal(bareIdentityJSON{
 54		Name:      i.name,
 55		Email:     i.email,
 56		Login:     i.login,
 57		AvatarUrl: i.avatarUrl,
 58	})
 59	return data, err
 60}
 61
 62func (i *Bare) UnmarshalJSON(data []byte) error {
 63	// Compute the Id when loading the op from disk.
 64	i.id = deriveId(data)
 65
 66	aux := bareIdentityJSON{}
 67
 68	if err := json.Unmarshal(data, &aux); err != nil {
 69		return err
 70	}
 71
 72	i.name = aux.Name
 73	i.email = aux.Email
 74	i.login = aux.Login
 75	i.avatarUrl = aux.AvatarUrl
 76
 77	return nil
 78}
 79
 80// Id return the Identity identifier
 81func (i *Bare) Id() entity.Id {
 82	// We don't have a proper Id at hand, so let's hash all the data to get one.
 83
 84	if i.id == "" {
 85		// something went really wrong
 86		panic("identity's id not set")
 87	}
 88	if i.id == entity.UnsetId {
 89		// This means we are trying to get the identity identifier *before* it has been stored
 90		// As the Id is computed based on the actual bytes written on the disk, we are going to predict
 91		// those and then get the Id. This is safe as it will be the exact same code writing on disk later.
 92
 93		data, err := json.Marshal(i)
 94		if err != nil {
 95			panic(err)
 96		}
 97
 98		i.id = deriveId(data)
 99	}
100	return i.id
101}
102
103// Name return the last version of the name
104func (i *Bare) Name() string {
105	return i.name
106}
107
108// Email return the last version of the email
109func (i *Bare) Email() string {
110	return i.email
111}
112
113// Login return the last version of the login
114func (i *Bare) Login() string {
115	return i.login
116}
117
118// AvatarUrl return the last version of the Avatar URL
119func (i *Bare) AvatarUrl() string {
120	return i.avatarUrl
121}
122
123// Keys return the last version of the valid keys
124func (i *Bare) Keys() []Key {
125	return []Key{}
126}
127
128// ValidKeysAtTime return the set of keys valid at a given lamport time
129func (i *Bare) ValidKeysAtTime(time lamport.Time) []Key {
130	return []Key{}
131}
132
133// DisplayName return a non-empty string to display, representing the
134// identity, based on the non-empty values.
135func (i *Bare) DisplayName() string {
136	switch {
137	case i.name == "" && i.login != "":
138		return i.login
139	case i.name != "" && i.login == "":
140		return i.name
141	case i.name != "" && i.login != "":
142		return fmt.Sprintf("%s (%s)", i.name, i.login)
143	}
144
145	panic("invalid person data")
146}
147
148// Validate check if the Identity data is valid
149func (i *Bare) Validate() error {
150	if text.Empty(i.name) && text.Empty(i.login) {
151		return fmt.Errorf("either name or login should be set")
152	}
153
154	if strings.Contains(i.name, "\n") {
155		return fmt.Errorf("name should be a single line")
156	}
157
158	if !text.Safe(i.name) {
159		return fmt.Errorf("name is not fully printable")
160	}
161
162	if strings.Contains(i.login, "\n") {
163		return fmt.Errorf("login should be a single line")
164	}
165
166	if !text.Safe(i.login) {
167		return fmt.Errorf("login is not fully printable")
168	}
169
170	if strings.Contains(i.email, "\n") {
171		return fmt.Errorf("email should be a single line")
172	}
173
174	if !text.Safe(i.email) {
175		return fmt.Errorf("email is not fully printable")
176	}
177
178	if i.avatarUrl != "" && !text.ValidUrl(i.avatarUrl) {
179		return fmt.Errorf("avatarUrl is not a valid URL")
180	}
181
182	return nil
183}
184
185// Write the identity into the Repository. In particular, this ensure that
186// the Id is properly set.
187func (i *Bare) Commit(repo repository.ClockedRepo) error {
188	// Nothing to do, everything is directly embedded
189	return nil
190}
191
192// If needed, write the identity into the Repository. In particular, this
193// ensure that the Id is properly set.
194func (i *Bare) CommitAsNeeded(repo repository.ClockedRepo) error {
195	// Nothing to do, everything is directly embedded
196	return nil
197}
198
199// IsProtected return true if the chain of git commits started to be signed.
200// If that's the case, only signed commit with a valid key for this identity can be added.
201func (i *Bare) IsProtected() bool {
202	return false
203}
204
205// LastModificationLamportTime return the Lamport time at which the last version of the identity became valid.
206func (i *Bare) LastModificationLamport() lamport.Time {
207	return 0
208}
209
210// LastModification return the timestamp at which the last version of the identity became valid.
211func (i *Bare) LastModification() timestamp.Timestamp {
212	return 0
213}