forked from go-git/go-git
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplugin.go
More file actions
155 lines (133 loc) · 4.12 KB
/
Copy pathplugin.go
File metadata and controls
155 lines (133 loc) · 4.12 KB
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Package plugin provides a generic, thread-safe registry for plugin factory
// functions. It enables off-tree implementations to be registered and
// retrieved at runtime.
//
// Each plugin entry is identified by a key value, which is a lightweight
// value type parameterized on the Go type it manages. This means a factory for
// type A cannot be registered under a key meant for type B — the compiler
// will reject it.
//
// The registry freezes automatically on the first call to [Get] for a given
// key: all registrations must happen before the first resolution (typically
// during package init).
package plugin
import (
"errors"
"fmt"
"sync"
)
var (
// ErrFrozen is returned by [Register] when the plugin entry has already
// been resolved via [Get] and can no longer accept new registrations.
ErrFrozen = errors.New("plugin registry is frozen")
// ErrNotFound is returned by [Get] when no factory has been registered
// for the requested plugin key.
ErrNotFound = errors.New("plugin not found")
// ErrNilFactory is returned by [Register] when a nil factory is provided.
ErrNilFactory = errors.New("factory must not be nil")
)
var (
mu sync.RWMutex
entries = map[Name]*entry{}
)
// Name represents the Plugin name.
type Name string
// Register sets the factory for the given key.
// It returns [ErrFrozen] if the key has already been resolved (via [Get]),
// or [ErrNilFactory] if factory is nil.
// Calling Register again on the same key replaces the previous factory.
func Register[T any](key key[T], factory func() T) error {
mu.Lock()
defer mu.Unlock()
e := entries[key.name]
if e == nil {
return fmt.Errorf("plugin: uninitialized key %q", key.name)
}
if e.frozen {
return fmt.Errorf("%w: cannot register %q", ErrFrozen, key.name)
}
if factory == nil {
return ErrNilFactory
}
if e.validate != nil {
if err := e.validate(factory()); err != nil {
return err
}
}
e.factory = factory
return nil
}
// Get calls the factory registered under key and returns a new T.
// The first call to Get for a given key freezes that plugin entry,
// preventing further registrations.
// It returns [ErrNotFound] if no factory has been registered for the key.
func Get[T any](key key[T]) (T, error) {
mu.Lock()
e := entries[key.name]
if e == nil {
mu.Unlock()
var zero T
return zero, fmt.Errorf("plugin: uninitialized key %q", key.name)
}
e.frozen = true
f := e.factory
mu.Unlock()
if f == nil {
var zero T
return zero, fmt.Errorf("%w: %q", ErrNotFound, key.name)
}
return f.(func() T)(), nil
}
// Has reports whether a plugin has been registered for the given key.
func Has[T any](key key[T]) bool {
mu.RLock()
defer mu.RUnlock()
e := entries[key.name]
return e != nil && e.factory != nil
}
// Key identifies a typed plugin entry.
// It is a value type — safe to copy, cannot be nil.
type key[T any] struct {
name Name
}
// newKey creates a new plugin entry key with the given name.
// It panics if a key with the same name has already been created.
func newKey[T any](name Name) key[T] {
return newKeyWithValidator[T](name, nil)
}
// newKeyWithValidator creates a new plugin entry key with the given name
// and optional registration-time validator.
func newKeyWithValidator[T any](name Name, validate func(T) error) key[T] {
mu.Lock()
defer mu.Unlock()
if _, exists := entries[name]; exists {
panic("plugin: duplicate key name: " + name)
}
var wrapped func(any) error
if validate != nil {
wrapped = func(v any) error {
return validate(v.(T))
}
}
entries[name] = &entry{validate: wrapped}
return key[T]{name: name}
}
// entry holds the internal state for a single plugin entry.
type entry struct {
frozen bool
factory any // func() T stored as any; nil means not registered
validate func(any) error
}
// resetEntry clears the factory and unfreezes the plugin entry identified by
// name, restoring it to its initial state. It is accessed from external test
// packages via go:linkname.
//
//nolint:unused // accessed via go:linkname from test files in the root package
func resetEntry(name Name) {
mu.Lock()
defer mu.Unlock()
if e := entries[name]; e != nil {
e.frozen = false
e.factory = nil
}
}