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