Skip to content

Commit 888c8ce

Browse files
authored
Merge branch 'main' into dependabot/github_actions/actions/cache-3.2.6
2 parents f7a14b4 + 51f5bbc commit 888c8ce

File tree

10 files changed

+183
-10
lines changed

10 files changed

+183
-10
lines changed

.github/workflows/codeql-analysis.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343

4444
# Initializes the CodeQL tools for scanning.
4545
- name: Initialize CodeQL
46-
uses: github/codeql-action/init@436dbd9100756e97f42f45da571adeebf8270723
46+
uses: github/codeql-action/init@17573ee1cc1b9d061760f3a006fc4aac4f944fd5
4747
with:
4848
languages: ${{ matrix.language }}
4949
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -54,7 +54,7 @@ jobs:
5454
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
5555
# If this step fails, then you should remove it and run the build manually (see below)
5656
- name: Autobuild
57-
uses: github/codeql-action/autobuild@436dbd9100756e97f42f45da571adeebf8270723
57+
uses: github/codeql-action/autobuild@17573ee1cc1b9d061760f3a006fc4aac4f944fd5
5858

5959
# ℹ️ Command-line programs to run using the OS shell.
6060
# 📚 https://git.io/JvXDl
@@ -68,4 +68,4 @@ jobs:
6868
# make release
6969

7070
- name: Perform CodeQL Analysis
71-
uses: github/codeql-action/analyze@436dbd9100756e97f42f45da571adeebf8270723
71+
uses: github/codeql-action/analyze@17573ee1cc1b9d061760f3a006fc4aac4f944fd5

PATTERNS.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,23 @@ Thus, the following Pattern would match both JSON events above:
6060
An **Extended Pattern** **MUST** be a JSON object containing
6161
a single field whose name is called the **Pattern Type**.
6262

63+
### Prefix Pattern
64+
65+
The Pattern Type of a Prefix Pattern is `prefix` and its value
66+
**MUST** be a string.
67+
68+
The following event:
69+
70+
```json
71+
{"a": "alpha"}
72+
```
73+
74+
would be matched by this Prefix Pattern:
75+
76+
```json
77+
{"a": [ { "prefix": "al" } ] }
78+
```
79+
6380
### Exists Pattern
6481

6582
The Pattern Type of an Exists Pattern is `exists` and its
@@ -132,9 +149,9 @@ Consider the following Event:
132149
```
133150
The following Shellstyle Patterns would match it:
134151
```json
135-
{"img": [ {"shellstyle": "*.jpg"} ]}
136-
{"img": [ {"shellstyle": "https://example.com/*"} ]}
137-
{"img": [ {"shellstyle": "https://example.com/*.jpg"} ]}
152+
{"img": [ {"shellstyle": "*.jpg"} ] }
153+
{"img": [ {"shellstyle": "https://example.com/*"} ] }
154+
{"img": [ {"shellstyle": "https://example.com/*.jpg"} ] }
138155
```
139156
## EventBridge Patterns
140157

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ The following Patterns would match it:
113113
}
114114
}
115115
```
116+
```json
117+
{
118+
"Image": {
119+
"Thumbnail": {
120+
"Url": [ "a", { "prefix": "https:" } ] }
121+
}
122+
}
123+
```
116124
The syntax and semantics of Patterns are fully specified
117125
in [Patterns in Quamina](PATTERNS.md).
118126

cl2_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,35 @@ func TestRulerCl2(t *testing.T) {
7878
}
7979
exactMatches := []int{1, 101, 35, 655, 1}
8080

81+
prefixRules := []string{
82+
"{\n" +
83+
" \"properties\": {\n" +
84+
" \"STREET\": [ { \"prefix\": \"AC\" } ]\n" +
85+
" }\n" +
86+
"}",
87+
"{\n" +
88+
" \"properties\": {\n" +
89+
" \"STREET\": [ { \"prefix\": \"BL\" } ]\n" +
90+
" }\n" +
91+
"}",
92+
"{\n" +
93+
" \"properties\": {\n" +
94+
" \"STREET\": [ { \"prefix\": \"DR\" } ]\n" +
95+
" }\n" +
96+
"}",
97+
"{\n" +
98+
" \"properties\": {\n" +
99+
" \"STREET\": [ { \"prefix\": \"FU\" } ]\n" +
100+
" }\n" +
101+
"}",
102+
"{\n" +
103+
" \"properties\": {\n" +
104+
" \"STREET\": [ { \"prefix\": \"RH\" } ]\n" +
105+
" }\n" +
106+
"}",
107+
}
108+
prefixMatches := []int{24, 442, 38, 2387, 328}
109+
81110
anythingButRules := []string{
82111
"{\n" +
83112
" \"properties\": {\n" +
@@ -166,6 +195,10 @@ func TestRulerCl2(t *testing.T) {
166195
bm.addRules(exactRules, exactMatches)
167196
fmt.Printf("EXACT events/sec: %.1f\n", bm.run(t, lines))
168197

198+
bm = newBenchmarker()
199+
bm.addRules(prefixRules, prefixMatches)
200+
fmt.Printf("PREFIX events/sec: %.1f\n", bm.run(t, lines))
201+
169202
bm = newBenchmarker()
170203
bm.addRules(anythingButRules, anythingButMatches)
171204
fmt.Printf("ANYTHING-BUT events/sec: %.1f\n", bm.run(t, lines))

core_matcher_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,20 @@ func TestExerciseMatching(t *testing.T) {
165165
`{"Image": { "Thumbnail": { "Url": [ { "shellstyle": "https://www.example.com/*" } ] } } }`,
166166
`{"Image": { "Thumbnail": { "Url": [ { "shellstyle": "https://www.example.com/*9943" } ] } } }`,
167167
`{"Image": { "Title": [ {"anything-but": ["Pikachu", "Eevee"] } ] } }`,
168+
`{"Image": { "Thumbnail": { "Url": [ { "prefix": "https:" } ] } } }`,
169+
`{"Image": { "Thumbnail": { "Url": [ "a", { "prefix": "https:" } ] } } }`,
168170
}
169171

170172
var err error
173+
blankMatcher := newCoreMatcher()
174+
empty, err := blankMatcher.matchesForJSONEvent([]byte(j))
175+
if err != nil {
176+
t.Error("blank: " + err.Error())
177+
}
178+
if len(empty) != 0 {
179+
t.Error("matches on blank matcher")
180+
}
181+
171182
for i, should := range patternsFromReadme {
172183
m := newCoreMatcher()
173184
err = m.addPattern(fmt.Sprintf("should %d", i), should)
@@ -187,6 +198,7 @@ func TestExerciseMatching(t *testing.T) {
187198
`{"Image": { "Animated": [ { "exists": false } ] } }`,
188199
`{"Image": { "NotThere": [ { "exists": true } ] } }`,
189200
`{"Image": { "IDs": [ { "exists": false } ], "Animated": [ false ] } }`,
201+
`{"Image": { "Thumbnail": { "Url": [ { "prefix": "http:" } ] } } }`,
190202
}
191203
for i, shouldNot := range shouldNotMatches {
192204
m := newCoreMatcher()

pattern.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const (
1919
existsFalseType
2020
shellStyleType
2121
anythingButType
22+
prefixType
2223
)
2324

2425
// typedVal represents the value of a field in a pattern, giving the value and the type of pattern.
@@ -196,12 +197,37 @@ func readSpecialPattern(pb *patternBuild, valsIn []typedVal) (pathVals []typedVa
196197
pathVals, err = readExistsSpecial(pb, pathVals)
197198
case "shellstyle":
198199
pathVals, err = readShellStyleSpecial(pb, pathVals)
200+
case "prefix":
201+
pathVals, err = readPrefixSpecial(pb, pathVals)
199202
default:
200203
err = errors.New("unrecognized in special pattern: " + tt)
201204
}
202205
return
203206
}
204207

208+
func readPrefixSpecial(pb *patternBuild, valsIn []typedVal) (pathVals []typedVal, err error) {
209+
t, err := pb.jd.Token()
210+
if err != nil {
211+
return
212+
}
213+
pathVals = valsIn
214+
215+
prefixString, ok := t.(string)
216+
if !ok {
217+
err = errors.New("value for 'prefix' must be a string")
218+
return
219+
}
220+
val := typedVal{
221+
vType: prefixType,
222+
val: `"` + prefixString + `"`,
223+
}
224+
pathVals = append(pathVals, val)
225+
226+
// has to be } or tokenizer will throw error
227+
_, err = pb.jd.Token()
228+
return
229+
}
230+
205231
func readExistsSpecial(pb *patternBuild, valsIn []typedVal) (pathVals []typedVal, err error) {
206232
t, err := pb.jd.Token()
207233
if err != nil {

pattern_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ func TestPatternFromJSON(t *testing.T) {
6161
`{"xxx": [ { "exists": false, "x": ["a", 3 ] }] }`,
6262
`{"abc": [ {"shellstyle":15} ] }`,
6363
`{"abc": [ {"shellstyle":"a**b"}, "foo" ] }`,
64+
`{"abc": [ {"prefix":23}, "foo" ] }`,
65+
`{"abc": [ {"prefix":["a", "b"]}, "foo" ] }`,
66+
`{"abc": [ {"prefix": - }, "foo" ] }`,
67+
`{"abc": [ {"prefix": - "a" }, "foo" ] }`,
68+
`{"abc": [ {"prefix": "a" {, "foo" ] }`,
6469
}
6570
for _, b := range bads {
6671
_, err := patternFromJSON([]byte(b))

small_table.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,7 @@ func mergeOneDfaStep(step1, step2 *dfaStep, memoize map[dfaStepKey]*dfaStep) *df
107107
return combined
108108
}
109109

110-
// TODO: this works, all the tests pass, but I'm not satisfied with it. My intuition is that you ought
111-
// to be able to come out of this with just one *fieldMatcher
110+
// TODO: this works, all the tests pass, but should to be able to have with just one *fieldMatcher
112111
newTable := newSmallTable[*dfaStep]()
113112
switch {
114113
case step1.fieldTransitions == nil && step2.fieldTransitions == nil:

value_matcher.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ func (m *valueMatcher) transitionOn(val []byte) []*fieldMatcher {
7070
return transitionDfa(fields.startDfa, val, transitions)
7171

7272
default:
73-
// no dfa, no singleton, nothing to do
73+
// no dfa, no singleton, nothing to do, this probably can't happen because a flattener
74+
// shouldn't preserve a field that hasn't appeared in a pattern
7475
return transitions
7576
}
7677
}
@@ -116,6 +117,8 @@ func (m *valueMatcher) addTransition(val typedVal) *fieldMatcher {
116117
var newNfa *smallTable[*nfaStepList]
117118
newNfa, nextField = makeShellStyleAutomaton(valBytes, nil)
118119
newDfa = nfa2Dfa(newNfa)
120+
case prefixType:
121+
newDfa, nextField = makePrefixAutomaton(valBytes, nil)
119122
default:
120123
panic("unknown value type")
121124
}
@@ -145,6 +148,11 @@ func (m *valueMatcher) addTransition(val typedVal) *fieldMatcher {
145148
fields.startDfa = nfa2Dfa(newAutomaton)
146149
m.update(fields)
147150
return nextField
151+
case prefixType:
152+
newAutomaton, nextField := makePrefixAutomaton(valBytes, nil)
153+
fields.startDfa = newAutomaton
154+
m.update(fields)
155+
return nextField
148156
default:
149157
panic("unknown value type")
150158
}
@@ -171,8 +179,10 @@ func (m *valueMatcher) addTransition(val typedVal) *fieldMatcher {
171179
var newNfa *smallTable[*nfaStepList]
172180
newNfa, nextField = makeShellStyleAutomaton(valBytes, nil)
173181
newDfa = nfa2Dfa(newNfa)
182+
case prefixType:
183+
newDfa, nextField = makePrefixAutomaton(valBytes, nil)
174184
default:
175-
panic("unknown val type")
185+
panic("unknown value type")
176186
}
177187

178188
// now table is ready for use, nuke singleton to signal threads to use it
@@ -183,6 +193,29 @@ func (m *valueMatcher) addTransition(val typedVal) *fieldMatcher {
183193
return nextField
184194
}
185195

196+
func makePrefixAutomaton(val []byte, useThisTransition *fieldMatcher) (*smallTable[*dfaStep], *fieldMatcher) {
197+
var nextField *fieldMatcher
198+
199+
if useThisTransition != nil {
200+
nextField = useThisTransition
201+
} else {
202+
nextField = newFieldMatcher()
203+
}
204+
return onePrefixStep(val, 0, nextField), nextField
205+
}
206+
207+
func onePrefixStep(val []byte, index int, nextField *fieldMatcher) *smallTable[*dfaStep] {
208+
var nextStep *dfaStep
209+
210+
// have to stop one short to skip the closing "
211+
if index == len(val)-2 {
212+
nextStep = &dfaStep{table: newSmallTable[*dfaStep](), fieldTransitions: []*fieldMatcher{nextField}}
213+
} else {
214+
nextStep = &dfaStep{table: onePrefixStep(val, index+1, nextField)}
215+
}
216+
return makeSmallDfaTable(nil, []byte{val[index]}, []*dfaStep{nextStep})
217+
}
218+
186219
// makeStringAutomaton creates a utf8-based automaton from a literal string
187220
// using smallTables. Note the addition of a valueTerminator. The implementation
188221
// is recursive because this allows the use of the makeSmallDfaTable call, which

value_matcher_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,46 @@ import (
77
"testing"
88
)
99

10+
func TestInvalidValueTypes(t *testing.T) {
11+
var before []typedVal
12+
addInvalid(t, before)
13+
14+
before = append(before, typedVal{vType: stringType, val: "foo"})
15+
addInvalid(t, before)
16+
17+
before = append(before, typedVal{vType: stringType, val: "bar"})
18+
addInvalid(t, before)
19+
}
20+
func addInvalid(t *testing.T, before []typedVal) {
21+
t.Helper()
22+
defer func() {
23+
if recover() == nil {
24+
t.Errorf("TestAddInvalidTransition should have panicked")
25+
}
26+
}()
27+
28+
panicType := valType(999)
29+
30+
// empty value matcher
31+
m := newValueMatcher()
32+
invalidField := typedVal{
33+
vType: panicType,
34+
val: "one",
35+
}
36+
for _, addBefore := range before {
37+
m.addTransition(addBefore)
38+
}
39+
m.addTransition(invalidField)
40+
}
41+
42+
func TestNoOpTransition(t *testing.T) {
43+
vm := newValueMatcher()
44+
tr := vm.transitionOn([]byte("foo"))
45+
if len(tr) != 0 {
46+
t.Error("matched on empty valuematcher")
47+
}
48+
}
49+
1050
func TestAddTransition(t *testing.T) {
1151
m := newValueMatcher()
1252
v1 := typedVal{

0 commit comments

Comments
 (0)