1// Package bug contains the bug data model and low-level related functions
  2package bug
  3
  4import (
  5	"fmt"
  6
  7	"github.com/MichaelMure/git-bug/entities/common"
  8	"github.com/MichaelMure/git-bug/entities/identity"
  9	"github.com/MichaelMure/git-bug/entity"
 10	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 11	"github.com/MichaelMure/git-bug/entity/dag"
 12	"github.com/MichaelMure/git-bug/repository"
 13)
 14
 15var _ Interface = &Bug{}
 16var _ entity.Interface[*Snapshot, Operation] = &Bug{}
 17
 18// 1: original format
 19// 2: no more legacy identities
 20// 3: Ids are generated from the create operation serialized data instead of from the first git commit
 21// 4: with DAG entity framework
 22const formatVersion = 4
 23
 24const Typename = "bug"
 25const Namespace = "bugs"
 26
 27var def = dag.Definition{
 28	Typename:             Typename,
 29	Namespace:            Namespace,
 30	OperationUnmarshaler: operationUnmarshaler,
 31	FormatVersion:        formatVersion,
 32}
 33
 34var ClockLoader = dag.ClockLoader(def)
 35
 36type Interface interface {
 37	entity.Interface[*Snapshot, Operation]
 38}
 39
 40// Bug holds the data of a bug thread, organized in a way close to
 41// how it will be persisted inside Git. This is the data structure
 42// used to merge two different version of the same Bug.
 43type Bug struct {
 44	*dag.Entity
 45}
 46
 47// NewBug create a new Bug
 48func NewBug() *Bug {
 49	return wrapper(dag.New(def))
 50}
 51
 52func wrapper(e *dag.Entity) *Bug {
 53	return &Bug{Entity: e}
 54}
 55
 56func simpleResolvers(repo repository.ClockedRepo) entity.Resolvers {
 57	return entity.Resolvers{
 58		&identity.Identity{}: identity.NewSimpleResolver(repo),
 59	}
 60}
 61
 62// Read will read a bug from a repository
 63func Read(repo repository.ClockedRepo, id entity.Id) (*Bug, error) {
 64	return ReadWithResolver(repo, simpleResolvers(repo), id)
 65}
 66
 67// ReadWithResolver will read a bug from its Id, with custom resolvers
 68func ReadWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (*Bug, error) {
 69	return dag.Read(def, wrapper, repo, resolvers, id)
 70}
 71
 72// ReadAll read and parse all local bugs
 73func ReadAll(repo repository.ClockedRepo) <-chan bootstrap.StreamedEntity[*Bug] {
 74	return dag.ReadAll(def, wrapper, repo, simpleResolvers(repo))
 75}
 76
 77// ReadAllWithResolver read and parse all local bugs
 78func ReadAllWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan bootstrap.StreamedEntity[*Bug] {
 79	return dag.ReadAll(def, wrapper, repo, resolvers)
 80}
 81
 82// ListLocalIds list all the available local bug ids
 83func ListLocalIds(repo repository.Repo) ([]entity.Id, error) {
 84	return dag.ListLocalIds(def, repo)
 85}
 86
 87// Validate check if the Bug data is valid
 88func (bug *Bug) Validate() error {
 89	if err := bug.Entity.Validate(); err != nil {
 90		return err
 91	}
 92
 93	// The very first Op should be a CreateOp
 94	firstOp := bug.FirstOp()
 95	if firstOp == nil || firstOp.Type() != CreateOp {
 96		return fmt.Errorf("first operation should be a Create op")
 97	}
 98
 99	// Check that there is no more CreateOp op
100	for i, op := range bug.Entity.Operations() {
101		if i == 0 {
102			continue
103		}
104		if op.Type() == CreateOp {
105			return fmt.Errorf("only one Create op allowed")
106		}
107	}
108
109	return nil
110}
111
112// Append add a new Operation to the Bug
113func (bug *Bug) Append(op Operation) {
114	bug.Entity.Append(op)
115}
116
117// Operations return the ordered operations
118func (bug *Bug) Operations() []Operation {
119	source := bug.Entity.Operations()
120	result := make([]Operation, len(source))
121	for i, op := range source {
122		result[i] = op.(Operation)
123	}
124	return result
125}
126
127// Compile a bug in an easily usable snapshot
128func (bug *Bug) Compile() *Snapshot {
129	snap := &Snapshot{
130		id:     bug.Id(),
131		Status: common.OpenStatus,
132	}
133
134	for _, op := range bug.Operations() {
135		op.Apply(snap)
136		snap.Operations = append(snap.Operations, op)
137	}
138
139	return snap
140}
141
142// FirstOp lookup for the very first operation of the bug.
143// For a valid Bug, this operation should be a CreateOp
144func (bug *Bug) FirstOp() Operation {
145	if fo := bug.Entity.FirstOp(); fo != nil {
146		return fo.(Operation)
147	}
148	return nil
149}
150
151// LastOp lookup for the very last operation of the bug.
152// For a valid Bug, should never be nil
153func (bug *Bug) LastOp() Operation {
154	if lo := bug.Entity.LastOp(); lo != nil {
155		return lo.(Operation)
156	}
157	return nil
158}