aboutsummaryrefslogtreecommitdiffstats
path: root/entity/resolver.go
blob: bd16b901cfc8d8d5519e6244173db87b18ff5e67 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package entity

import (
	"fmt"
	"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) (Resolved, error)
}

// Resolvers is a collection of Resolver, for different type of Entity
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 Resolved](rs Resolvers, id Id) (T, error) {
	var zero T
	for t, resolver := range rs {
		switch t.(type) {
		case T:
			val, err := resolver.(Resolver).Resolve(id)
			if err != nil {
				return zero, err
			}
			return val.(T), nil
		}
	}
	return zero, fmt.Errorf("unknown type to resolve")
}

var _ Resolver = &CachedResolver{}

// CachedResolver is a resolver ensuring that loading is done only once through another Resolver.
type CachedResolver struct {
	resolver Resolver
	mu       sync.RWMutex
	entities map[Id]Resolved
}

func NewCachedResolver(resolver Resolver) *CachedResolver {
	return &CachedResolver{
		resolver: resolver,
		entities: make(map[Id]Resolved),
	}
}

func (c *CachedResolver) Resolve(id Id) (Resolved, error) {
	c.mu.RLock()
	if i, ok := c.entities[id]; ok {
		c.mu.RUnlock()
		return i, nil
	}
	c.mu.RUnlock()

	c.mu.Lock()
	defer c.mu.Unlock()

	i, err := c.resolver.Resolve(id)
	if err != nil {
		return nil, err
	}
	c.entities[id] = i
	return i, nil
}

var _ Resolver = ResolverFunc[Resolved](nil)

// ResolverFunc is a helper to morph a function resolver into a Resolver
type ResolverFunc[EntityT Resolved] func(id Id) (EntityT, 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 ...Resolved) Resolver {
	return ResolverFunc[Resolved](func(id Id) (Resolved, error) {
		for _, entity := range entities {
			if entity.Id() == id {
				return entity, nil
			}
		}
		return nil, fmt.Errorf("entity not found")
	})
}