aboutsummaryrefslogtreecommitdiffstats
path: root/bug/operation.go
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2022-07-25 13:16:16 +0200
committerMichael Muré <batolettre@gmail.com>2022-07-25 13:27:17 +0200
commit3d454d9dc8ba2409046c0938618a70864e6eb8ef (patch)
tree8745f656cc8218654632ce003f997a39988d3043 /bug/operation.go
parent2ade8fb1d570ddcb4aedc9386af46d208b129daa (diff)
downloadgit-bug-3d454d9dc8ba2409046c0938618a70864e6eb8ef.tar.gz
entity/dag: proper base operation for simplified implementation
- reduce boilerplace necessary to implement an operation - consolidate what an operation is in the core, which in turn pave the way for a generic cache layer mechanism - avoid the previously complex unmarshalling process - support operation metadata from the core - simplified testing
Diffstat (limited to 'bug/operation.go')
-rw-r--r--bug/operation.go240
1 files changed, 9 insertions, 231 deletions
diff --git a/bug/operation.go b/bug/operation.go
index b5c6b1de..9c87d8f3 100644
--- a/bug/operation.go
+++ b/bug/operation.go
@@ -1,23 +1,15 @@
package bug
import (
- "crypto/rand"
"encoding/json"
"fmt"
- "time"
- "github.com/pkg/errors"
-
- "github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
)
-// OperationType is an operation type identifier
-type OperationType int
-
const (
- _ OperationType = iota
+ _ dag.OperationType = iota
CreateOp
SetTitleOp
AddCommentOp
@@ -32,55 +24,24 @@ const (
type Operation interface {
dag.Operation
- // Type return the type of the operation
- Type() OperationType
-
- // Time return the time when the operation was added
- Time() time.Time
// Apply the operation to a Snapshot to create the final state
Apply(snapshot *Snapshot)
-
- // SetMetadata store arbitrary metadata about the operation
- SetMetadata(key string, value string)
- // GetMetadata retrieve arbitrary metadata about the operation
- GetMetadata(key string) (string, bool)
- // AllMetadata return all metadata for this operation
- AllMetadata() map[string]string
-
- setExtraMetadataImmutable(key string, value string)
}
-func idOperation(op Operation, base *OpBase) entity.Id {
- if base.id == "" {
- // something went really wrong
- panic("op's id not set")
- }
- if base.id == entity.UnsetId {
- // This means we are trying to get the op's Id *before* it has been stored, for instance when
- // adding multiple ops in one go in an OperationPack.
- // As the Id is computed based on the actual bytes written on the disk, we are going to predict
- // those and then get the Id. This is safe as it will be the exact same code writing on disk later.
-
- data, err := json.Marshal(op)
- if err != nil {
- panic(err)
- }
-
- base.id = entity.DeriveId(data)
- }
- return base.id
-}
+// make sure that package external operations do conform to our interface
+var _ Operation = &dag.NoOpOperation[*Snapshot]{}
+var _ Operation = &dag.SetMetadataOperation[*Snapshot]{}
-func operationUnmarshaller(author identity.Interface, raw json.RawMessage, resolver identity.Resolver) (dag.Operation, error) {
+func operationUnmarshaller(raw json.RawMessage, resolver identity.Resolver) (dag.Operation, error) {
var t struct {
- OperationType OperationType `json:"type"`
+ OperationType dag.OperationType `json:"type"`
}
if err := json.Unmarshal(raw, &t); err != nil {
return nil, err
}
- var op Operation
+ var op dag.Operation
switch t.OperationType {
case AddCommentOp:
@@ -92,9 +53,9 @@ func operationUnmarshaller(author identity.Interface, raw json.RawMessage, resol
case LabelChangeOp:
op = &LabelChangeOperation{}
case NoOpOp:
- op = &NoOpOperation{}
+ op = &dag.NoOpOperation[*Snapshot]{}
case SetMetadataOp:
- op = &SetMetadataOperation{}
+ op = &dag.SetMetadataOperation[*Snapshot]{}
case SetStatusOp:
op = &SetStatusOperation{}
case SetTitleOp:
@@ -108,188 +69,5 @@ func operationUnmarshaller(author identity.Interface, raw json.RawMessage, resol
return nil, err
}
- switch op := op.(type) {
- case *AddCommentOperation:
- op.Author_ = author
- case *CreateOperation:
- op.Author_ = author
- case *EditCommentOperation:
- op.Author_ = author
- case *LabelChangeOperation:
- op.Author_ = author
- case *NoOpOperation:
- op.Author_ = author
- case *SetMetadataOperation:
- op.Author_ = author
- case *SetStatusOperation:
- op.Author_ = author
- case *SetTitleOperation:
- op.Author_ = author
- default:
- panic(fmt.Sprintf("unknown operation type %T", op))
- }
-
return op, nil
}
-
-// OpBase implement the common code for all operations
-type OpBase struct {
- OperationType OperationType `json:"type"`
- Author_ identity.Interface `json:"-"` // not serialized
- // TODO: part of the data model upgrade, this should eventually be a timestamp + lamport
- UnixTime int64 `json:"timestamp"`
- Metadata map[string]string `json:"metadata,omitempty"`
-
- // mandatory random bytes to ensure a better randomness of the data used to later generate the ID
- // len(Nonce) should be > 20 and < 64 bytes
- // It has no functional purpose and should be ignored.
- Nonce []byte `json:"nonce"`
-
- // Not serialized. Store the op's id in memory.
- id entity.Id
- // Not serialized. Store the extra metadata in memory,
- // compiled from SetMetadataOperation.
- extraMetadata map[string]string
-}
-
-// newOpBase is the constructor for an OpBase
-func newOpBase(opType OperationType, author identity.Interface, unixTime int64) OpBase {
- return OpBase{
- OperationType: opType,
- Author_: author,
- UnixTime: unixTime,
- Nonce: makeNonce(20),
- id: entity.UnsetId,
- }
-}
-
-func makeNonce(len int) []byte {
- result := make([]byte, len)
- _, err := rand.Read(result)
- if err != nil {
- panic(err)
- }
- return result
-}
-
-func (base *OpBase) UnmarshalJSON(data []byte) error {
- // Compute the Id when loading the op from disk.
- base.id = entity.DeriveId(data)
-
- aux := struct {
- OperationType OperationType `json:"type"`
- UnixTime int64 `json:"timestamp"`
- Metadata map[string]string `json:"metadata,omitempty"`
- Nonce []byte `json:"nonce"`
- }{}
-
- if err := json.Unmarshal(data, &aux); err != nil {
- return err
- }
-
- base.OperationType = aux.OperationType
- base.UnixTime = aux.UnixTime
- base.Metadata = aux.Metadata
- base.Nonce = aux.Nonce
-
- return nil
-}
-
-func (base *OpBase) Type() OperationType {
- return base.OperationType
-}
-
-// Time return the time when the operation was added
-func (base *OpBase) Time() time.Time {
- return time.Unix(base.UnixTime, 0)
-}
-
-// Validate check the OpBase for errors
-func (base *OpBase) Validate(op Operation, opType OperationType) error {
- if base.OperationType != opType {
- return fmt.Errorf("incorrect operation type (expected: %v, actual: %v)", opType, base.OperationType)
- }
-
- if op.Time().Unix() == 0 {
- return fmt.Errorf("time not set")
- }
-
- if base.Author_ == nil {
- return fmt.Errorf("author not set")
- }
-
- if err := op.Author().Validate(); err != nil {
- return errors.Wrap(err, "author")
- }
-
- if op, ok := op.(dag.OperationWithFiles); ok {
- for _, hash := range op.GetFiles() {
- if !hash.IsValid() {
- return fmt.Errorf("file with invalid hash %v", hash)
- }
- }
- }
-
- if len(base.Nonce) > 64 {
- return fmt.Errorf("nonce is too big")
- }
- if len(base.Nonce) < 20 {
- return fmt.Errorf("nonce is too small")
- }
-
- return nil
-}
-
-// SetMetadata store arbitrary metadata about the operation
-func (base *OpBase) SetMetadata(key string, value string) {
- if base.Metadata == nil {
- base.Metadata = make(map[string]string)
- }
-
- base.Metadata[key] = value
- base.id = entity.UnsetId
-}
-
-// GetMetadata retrieve arbitrary metadata about the operation
-func (base *OpBase) GetMetadata(key string) (string, bool) {
- val, ok := base.Metadata[key]
-
- if ok {
- return val, true
- }
-
- // extraMetadata can't replace the original operations value if any
- val, ok = base.extraMetadata[key]
-
- return val, ok
-}
-
-// AllMetadata return all metadata for this operation
-func (base *OpBase) AllMetadata() map[string]string {
- result := make(map[string]string)
-
- for key, val := range base.extraMetadata {
- result[key] = val
- }
-
- // Original metadata take precedence
- for key, val := range base.Metadata {
- result[key] = val
- }
-
- return result
-}
-
-func (base *OpBase) setExtraMetadataImmutable(key string, value string) {
- if base.extraMetadata == nil {
- base.extraMetadata = make(map[string]string)
- }
- if _, exist := base.extraMetadata[key]; !exist {
- base.extraMetadata[key] = value
- }
-}
-
-// Author return author identity
-func (base *OpBase) Author() identity.Interface {
- return base.Author_
-}