1package bug
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6
  7	"github.com/pkg/errors"
  8
  9	"github.com/MichaelMure/git-bug/entity"
 10	"github.com/MichaelMure/git-bug/identity"
 11	"github.com/MichaelMure/git-bug/util/text"
 12)
 13
 14var _ Operation = &SetMetadataOperation{}
 15
 16type SetMetadataOperation struct {
 17	OpBase
 18	Target      entity.Id         `json:"target"`
 19	NewMetadata map[string]string `json:"new_metadata"`
 20}
 21
 22func (op *SetMetadataOperation) Id() entity.Id {
 23	return idOperation(op, &op.OpBase)
 24}
 25
 26func (op *SetMetadataOperation) Apply(snapshot *Snapshot) {
 27	for _, target := range snapshot.Operations {
 28		if target.Id() == op.Target {
 29			// Apply the metadata in an immutable way: if a metadata already
 30			// exist, it's not possible to override it.
 31			for key, value := range op.NewMetadata {
 32				target.setExtraMetadataImmutable(key, value)
 33			}
 34			return
 35		}
 36	}
 37}
 38
 39func (op *SetMetadataOperation) Validate() error {
 40	if err := op.OpBase.Validate(op, SetMetadataOp); err != nil {
 41		return err
 42	}
 43
 44	if err := op.Target.Validate(); err != nil {
 45		return errors.Wrap(err, "target invalid")
 46	}
 47
 48	for key, val := range op.NewMetadata {
 49		if !text.SafeOneLine(key) {
 50			return fmt.Errorf("metadata key is unsafe")
 51		}
 52		if !text.Safe(val) {
 53			return fmt.Errorf("metadata value is not fully printable")
 54		}
 55	}
 56
 57	return nil
 58}
 59
 60// UnmarshalJSON is a two step JSON unmarshalling
 61// This workaround is necessary to avoid the inner OpBase.MarshalJSON
 62// overriding the outer op's MarshalJSON
 63func (op *SetMetadataOperation) UnmarshalJSON(data []byte) error {
 64	// Unmarshal OpBase and the op separately
 65
 66	base := OpBase{}
 67	err := json.Unmarshal(data, &base)
 68	if err != nil {
 69		return err
 70	}
 71
 72	aux := struct {
 73		Target      entity.Id         `json:"target"`
 74		NewMetadata map[string]string `json:"new_metadata"`
 75	}{}
 76
 77	err = json.Unmarshal(data, &aux)
 78	if err != nil {
 79		return err
 80	}
 81
 82	op.OpBase = base
 83	op.Target = aux.Target
 84	op.NewMetadata = aux.NewMetadata
 85
 86	return nil
 87}
 88
 89// Sign post method for gqlgen
 90func (op *SetMetadataOperation) IsAuthored() {}
 91
 92func NewSetMetadataOp(author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *SetMetadataOperation {
 93	return &SetMetadataOperation{
 94		OpBase:      newOpBase(SetMetadataOp, author, unixTime),
 95		Target:      target,
 96		NewMetadata: newMetadata,
 97	}
 98}
 99
100// Convenience function to apply the operation
101func SetMetadata(b Interface, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) (*SetMetadataOperation, error) {
102	SetMetadataOp := NewSetMetadataOp(author, unixTime, target, newMetadata)
103	if err := SetMetadataOp.Validate(); err != nil {
104		return nil, err
105	}
106	b.Append(SetMetadataOp)
107	return SetMetadataOp, nil
108}