bug.go

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