-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathquamina.go
148 lines (135 loc) · 5.38 KB
/
quamina.go
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
package quamina
import (
"errors"
"fmt"
)
// Quamina instances provide the public APIs of this pattern-matching library. A single Quamina instance is
// not thread-safe in that it cannot safely be used simultaneously in multiple goroutines. To re-use a
// Quamina instance concurrently in multiple goroutines, create copies using the Copy API.
type Quamina struct {
flattener Flattener
matcher matcher
mediaTypeSpecified bool
deletionSpecified bool
}
// Option is an interface type used in Quamina's New API to pass in options. By convention, Option names
// have a prefix of "With".
type Option func(q *Quamina) error
// WithMediaType provides a media-type to support the selection of an appropriate Flattener.
// This option call may not be provided more than once, nor can it be combined on the same
// invocation of quamina.New() with the WithFlattener() option.
func WithMediaType(mediaType string) Option {
return func(q *Quamina) error {
if q.flattener != nil {
return errors.New("flattener already specified")
}
if q.mediaTypeSpecified {
return errors.New("media-type specified more than once")
}
switch mediaType {
case "application/json":
q.flattener = newJSONFlattener()
default:
return fmt.Errorf(`media type "%s" is not supported by Quamina`, mediaType)
}
q.mediaTypeSpecified = true
return nil
}
}
// WithFlattener allows the specification of a caller-provided Flattener instance to use on incoming Events.
// This option call may not be provided more than once, nor can it be combined on the same
// invocation of quamina.New() with the WithMediaType() option.
func WithFlattener(f Flattener) Option {
return func(q *Quamina) error {
if q.mediaTypeSpecified {
return errors.New("media-type already specified")
}
if q.flattener != nil {
return errors.New("flattener specified more than once")
}
if f == nil {
return errors.New("nil Flattener")
}
q.flattener = f
return nil
}
}
// WithPatternDeletion arranges, if the argument is true, that this Quamina instance will support
// the DeletePatterns() method. This option call may not be provided more than once.
func WithPatternDeletion(b bool) Option {
return func(q *Quamina) error {
if q.deletionSpecified {
return errors.New("pattern deletion already specified")
}
if b {
q.matcher = newPrunerMatcher(nil)
} else {
q.matcher = newCoreMatcher()
}
q.deletionSpecified = true
return nil
}
}
// WithPatternStorage supplies the Quamina instance with a LivePatternState
// instance to be used to store the active patterns, i.e. those that have been
// added with AddPattern but not deleted with DeletePattern. This option call
// may not be provided more than once.
func WithPatternStorage(ps LivePatternsState) Option {
return func(q *Quamina) error {
if ps == nil {
return errors.New("null PatternStorage")
}
return errors.New(" Pattern storage option not implemented yet")
}
}
// New returns a new Quamina instance. Consult the APIs beginning with “With” for the options
// that may be used to configure the new instance.
func New(opts ...Option) (*Quamina, error) {
var q Quamina
for _, option := range opts {
if err := option(&q); err != nil {
return nil, err
}
}
if (!q.mediaTypeSpecified) && (q.flattener == nil) {
q.flattener = newJSONFlattener()
}
if !q.deletionSpecified {
q.matcher = newCoreMatcher()
}
return &q, nil
}
// Copy produces a new Quamina instance designed to be used safely in parallel with existing instances on different
// goroutines. Copy'ed instances share the same underlying data structures, so a pattern added to any instance
// with AddPattern will be visible in all of them.
func (q *Quamina) Copy() *Quamina {
return &Quamina{matcher: q.matcher, flattener: q.flattener.Copy()}
}
// X is used in the AddPattern and MatchesForEvent APIs to identify the patterns that are added to
// a Quamina instance and are reported by that instance as matching an event. Commonly, X is a string
// used to name the pattern.
type X any
// AddPattern adds a pattern, identified by the x argument, to a Quamina instance.
// patternJSON is a JSON object. error is returned in the case that the PatternJSON is invalid JSON or
// has a leaf which is not provided as an array. AddPattern is single-threaded; if it is invoked concurrently
// from multiple goroutines (in instances created using the Copy method) calls will block until any other
// AddPattern call in progress succeeds.
func (q *Quamina) AddPattern(x X, patternJSON string) error {
return q.matcher.addPattern(x, patternJSON)
}
// DeletePatterns removes patterns identified by the x argument from the Quamina instance; the effect
// is that return values from future calls to MatchesForEvent will not include this x value.
func (q *Quamina) DeletePatterns(x X) error {
return q.matcher.deletePatterns(x)
}
// MatchesForEvent returns a slice of X values which identify patterns that have previously been added to this
// Quamina instance and which “match” the event in the sense described in README. The matches slice may be empty
// if no patterns match. error can be returned in case that the event is not a valid JSON object or contains
// invalid UTF-8 byte sequences.
func (q *Quamina) MatchesForEvent(event []byte) ([]X, error) {
fields, err := q.flattener.Flatten(event, q.matcher.getSegmentsTreeTracker())
if err != nil {
return nil, err
}
return q.matcher.matchesForFields(fields)
}