aboutsummaryrefslogtreecommitdiffstats
path: root/entity
diff options
context:
space:
mode:
Diffstat (limited to 'entity')
-rw-r--r--entity/dag/common_test.go12
-rw-r--r--entity/dag/entity.go63
-rw-r--r--entity/dag/entity_actions.go14
-rw-r--r--entity/dag/entity_actions_test.go34
-rw-r--r--entity/dag/entity_test.go12
-rw-r--r--entity/dag/example_test.go14
-rw-r--r--entity/dag/interface.go6
-rw-r--r--entity/dag/op_set_metadata_test.go6
-rw-r--r--entity/dag/operation.go9
-rw-r--r--entity/err.go33
-rw-r--r--entity/interface.go2
-rw-r--r--entity/resolver.go29
-rw-r--r--entity/streamed.go6
13 files changed, 155 insertions, 85 deletions
diff --git a/entity/dag/common_test.go b/entity/dag/common_test.go
index f78b09e9..51acfa49 100644
--- a/entity/dag/common_test.go
+++ b/entity/dag/common_test.go
@@ -88,6 +88,18 @@ func unmarshaler(raw json.RawMessage, resolvers entity.Resolvers) (Operation, er
}
/*
+ Entity
+*/
+
+type Foo struct {
+ *Entity
+}
+
+func wrapper(e *Entity) *Foo {
+ return &Foo{Entity: e}
+}
+
+/*
Identities + repo + definition
*/
diff --git a/entity/dag/entity.go b/entity/dag/entity.go
index ca674ad7..2028e1b4 100644
--- a/entity/dag/entity.go
+++ b/entity/dag/entity.go
@@ -59,32 +59,35 @@ func New(definition Definition) *Entity {
}
// Read will read and decode a stored local Entity from a repository
-func Read(def Definition, repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (*Entity, error) {
+func Read[EntityT entity.Interface](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (EntityT, error) {
if err := id.Validate(); err != nil {
- return nil, errors.Wrap(err, "invalid id")
+ return *new(EntityT), errors.Wrap(err, "invalid id")
}
ref := fmt.Sprintf("refs/%s/%s", def.Namespace, id.String())
- return read(def, repo, resolvers, ref)
+ return read[EntityT](def, wrapper, repo, resolvers, ref)
}
// readRemote will read and decode a stored remote Entity from a repository
-func readRemote(def Definition, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, id entity.Id) (*Entity, error) {
+func readRemote[EntityT entity.Interface](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, id entity.Id) (EntityT, error) {
if err := id.Validate(); err != nil {
- return nil, errors.Wrap(err, "invalid id")
+ return *new(EntityT), errors.Wrap(err, "invalid id")
}
ref := fmt.Sprintf("refs/remotes/%s/%s/%s", def.Namespace, remote, id.String())
- return read(def, repo, resolvers, ref)
+ return read[EntityT](def, wrapper, repo, resolvers, ref)
}
// read fetch from git and decode an Entity at an arbitrary git reference.
-func read(def Definition, repo repository.ClockedRepo, resolvers entity.Resolvers, ref string) (*Entity, error) {
+func read[EntityT entity.Interface](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, ref string) (EntityT, error) {
rootHash, err := repo.ResolveRef(ref)
+ if err == repository.ErrNotFound {
+ return *new(EntityT), entity.NewErrNotFound(def.Typename)
+ }
if err != nil {
- return nil, err
+ return *new(EntityT), err
}
// Perform a breadth-first search to get a topological order of the DAG where we discover the
@@ -104,7 +107,7 @@ func read(def Definition, repo repository.ClockedRepo, resolvers entity.Resolver
commit, err := repo.ReadCommit(hash)
if err != nil {
- return nil, err
+ return *new(EntityT), err
}
BFSOrder = append(BFSOrder, commit)
@@ -137,26 +140,26 @@ func read(def Definition, repo repository.ClockedRepo, resolvers entity.Resolver
// can have no parents. Said otherwise, the DAG need to have exactly
// one leaf.
if !isFirstCommit && len(commit.Parents) == 0 {
- return nil, fmt.Errorf("multiple leafs in the entity DAG")
+ return *new(EntityT), fmt.Errorf("multiple leafs in the entity DAG")
}
opp, err := readOperationPack(def, repo, resolvers, commit)
if err != nil {
- return nil, err
+ return *new(EntityT), err
}
err = opp.Validate()
if err != nil {
- return nil, err
+ return *new(EntityT), err
}
if isMerge && len(opp.Operations) > 0 {
- return nil, fmt.Errorf("merge commit cannot have operations")
+ return *new(EntityT), fmt.Errorf("merge commit cannot have operations")
}
// Check that the create lamport clock is set (not checked in Validate() as it's optional)
if isFirstCommit && opp.CreateTime <= 0 {
- return nil, fmt.Errorf("creation lamport time not set")
+ return *new(EntityT), fmt.Errorf("creation lamport time not set")
}
// make sure that the lamport clocks causality match the DAG topology
@@ -167,7 +170,7 @@ func read(def Definition, repo repository.ClockedRepo, resolvers entity.Resolver
}
if parentPack.EditTime >= opp.EditTime {
- return nil, fmt.Errorf("lamport clock ordering doesn't match the DAG")
+ return *new(EntityT), fmt.Errorf("lamport clock ordering doesn't match the DAG")
}
// to avoid an attack where clocks are pushed toward the uint64 rollover, make sure
@@ -175,7 +178,7 @@ func read(def Definition, repo repository.ClockedRepo, resolvers entity.Resolver
// we ignore merge commits here to allow merging after a loooong time without breaking anything,
// as long as there is one valid chain of small hops, it's fine.
if !isMerge && opp.EditTime-parentPack.EditTime > 1_000_000 {
- return nil, fmt.Errorf("lamport clock jumping too far in the future, likely an attack")
+ return *new(EntityT), fmt.Errorf("lamport clock jumping too far in the future, likely an attack")
}
}
@@ -187,11 +190,11 @@ func read(def Definition, repo repository.ClockedRepo, resolvers entity.Resolver
for _, opp := range oppMap {
err = repo.Witness(fmt.Sprintf(creationClockPattern, def.Namespace), opp.CreateTime)
if err != nil {
- return nil, err
+ return *new(EntityT), err
}
err = repo.Witness(fmt.Sprintf(editClockPattern, def.Namespace), opp.EditTime)
if err != nil {
- return nil, err
+ return *new(EntityT), err
}
}
@@ -232,13 +235,13 @@ func read(def Definition, repo repository.ClockedRepo, resolvers entity.Resolver
}
}
- return &Entity{
+ return wrapper(&Entity{
Definition: def,
ops: ops,
lastCommit: rootHash,
createTime: createTime,
editTime: editTime,
- }, nil
+ }), nil
}
// readClockNoCheck fetch from git, read and witness the clocks of an Entity at an arbitrary git reference.
@@ -247,6 +250,9 @@ func read(def Definition, repo repository.ClockedRepo, resolvers entity.Resolver
// operation blobs can be implemented instead.
func readClockNoCheck(def Definition, repo repository.ClockedRepo, ref string) error {
rootHash, err := repo.ResolveRef(ref)
+ if err == repository.ErrNotFound {
+ return entity.NewErrNotFound(def.Typename)
+ }
if err != nil {
return err
}
@@ -293,14 +299,9 @@ func readClockNoCheck(def Definition, repo repository.ClockedRepo, ref string) e
return nil
}
-type StreamedEntity struct {
- Entity *Entity
- Err error
-}
-
// ReadAll read and parse all local Entity
-func ReadAll(def Definition, repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan StreamedEntity {
- out := make(chan StreamedEntity)
+func ReadAll[EntityT entity.Interface](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan entity.StreamedEntity[EntityT] {
+ out := make(chan entity.StreamedEntity[EntityT])
go func() {
defer close(out)
@@ -309,19 +310,19 @@ func ReadAll(def Definition, repo repository.ClockedRepo, resolvers entity.Resol
refs, err := repo.ListRefs(refPrefix)
if err != nil {
- out <- StreamedEntity{Err: err}
+ out <- entity.StreamedEntity[EntityT]{Err: err}
return
}
for _, ref := range refs {
- e, err := read(def, repo, resolvers, ref)
+ e, err := read[EntityT](def, wrapper, repo, resolvers, ref)
if err != nil {
- out <- StreamedEntity{Err: err}
+ out <- entity.StreamedEntity[EntityT]{Err: err}
return
}
- out <- StreamedEntity{Entity: e}
+ out <- entity.StreamedEntity[EntityT]{Entity: e}
}
}()
diff --git a/entity/dag/entity_actions.go b/entity/dag/entity_actions.go
index c971f316..2a2bf87f 100644
--- a/entity/dag/entity_actions.go
+++ b/entity/dag/entity_actions.go
@@ -32,13 +32,13 @@ func Push(def Definition, repo repository.Repo, remote string) (string, error) {
// Pull will do a Fetch + MergeAll
// Contrary to MergeAll, this function will return an error if a merge fail.
-func Pull(def Definition, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, author identity.Interface) error {
+func Pull[EntityT entity.Interface](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, author identity.Interface) error {
_, err := Fetch(def, repo, remote)
if err != nil {
return err
}
- for merge := range MergeAll(def, repo, resolvers, remote, author) {
+ for merge := range MergeAll(def, wrapper, repo, resolvers, remote, author) {
if merge.Err != nil {
return merge.Err
}
@@ -68,7 +68,7 @@ func Pull(def Definition, repo repository.ClockedRepo, resolvers entity.Resolver
//
// Note: an author is necessary for the case where a merge commit is created, as this commit will
// have an author and may be signed if a signing key is available.
-func MergeAll(def Definition, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, author identity.Interface) <-chan entity.MergeResult {
+func MergeAll[EntityT entity.Interface](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, author identity.Interface) <-chan entity.MergeResult {
out := make(chan entity.MergeResult)
go func() {
@@ -82,7 +82,7 @@ func MergeAll(def Definition, repo repository.ClockedRepo, resolvers entity.Reso
}
for _, remoteRef := range remoteRefs {
- out <- merge(def, repo, resolvers, remoteRef, author)
+ out <- merge[EntityT](def, wrapper, repo, resolvers, remoteRef, author)
}
}()
@@ -91,14 +91,14 @@ func MergeAll(def Definition, repo repository.ClockedRepo, resolvers entity.Reso
// merge perform a merge to make sure a local Entity is up-to-date.
// See MergeAll for more details.
-func merge(def Definition, repo repository.ClockedRepo, resolvers entity.Resolvers, remoteRef string, author identity.Interface) entity.MergeResult {
+func merge[EntityT entity.Interface](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remoteRef string, author identity.Interface) entity.MergeResult {
id := entity.RefToId(remoteRef)
if err := id.Validate(); err != nil {
return entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error())
}
- remoteEntity, err := read(def, repo, resolvers, remoteRef)
+ remoteEntity, err := read[EntityT](def, wrapper, repo, resolvers, remoteRef)
if err != nil {
return entity.NewMergeInvalidStatus(id,
errors.Wrapf(err, "remote %s is not readable", def.Typename).Error())
@@ -197,7 +197,7 @@ func merge(def Definition, repo repository.ClockedRepo, resolvers entity.Resolve
// an empty operationPack.
// First step is to collect those clocks.
- localEntity, err := read(def, repo, resolvers, localRef)
+ localEntity, err := read[EntityT](def, wrapper, repo, resolvers, localRef)
if err != nil {
return entity.NewMergeError(err, id)
}
diff --git a/entity/dag/entity_actions_test.go b/entity/dag/entity_actions_test.go
index e6888148..fd219644 100644
--- a/entity/dag/entity_actions_test.go
+++ b/entity/dag/entity_actions_test.go
@@ -11,10 +11,10 @@ import (
"github.com/MichaelMure/git-bug/repository"
)
-func allEntities(t testing.TB, bugs <-chan StreamedEntity) []*Entity {
+func allEntities(t testing.TB, bugs <-chan entity.StreamedEntity[*Foo]) []*Foo {
t.Helper()
- var result []*Entity
+ var result []*Foo
for streamed := range bugs {
require.NoError(t, streamed.Err)
@@ -36,10 +36,10 @@ func TestEntityPushPull(t *testing.T) {
_, err = Push(def, repoA, "remote")
require.NoError(t, err)
- err = Pull(def, repoB, resolvers, "remote", id1)
+ err = Pull(def, wrapper, repoB, resolvers, "remote", id1)
require.NoError(t, err)
- entities := allEntities(t, ReadAll(def, repoB, resolvers))
+ entities := allEntities(t, ReadAll(def, wrapper, repoB, resolvers))
require.Len(t, entities, 1)
// B --> remote --> A
@@ -52,10 +52,10 @@ func TestEntityPushPull(t *testing.T) {
_, err = Push(def, repoB, "remote")
require.NoError(t, err)
- err = Pull(def, repoA, resolvers, "remote", id1)
+ err = Pull(def, wrapper, repoA, resolvers, "remote", id1)
require.NoError(t, err)
- entities = allEntities(t, ReadAll(def, repoB, resolvers))
+ entities = allEntities(t, ReadAll(def, wrapper, repoB, resolvers))
require.Len(t, entities, 2)
}
@@ -85,7 +85,7 @@ func TestListLocalIds(t *testing.T) {
listLocalIds(t, def, repoA, 2)
listLocalIds(t, def, repoB, 0)
- err = Pull(def, repoB, resolvers, "remote", id1)
+ err = Pull(def, wrapper, repoB, resolvers, "remote", id1)
require.NoError(t, err)
listLocalIds(t, def, repoA, 2)
@@ -228,7 +228,7 @@ func TestMerge(t *testing.T) {
_, err = Fetch(def, repoB, "remote")
require.NoError(t, err)
- results := MergeAll(def, repoB, resolvers, "remote", id1)
+ results := MergeAll(def, wrapper, repoB, resolvers, "remote", id1)
assertMergeResults(t, []entity.MergeResult{
{
@@ -246,7 +246,7 @@ func TestMerge(t *testing.T) {
// SCENARIO 2
// if the remote and local Entity have the same state, nothing is changed
- results = MergeAll(def, repoB, resolvers, "remote", id1)
+ results = MergeAll(def, wrapper, repoB, resolvers, "remote", id1)
assertMergeResults(t, []entity.MergeResult{
{
@@ -272,7 +272,7 @@ func TestMerge(t *testing.T) {
err = e2A.Commit(repoA)
require.NoError(t, err)
- results = MergeAll(def, repoA, resolvers, "remote", id1)
+ results = MergeAll(def, wrapper, repoA, resolvers, "remote", id1)
assertMergeResults(t, []entity.MergeResult{
{
@@ -297,7 +297,7 @@ func TestMerge(t *testing.T) {
_, err = Fetch(def, repoB, "remote")
require.NoError(t, err)
- results = MergeAll(def, repoB, resolvers, "remote", id1)
+ results = MergeAll(def, wrapper, repoB, resolvers, "remote", id1)
assertMergeResults(t, []entity.MergeResult{
{
@@ -324,10 +324,10 @@ func TestMerge(t *testing.T) {
err = e2A.Commit(repoA)
require.NoError(t, err)
- e1B, err := Read(def, repoB, resolvers, e1A.Id())
+ e1B, err := Read(def, wrapper, repoB, resolvers, e1A.Id())
require.NoError(t, err)
- e2B, err := Read(def, repoB, resolvers, e2A.Id())
+ e2B, err := Read(def, wrapper, repoB, resolvers, e2A.Id())
require.NoError(t, err)
e1B.Append(newOp1(id1, "barbarfoofoo"))
@@ -344,7 +344,7 @@ func TestMerge(t *testing.T) {
_, err = Fetch(def, repoB, "remote")
require.NoError(t, err)
- results = MergeAll(def, repoB, resolvers, "remote", id1)
+ results = MergeAll(def, wrapper, repoB, resolvers, "remote", id1)
assertMergeResults(t, []entity.MergeResult{
{
@@ -365,7 +365,7 @@ func TestMerge(t *testing.T) {
_, err = Fetch(def, repoA, "remote")
require.NoError(t, err)
- results = MergeAll(def, repoA, resolvers, "remote", id1)
+ results = MergeAll(def, wrapper, repoA, resolvers, "remote", id1)
assertMergeResults(t, []entity.MergeResult{
{
@@ -396,10 +396,10 @@ func TestRemove(t *testing.T) {
err = Remove(def, repoA, e.Id())
require.NoError(t, err)
- _, err = Read(def, repoA, resolvers, e.Id())
+ _, err = Read(def, wrapper, repoA, resolvers, e.Id())
require.Error(t, err)
- _, err = readRemote(def, repoA, resolvers, "remote", e.Id())
+ _, err = readRemote(def, wrapper, repoA, resolvers, "remote", e.Id())
require.Error(t, err)
// Remove is idempotent
diff --git a/entity/dag/entity_test.go b/entity/dag/entity_test.go
index e399b6c7..c457eb21 100644
--- a/entity/dag/entity_test.go
+++ b/entity/dag/entity_test.go
@@ -9,7 +9,7 @@ import (
func TestWriteRead(t *testing.T) {
repo, id1, id2, resolver, def := makeTestContext()
- entity := New(def)
+ entity := wrapper(New(def))
require.False(t, entity.NeedCommit())
entity.Append(newOp1(id1, "foo"))
@@ -24,16 +24,16 @@ func TestWriteRead(t *testing.T) {
require.NoError(t, entity.CommitAsNeeded(repo))
require.False(t, entity.NeedCommit())
- read, err := Read(def, repo, resolver, entity.Id())
+ read, err := Read(def, wrapper, repo, resolver, entity.Id())
require.NoError(t, err)
- assertEqualEntities(t, entity, read)
+ assertEqualEntities(t, entity.Entity, read.Entity)
}
func TestWriteReadMultipleAuthor(t *testing.T) {
repo, id1, id2, resolver, def := makeTestContext()
- entity := New(def)
+ entity := wrapper(New(def))
entity.Append(newOp1(id1, "foo"))
entity.Append(newOp2(id2, "bar"))
@@ -43,10 +43,10 @@ func TestWriteReadMultipleAuthor(t *testing.T) {
entity.Append(newOp2(id1, "foobar"))
require.NoError(t, entity.CommitAsNeeded(repo))
- read, err := Read(def, repo, resolver, entity.Id())
+ read, err := Read(def, wrapper, repo, resolver, entity.Id())
require.NoError(t, err)
- assertEqualEntities(t, entity, read)
+ assertEqualEntities(t, entity.Entity, read.Entity)
}
func assertEqualEntities(t *testing.T, a, b *Entity) {
diff --git a/entity/dag/example_test.go b/entity/dag/example_test.go
index b1511dc6..a263eb2b 100644
--- a/entity/dag/example_test.go
+++ b/entity/dag/example_test.go
@@ -200,7 +200,11 @@ type ProjectConfig struct {
}
func NewProjectConfig() *ProjectConfig {
- return &ProjectConfig{Entity: dag.New(def)}
+ return wrapper(dag.New(def))
+}
+
+func wrapper(e *dag.Entity) *ProjectConfig {
+ return &ProjectConfig{Entity: e}
}
// a Definition describes a few properties of the Entity, a sort of configuration to manipulate the
@@ -282,11 +286,7 @@ func (pc ProjectConfig) Compile() *Snapshot {
// Read is a helper to load a ProjectConfig from a Repository
func Read(repo repository.ClockedRepo, id entity.Id) (*ProjectConfig, error) {
- e, err := dag.Read(def, repo, simpleResolvers(repo), id)
- if err != nil {
- return nil, err
- }
- return &ProjectConfig{Entity: e}, nil
+ return dag.Read(def, wrapper, repo, simpleResolvers(repo), id)
}
func simpleResolvers(repo repository.ClockedRepo) entity.Resolvers {
@@ -331,7 +331,7 @@ func Example_entity() {
_ = confRene.Commit(repoRene)
// Isaac pull and read the config
- _ = dag.Pull(def, repoIsaac, simpleResolvers(repoIsaac), "origin", isaac)
+ _ = dag.Pull(def, wrapper, repoIsaac, simpleResolvers(repoIsaac), "origin", isaac)
confIsaac, _ := Read(repoIsaac, confRene.Id())
// Compile gives the current state of the config
diff --git a/entity/dag/interface.go b/entity/dag/interface.go
index 613f60e6..80abaced 100644
--- a/entity/dag/interface.go
+++ b/entity/dag/interface.go
@@ -25,6 +25,10 @@ type Interface[SnapT Snapshot, OpT Operation] interface {
// Commit writes the staging area in Git and move the operations to the packs
Commit(repo repository.ClockedRepo) error
+ // CommitAsNeeded execute a Commit only if necessary. This function is useful to avoid getting an error if the Entity
+ // is already in sync with the repository.
+ CommitAsNeeded(repo repository.ClockedRepo) error
+
// FirstOp lookup for the very first operation of the Entity.
FirstOp() OpT
@@ -32,7 +36,7 @@ type Interface[SnapT Snapshot, OpT Operation] interface {
// For a valid Entity, should never be nil
LastOp() OpT
- // Compile a bug in an easily usable snapshot
+ // Compile an Entity in an easily usable snapshot
Compile() SnapT
// CreateLamportTime return the Lamport time of creation
diff --git a/entity/dag/op_set_metadata_test.go b/entity/dag/op_set_metadata_test.go
index f4f20e8e..07ece013 100644
--- a/entity/dag/op_set_metadata_test.go
+++ b/entity/dag/op_set_metadata_test.go
@@ -12,6 +12,8 @@ import (
"github.com/stretchr/testify/require"
)
+var _ Snapshot = &snapshotMock{}
+
type snapshotMock struct {
ops []Operation
}
@@ -20,6 +22,10 @@ func (s *snapshotMock) AllOperations() []Operation {
return s.ops
}
+func (s *snapshotMock) AppendOperation(op Operation) {
+ s.ops = append(s.ops, op)
+}
+
func TestSetMetadata(t *testing.T) {
snap := &snapshotMock{}
diff --git a/entity/dag/operation.go b/entity/dag/operation.go
index 1a778878..f50d91b6 100644
--- a/entity/dag/operation.go
+++ b/entity/dag/operation.go
@@ -63,6 +63,13 @@ type Operation interface {
setExtraMetadataImmutable(key string, value string)
}
+type OperationWithApply[SnapT Snapshot] interface {
+ Operation
+
+ // Apply the operation to a Snapshot to create the final state
+ Apply(snapshot SnapT)
+}
+
// OperationWithFiles is an optional extension for an Operation that has files dependency, stored in git.
type OperationWithFiles interface {
// GetFiles return the files needed by this operation
@@ -83,6 +90,8 @@ type OperationDoesntChangeSnapshot interface {
type Snapshot interface {
// AllOperations returns all the operations that have been applied to that snapshot, in order
AllOperations() []Operation
+ // AppendOperation add an operation in the list
+ AppendOperation(op Operation)
}
// OpBase implement the common feature that every Operation should support.
diff --git a/entity/err.go b/entity/err.go
index 408e27b4..4453d36e 100644
--- a/entity/err.go
+++ b/entity/err.go
@@ -5,13 +5,34 @@ import (
"strings"
)
+// ErrNotFound is to be returned when an entity, item, element is
+// not found.
+type ErrNotFound struct {
+ typename string
+}
+
+func NewErrNotFound(typename string) *ErrNotFound {
+ return &ErrNotFound{typename: typename}
+}
+
+func (e ErrNotFound) Error() string {
+ return fmt.Sprintf("%s doesn't exist", e.typename)
+}
+
+func IsErrNotFound(err error) bool {
+ _, ok := err.(*ErrNotFound)
+ return ok
+}
+
+// ErrMultipleMatch is to be returned when more than one entity, item, element
+// is found, where only one was expected.
type ErrMultipleMatch struct {
- entityType string
- Matching []Id
+ typename string
+ Matching []Id
}
-func NewErrMultipleMatch(entityType string, matching []Id) *ErrMultipleMatch {
- return &ErrMultipleMatch{entityType: entityType, Matching: matching}
+func NewErrMultipleMatch(typename string, matching []Id) *ErrMultipleMatch {
+ return &ErrMultipleMatch{typename: typename, Matching: matching}
}
func (e ErrMultipleMatch) Error() string {
@@ -22,7 +43,7 @@ func (e ErrMultipleMatch) Error() string {
}
return fmt.Sprintf("Multiple matching %s found:\n%s",
- e.entityType,
+ e.typename,
strings.Join(matching, "\n"))
}
@@ -31,6 +52,8 @@ func IsErrMultipleMatch(err error) bool {
return ok
}
+// ErrInvalidFormat is to be returned when reading on-disk data with an unexpected
+// format or version.
type ErrInvalidFormat struct {
version uint
expected uint
diff --git a/entity/interface.go b/entity/interface.go
index fb4735e4..3035ac88 100644
--- a/entity/interface.go
+++ b/entity/interface.go
@@ -9,4 +9,6 @@ type Interface interface {
// the root of the entity.
// It is acceptable to use such a hash and keep mutating that data as long as Id() is not called.
Id() Id
+ // Validate check if the Entity data is valid
+ Validate() error
}
diff --git a/entity/resolver.go b/entity/resolver.go
index b2f831d7..bd16b901 100644
--- a/entity/resolver.go
+++ b/entity/resolver.go
@@ -5,16 +5,23 @@ import (
"sync"
)
+// Resolved is a minimal interface on which Resolver operates on.
+// Notably, this operates on Entity and Excerpt in the cache.
+type Resolved interface {
+ // Id returns the object identifier.
+ Id() Id
+}
+
// Resolver is an interface to find an Entity from its Id
type Resolver interface {
- Resolve(id Id) (Interface, error)
+ Resolve(id Id) (Resolved, error)
}
// Resolvers is a collection of Resolver, for different type of Entity
-type Resolvers map[Interface]Resolver
+type Resolvers map[Resolved]Resolver
// Resolve use the appropriate sub-resolver for the given type and find the Entity matching the Id.
-func Resolve[T Interface](rs Resolvers, id Id) (T, error) {
+func Resolve[T Resolved](rs Resolvers, id Id) (T, error) {
var zero T
for t, resolver := range rs {
switch t.(type) {
@@ -35,17 +42,17 @@ var _ Resolver = &CachedResolver{}
type CachedResolver struct {
resolver Resolver
mu sync.RWMutex
- entities map[Id]Interface
+ entities map[Id]Resolved
}
func NewCachedResolver(resolver Resolver) *CachedResolver {
return &CachedResolver{
resolver: resolver,
- entities: make(map[Id]Interface),
+ entities: make(map[Id]Resolved),
}
}
-func (c *CachedResolver) Resolve(id Id) (Interface, error) {
+func (c *CachedResolver) Resolve(id Id) (Resolved, error) {
c.mu.RLock()
if i, ok := c.entities[id]; ok {
c.mu.RUnlock()
@@ -64,18 +71,18 @@ func (c *CachedResolver) Resolve(id Id) (Interface, error) {
return i, nil
}
-var _ Resolver = ResolverFunc(nil)
+var _ Resolver = ResolverFunc[Resolved](nil)
// ResolverFunc is a helper to morph a function resolver into a Resolver
-type ResolverFunc func(id Id) (Interface, error)
+type ResolverFunc[EntityT Resolved] func(id Id) (EntityT, error)
-func (fn ResolverFunc) Resolve(id Id) (Interface, error) {
+func (fn ResolverFunc[EntityT]) Resolve(id Id) (Resolved, 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) {
+func MakeResolver(entities ...Resolved) Resolver {
+ return ResolverFunc[Resolved](func(id Id) (Resolved, error) {
for _, entity := range entities {
if entity.Id() == id {
return entity, nil
diff --git a/entity/streamed.go b/entity/streamed.go
new file mode 100644
index 00000000..789224a3
--- /dev/null
+++ b/entity/streamed.go
@@ -0,0 +1,6 @@
+package entity
+
+type StreamedEntity[EntityT Interface] struct {
+ Entity EntityT
+ Err error
+}