diff --git a/entities/bug/bug.go b/entities/bug/bug.go index 4ce10c5bbff8aec24f4ffe32cc88b0fe59f135c6..1140c543aea359c7662cb993df01848f1bf27169 100644 --- a/entities/bug/bug.go +++ b/entities/bug/bug.go @@ -23,7 +23,7 @@ const formatVersion = 4 var def = dag.Definition{ Typename: "bug", Namespace: "bugs", - OperationUnmarshaler: operationUnmarshaller, + OperationUnmarshaler: operationUnmarshaler, FormatVersion: formatVersion, } diff --git a/entities/bug/op_add_comment_test.go b/entities/bug/op_add_comment_test.go index 6f29cb019be2451f243ce8f23cb738cc2dd5faa1..fee9e785cb2796cacc6acd299ce8c74d52433a5f 100644 --- a/entities/bug/op_add_comment_test.go +++ b/entities/bug/op_add_comment_test.go @@ -4,15 +4,16 @@ import ( "testing" "github.com/MichaelMure/git-bug/entities/identity" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/entity/dag" "github.com/MichaelMure/git-bug/repository" ) func TestAddCommentSerialize(t *testing.T) { - dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *AddCommentOperation { - return NewAddCommentOp(author, unixTime, "message", nil) + dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*AddCommentOperation, entity.Resolvers) { + return NewAddCommentOp(author, unixTime, "message", nil), nil }) - dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *AddCommentOperation { - return NewAddCommentOp(author, unixTime, "message", []repository.Hash{"hash1", "hash2"}) + dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*AddCommentOperation, entity.Resolvers) { + return NewAddCommentOp(author, unixTime, "message", []repository.Hash{"hash1", "hash2"}), nil }) } diff --git a/entities/bug/op_create_test.go b/entities/bug/op_create_test.go index e534162b0f70164b7eae77dc8832063c6d38f614..d8bde46f138c8753436f47812c1b04832637c8a1 100644 --- a/entities/bug/op_create_test.go +++ b/entities/bug/op_create_test.go @@ -40,10 +40,10 @@ func TestCreate(t *testing.T) { } func TestCreateSerialize(t *testing.T) { - dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *CreateOperation { - return NewCreateOp(author, unixTime, "title", "message", nil) + dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*CreateOperation, entity.Resolvers) { + return NewCreateOp(author, unixTime, "title", "message", nil), nil }) - dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *CreateOperation { - return NewCreateOp(author, unixTime, "title", "message", []repository.Hash{"hash1", "hash2"}) + dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*CreateOperation, entity.Resolvers) { + return NewCreateOp(author, unixTime, "title", "message", []repository.Hash{"hash1", "hash2"}), nil }) } diff --git a/entities/bug/op_edit_comment_test.go b/entities/bug/op_edit_comment_test.go index 1b649cd180013eaefd626477bfec6deaceeacbde..2ca1345e29fbd569852f8ed23bb769e1925586b9 100644 --- a/entities/bug/op_edit_comment_test.go +++ b/entities/bug/op_edit_comment_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/MichaelMure/git-bug/entities/identity" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/entity/dag" "github.com/MichaelMure/git-bug/repository" ) @@ -75,10 +76,10 @@ func TestEdit(t *testing.T) { } func TestEditCommentSerialize(t *testing.T) { - dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *EditCommentOperation { - return NewEditCommentOp(author, unixTime, "target", "message", nil) + dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*EditCommentOperation, entity.Resolvers) { + return NewEditCommentOp(author, unixTime, "target", "message", nil), nil }) - dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *EditCommentOperation { - return NewEditCommentOp(author, unixTime, "target", "message", []repository.Hash{"hash1", "hash2"}) + dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*EditCommentOperation, entity.Resolvers) { + return NewEditCommentOp(author, unixTime, "target", "message", []repository.Hash{"hash1", "hash2"}), nil }) } diff --git a/entities/bug/op_label_change_test.go b/entities/bug/op_label_change_test.go index edbe47144bf800b8bf83ff0e29138e192a033da7..e6dc880359ccbcf876fc15c36d09e78224adcd8e 100644 --- a/entities/bug/op_label_change_test.go +++ b/entities/bug/op_label_change_test.go @@ -4,17 +4,18 @@ import ( "testing" "github.com/MichaelMure/git-bug/entities/identity" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/entity/dag" ) func TestLabelChangeSerialize(t *testing.T) { - dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *LabelChangeOperation { - return NewLabelChangeOperation(author, unixTime, []Label{"added"}, []Label{"removed"}) + dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*LabelChangeOperation, entity.Resolvers) { + return NewLabelChangeOperation(author, unixTime, []Label{"added"}, []Label{"removed"}), nil }) - dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *LabelChangeOperation { - return NewLabelChangeOperation(author, unixTime, []Label{"added"}, nil) + dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*LabelChangeOperation, entity.Resolvers) { + return NewLabelChangeOperation(author, unixTime, []Label{"added"}, nil), nil }) - dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *LabelChangeOperation { - return NewLabelChangeOperation(author, unixTime, nil, []Label{"removed"}) + dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*LabelChangeOperation, entity.Resolvers) { + return NewLabelChangeOperation(author, unixTime, nil, []Label{"removed"}), nil }) } diff --git a/entities/bug/op_set_status_test.go b/entities/bug/op_set_status_test.go index 5bb30265d584e3d013e29df54bc2dc01d40b76ea..0f6d358a19571923321bb7d7e3f7d33fbbaded01 100644 --- a/entities/bug/op_set_status_test.go +++ b/entities/bug/op_set_status_test.go @@ -5,11 +5,12 @@ import ( "github.com/MichaelMure/git-bug/entities/common" "github.com/MichaelMure/git-bug/entities/identity" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/entity/dag" ) func TestSetStatusSerialize(t *testing.T) { - dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *SetStatusOperation { - return NewSetStatusOp(author, unixTime, common.ClosedStatus) + dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*SetStatusOperation, entity.Resolvers) { + return NewSetStatusOp(author, unixTime, common.ClosedStatus), nil }) } diff --git a/entities/bug/op_set_title_test.go b/entities/bug/op_set_title_test.go index 7960ec4fc341bf4a517674cd40da7834c1fc1d46..82425ab49208cb9262a1a18655d2535a279701b0 100644 --- a/entities/bug/op_set_title_test.go +++ b/entities/bug/op_set_title_test.go @@ -4,11 +4,12 @@ import ( "testing" "github.com/MichaelMure/git-bug/entities/identity" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/entity/dag" ) func TestSetTitleSerialize(t *testing.T) { - dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *SetTitleOperation { - return NewSetTitleOp(author, unixTime, "title", "was") + dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*SetTitleOperation, entity.Resolvers) { + return NewSetTitleOp(author, unixTime, "title", "was"), nil }) } diff --git a/entities/bug/operation.go b/entities/bug/operation.go index a02fc780fb6a7db190a48ecfc44910f5586e85da..41d80700af90519dd36b76f0ca3ab68ba40bce9d 100644 --- a/entities/bug/operation.go +++ b/entities/bug/operation.go @@ -32,7 +32,7 @@ type Operation interface { var _ Operation = &dag.NoOpOperation[*Snapshot]{} var _ Operation = &dag.SetMetadataOperation[*Snapshot]{} -func operationUnmarshaller(raw json.RawMessage, resolvers entity.Resolvers) (dag.Operation, error) { +func operationUnmarshaler(raw json.RawMessage, resolvers entity.Resolvers) (dag.Operation, error) { var t struct { OperationType dag.OperationType `json:"type"` } diff --git a/entity/dag/common_test.go b/entity/dag/common_test.go index 713d0734fdf33c1efeb79d1aa027d537c55e0483..f78b09e95848a23d48005368202c1a12af22fac0 100644 --- a/entity/dag/common_test.go +++ b/entity/dag/common_test.go @@ -141,16 +141,7 @@ func makeTestContextInternal(repo repository.ClockedRepo) (identity.Interface, i } resolvers := entity.Resolvers{ - &identity.Identity{}: entity.ResolverFunc(func(id entity.Id) (entity.Interface, error) { - switch id { - case id1.Id(): - return id1, nil - case id2.Id(): - return id2, nil - default: - return nil, identity.ErrIdentityNotExist - } - }), + &identity.Identity{}: entity.MakeResolver(id1, id2), } def := Definition{ diff --git a/entity/dag/entity.go b/entity/dag/entity.go index f9b55bd46d8b9f2a2f2b99c95eef4f98cadcdf8f..8b56127444cc116ab511939da8a166b3fb554fbb 100644 --- a/entity/dag/entity.go +++ b/entity/dag/entity.go @@ -19,6 +19,8 @@ const refsPattern = "refs/%s/%s" const creationClockPattern = "%s-create" const editClockPattern = "%s-edit" +type OperationUnmarshaler func(raw json.RawMessage, resolver entity.Resolvers) (Operation, error) + // Definition hold the details defining one specialization of an Entity. type Definition struct { // the name of the entity (bug, pull-request, ...), for human consumption @@ -26,7 +28,7 @@ type Definition struct { // the Namespace in git references (bugs, prs, ...) Namespace string // a function decoding a JSON message into an Operation - OperationUnmarshaler func(raw json.RawMessage, resolver entity.Resolvers) (Operation, error) + OperationUnmarshaler OperationUnmarshaler // the expected format version number, that can be used for data migration/upgrade FormatVersion uint } diff --git a/entity/dag/example_test.go b/entity/dag/example_test.go index 9c6a4a625965664b0e3b1ce33a0c21839deb9e68..b1511dc681fbf554ea398475d3012336729d1175 100644 --- a/entity/dag/example_test.go +++ b/entity/dag/example_test.go @@ -208,13 +208,13 @@ func NewProjectConfig() *ProjectConfig { var def = dag.Definition{ Typename: "project config", Namespace: "conf", - OperationUnmarshaler: operationUnmarshaller, + OperationUnmarshaler: operationUnmarshaler, FormatVersion: 1, } -// operationUnmarshaller is a function doing the de-serialization of the JSON data into our own +// operationUnmarshaler is a function doing the de-serialization of the JSON data into our own // concrete Operations. If needed, we can use the resolver to connect to other entities. -func operationUnmarshaller(raw json.RawMessage, resolvers entity.Resolvers) (dag.Operation, error) { +func operationUnmarshaler(raw json.RawMessage, resolvers entity.Resolvers) (dag.Operation, error) { var t struct { OperationType dag.OperationType `json:"type"` } diff --git a/entity/dag/op_noop_test.go b/entity/dag/op_noop_test.go index c7710b8364a7e9b1d9650396942f71a78461e560..61497b5b19b85e1effd791e7e8dc8a3b2951f191 100644 --- a/entity/dag/op_noop_test.go +++ b/entity/dag/op_noop_test.go @@ -1,13 +1,19 @@ package dag import ( + "encoding/json" "testing" "github.com/MichaelMure/git-bug/entities/identity" + "github.com/MichaelMure/git-bug/entity" ) func TestNoopSerialize(t *testing.T) { - SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *NoOpOperation[*snapshotMock] { - return NewNoOpOp[*snapshotMock](1, author, unixTime) + SerializeRoundTripTest(t, func(raw json.RawMessage, resolver entity.Resolvers) (Operation, error) { + var op NoOpOperation[*snapshotMock] + err := json.Unmarshal(raw, &op) + return &op, err + }, func(author identity.Interface, unixTime int64) (*NoOpOperation[*snapshotMock], entity.Resolvers) { + return NewNoOpOp[*snapshotMock](1, author, unixTime), nil }) } diff --git a/entity/dag/op_set_metadata_test.go b/entity/dag/op_set_metadata_test.go index 49603021bc2514063df14ac671611434d6bc9676..f4f20e8e8521481d25b0a1c081017b9dc2beb88d 100644 --- a/entity/dag/op_set_metadata_test.go +++ b/entity/dag/op_set_metadata_test.go @@ -1,10 +1,12 @@ package dag import ( + "encoding/json" "testing" "time" "github.com/MichaelMure/git-bug/entities/identity" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/repository" "github.com/stretchr/testify/require" @@ -97,10 +99,14 @@ func TestSetMetadata(t *testing.T) { } func TestSetMetadataSerialize(t *testing.T) { - SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *SetMetadataOperation[*snapshotMock] { + SerializeRoundTripTest(t, func(raw json.RawMessage, resolver entity.Resolvers) (Operation, error) { + var op SetMetadataOperation[*snapshotMock] + err := json.Unmarshal(raw, &op) + return &op, err + }, func(author identity.Interface, unixTime int64) (*SetMetadataOperation[*snapshotMock], entity.Resolvers) { return NewSetMetadataOp[*snapshotMock](1, author, unixTime, "message", map[string]string{ "key1": "value1", "key2": "value2", - }) + }), nil }) } diff --git a/entity/dag/operation_testing.go b/entity/dag/operation_testing.go index 6ebdcae8408aa267d5c4c536dd05aa199be6d32d..0ca47d4b5dd065454c11e71e84c699e13256745b 100644 --- a/entity/dag/operation_testing.go +++ b/entity/dag/operation_testing.go @@ -14,44 +14,30 @@ import ( // SerializeRoundTripTest realize a marshall/unmarshall round-trip in the same condition as with OperationPack, // and check if the recovered operation is identical. -func SerializeRoundTripTest[OpT Operation](t *testing.T, maker func(author identity.Interface, unixTime int64) OpT) { +func SerializeRoundTripTest[OpT Operation]( + t *testing.T, + unmarshaler OperationUnmarshaler, + maker func(author identity.Interface, unixTime int64) (OpT, entity.Resolvers), +) { repo := repository.NewMockRepo() rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") require.NoError(t, err) - op := maker(rene, time.Now().Unix()) + op, resolvers := maker(rene, time.Now().Unix()) // enforce having an id op.Id() - rdt := &roundTripper[OpT]{Before: op, author: rene} - - data, err := json.Marshal(rdt) + data, err := json.Marshal(op) require.NoError(t, err) - err = json.Unmarshal(data, &rdt) + after, err := unmarshaler(data, resolvers) require.NoError(t, err) - require.Equal(t, op, rdt.after) -} - -type roundTripper[OpT Operation] struct { - Before OpT - author identity.Interface - after OpT -} - -func (r *roundTripper[OpT]) MarshalJSON() ([]byte, error) { - return json.Marshal(r.Before) -} - -func (r *roundTripper[OpT]) UnmarshalJSON(data []byte) error { - if err := json.Unmarshal(data, &r.after); err != nil { - return err - } // Set the id from the serialized data - r.after.setId(entity.DeriveId(data)) + after.setId(entity.DeriveId(data)) // Set the author, as OperationPack would do - r.after.setAuthor(r.author) - return nil + after.setAuthor(rene) + + require.Equal(t, op, after) } diff --git a/entity/resolver.go b/entity/resolver.go index d4fe5d3e03bcc38d0edfe2d972dd8f3e11225889..b2f831d77cca724ffbc5e427be30df68de048fef 100644 --- a/entity/resolver.go +++ b/entity/resolver.go @@ -72,3 +72,15 @@ type ResolverFunc func(id Id) (Interface, error) func (fn ResolverFunc) Resolve(id Id) (Interface, error) { return fn(id) } + +// MakeResolver create a resolver able to return the given entities. +func MakeResolver(entities ...Interface) Resolver { + return ResolverFunc(func(id Id) (Interface, error) { + for _, entity := range entities { + if entity.Id() == id { + return entity, nil + } + } + return nil, fmt.Errorf("entity not found") + }) +}