1package entity
 2
 3import (
 4	"fmt"
 5	"sync"
 6)
 7
 8// Resolver is an interface to find an Entity from its Id
 9type Resolver interface {
10	Resolve(id Id) (Interface, error)
11}
12
13// Resolvers is a collection of Resolver, for different type of Entity
14type Resolvers map[Interface]Resolver
15
16// Resolve use the appropriate sub-resolver for the given type and find the Entity matching the Id.
17func Resolve[T Interface](rs Resolvers, id Id) (T, error) {
18	var zero T
19	for t, resolver := range rs {
20		switch t.(type) {
21		case T:
22			val, err := resolver.(Resolver).Resolve(id)
23			if err != nil {
24				return zero, err
25			}
26			return val.(T), nil
27		}
28	}
29	return zero, fmt.Errorf("unknown type to resolve")
30}
31
32var _ Resolver = &CachedResolver{}
33
34// CachedResolver is a resolver ensuring that loading is done only once through another Resolver.
35type CachedResolver struct {
36	resolver Resolver
37	mu       sync.RWMutex
38	entities map[Id]Interface
39}
40
41func NewCachedResolver(resolver Resolver) *CachedResolver {
42	return &CachedResolver{
43		resolver: resolver,
44		entities: make(map[Id]Interface),
45	}
46}
47
48func (c *CachedResolver) Resolve(id Id) (Interface, error) {
49	c.mu.RLock()
50	if i, ok := c.entities[id]; ok {
51		c.mu.RUnlock()
52		return i, nil
53	}
54	c.mu.RUnlock()
55
56	c.mu.Lock()
57	defer c.mu.Unlock()
58
59	i, err := c.resolver.Resolve(id)
60	if err != nil {
61		return nil, err
62	}
63	c.entities[id] = i
64	return i, nil
65}
66
67var _ Resolver = ResolverFunc(nil)
68
69// ResolverFunc is a helper to morph a function resolver into a Resolver
70type ResolverFunc func(id Id) (Interface, error)
71
72func (fn ResolverFunc) Resolve(id Id) (Interface, error) {
73	return fn(id)
74}
75
76// MakeResolver create a resolver able to return the given entities.
77func MakeResolver(entities ...Interface) Resolver {
78	return ResolverFunc(func(id Id) (Interface, error) {
79		for _, entity := range entities {
80			if entity.Id() == id {
81				return entity, nil
82			}
83		}
84		return nil, fmt.Errorf("entity not found")
85	})
86}