-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathclassifier.go
More file actions
176 lines (135 loc) · 3.95 KB
/
classifier.go
File metadata and controls
176 lines (135 loc) · 3.95 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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package task
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"github.com/0xef53/go-task/classifiers"
"github.com/google/uuid"
)
var (
ErrRegistrationFailed = errors.New("cannot register")
ErrAssignmentFailed = errors.New("cannot assign")
)
// TaskClassifier defines the interface for managing task classification.
type TaskClassifier interface {
Assign(context.Context, classifiers.Options, string) error
Unassign(string)
Get(...string) []string
Len() int
}
// TaskClassifierDefinition represents the configuration of a classifier for a specific task.
type TaskClassifierDefinition struct {
Name string
Opts classifiers.Options
}
// Validate checks the [TaskClassifierDefinition] for correctness.
func (o *TaskClassifierDefinition) Validate() error {
o.Name = strings.TrimSpace(o.Name)
if len(o.Name) == 0 {
return fmt.Errorf("empty classifier name")
}
if o.Opts == nil {
return fmt.Errorf("empty classifier options")
}
return nil
}
// rootClassifier manages a set of [TaskClassifier] instances.
type rootClassifier struct {
mu sync.Mutex
table map[string]TaskClassifier
aliases map[string]string
}
// newRootClassifier returns a new [rootClassifier] instance.
func newRootClassifier() *rootClassifier {
return &rootClassifier{
table: make(map[string]TaskClassifier),
aliases: make(map[string]string),
}
}
// Register registers a [TaskClassifier] instance under one or more names.
// If multiple names are specified, the first one will be the primary one,
// and the rest will be aliases.
// If no names are provided, a default name is generated based on the classifier's type.
//
// Returns the list of registered names or an error if any name already exists
// or the classifier is nil.
func (r *rootClassifier) Register(c TaskClassifier, names ...string) ([]string, error) {
r.mu.Lock()
defer r.mu.Unlock()
if c == nil {
return nil, fmt.Errorf("%w: empty classifier interface", ErrRegistrationFailed)
}
if len(names) == 0 {
ff := strings.Split(strings.TrimLeft(fmt.Sprintf("%T", c), "*"), ".")
names = []string{
fmt.Sprintf("%s-%s", ff[len(ff)-1], strings.Split(uuid.New().String(), "-")[0]),
}
}
for _, n := range names {
if _, found := r.aliases[n]; found {
return nil, fmt.Errorf("%w: name already exists: %s", ErrRegistrationFailed, n)
}
}
for _, n := range names {
r.aliases[n] = names[0]
}
r.table[names[0]] = c
return names, nil
}
// Deregister removes the [TaskClassifier] and all associated aliases from the list.
func (r *rootClassifier) Deregister(name string) error {
r.mu.Lock()
defer r.mu.Unlock()
var mainName string
for alias, v := range r.aliases {
if alias == name {
mainName = v
delete(r.aliases, alias)
break
}
}
for alias, v := range r.aliases {
if v == mainName {
delete(r.aliases, alias)
}
}
delete(r.table, mainName)
return nil
}
// Assign associates a task identified by tid with a classifier defined by def.
func (r *rootClassifier) Assign(ctx context.Context, def *TaskClassifierDefinition, tid string) error {
if def == nil {
return fmt.Errorf("%w: empty classifier definition", ErrAssignmentFailed)
}
if err := def.Validate(); err != nil {
return fmt.Errorf("%w: %w", ErrAssignmentFailed, err)
}
r.mu.Lock()
if c, found := r.table[def.Name]; found {
r.mu.Unlock()
return c.Assign(ctx, def.Opts, tid)
}
r.mu.Unlock()
return fmt.Errorf("%w: classifier not found: %s", ErrAssignmentFailed, def.Name)
}
// Unassign removes the association for the task identified by tid from all registered classifiers.
func (r *rootClassifier) Unassign(tid string) {
r.mu.Lock()
defer r.mu.Unlock()
for _, c := range r.table {
c.Unassign(tid)
}
}
// Get returns a slice of task IDs from all registered classifiers
// that match the provided labels.
func (r *rootClassifier) Get(labels ...string) []string {
r.mu.Lock()
defer r.mu.Unlock()
tids := make([]string, 0, 1)
for _, c := range r.table {
tids = append(tids, c.Get(labels...)...)
}
return tids
}