fix(eventbridge): boolean/numeric pattern values are matched correctly#1889
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes EventBridge event-pattern matching for detail fields so exact matches are type-aware (booleans and numbers no longer compare against stringified event values), aligning Floci behavior with AWS semantics and the AWS CLI TestEventPattern behavior.
Changes:
- Update
EventBridgeServicedetail matching to carryJsonNodevalues end-to-end and apply type-aware exact matching for strings/booleans/numbers. - Restrict string-only operators (
prefix,suffix,equals-ignore-case) to apply only when the event value is actually a JSON string. - Add integration tests covering boolean/numeric literal matching, string-vs-primitive mismatches, numeric string-representation equality,
exists, andanything-butbehavior for numbers.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/main/java/io/github/hectorvent/floci/services/eventbridge/EventBridgeService.java | Makes detail pattern matching type-aware by comparing JsonNode values (fixing boolean/numeric semantics and improving anything-but). |
| src/test/java/io/github/hectorvent/floci/services/eventbridge/EventBridgeTestEventPatternIntegrationTest.java | Adds integration tests validating AWS-compatible matching for boolean/numeric detail values and related operators. |
|
| Filename | Overview |
|---|---|
| src/main/java/io/github/hectorvent/floci/services/eventbridge/EventBridgeService.java | Core fix: passes JsonNode end-to-end through matchesDetailNode/matchesArrayField/matchesSingleElement; adds type-aware matchesExactValue for string/boolean/number/null; fixes anything-but to use matchesExactValue; exists correctly treats NullNode as present. Logic is sound. |
| src/test/java/io/github/hectorvent/floci/services/eventbridge/EventBridgeTestEventPatternIntegrationTest.java | Adds 20 integration tests covering boolean literal match/mismatch, numeric literal match/mismatch, type cross-contamination, exists with null/missing fields, null literal matching, anything-but with numbers, and scientific-notation equality. Coverage is thorough. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[matchesDetailNode] -->|envelope: source/detail-type/account/region| B[matchesArrayField - String overload]
B --> C[TextNode.valueOf wrap]
C --> D[matchesArrayField - JsonNode overload]
A -->|detail field| D
D --> E{for each pattern element}
E --> F[matchesSingleElement]
F -->|element.isObject| G{filter type}
G -->|prefix/suffix/equals-ignore-case| H[value != null and string op - text only]
G -->|anything-but array| I[matchesExactValue each exclusion]
G -->|anything-but prefix| J[string prefix check]
G -->|exists| K[present = actual != null - NullNode = present]
F -->|literal element| L[matchesExactValue]
L -->|null| M[actual.isNull]
L -->|string| N[actual.isTextual and equals]
L -->|boolean| O[actual.isBoolean and value equals]
L -->|number| P[actual.isNumber and decimalValue compareTo == 0]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[matchesDetailNode] -->|envelope: source/detail-type/account/region| B[matchesArrayField - String overload]
B --> C[TextNode.valueOf wrap]
C --> D[matchesArrayField - JsonNode overload]
A -->|detail field| D
D --> E{for each pattern element}
E --> F[matchesSingleElement]
F -->|element.isObject| G{filter type}
G -->|prefix/suffix/equals-ignore-case| H[value != null and string op - text only]
G -->|anything-but array| I[matchesExactValue each exclusion]
G -->|anything-but prefix| J[string prefix check]
G -->|exists| K[present = actual != null - NullNode = present]
F -->|literal element| L[matchesExactValue]
L -->|null| M[actual.isNull]
L -->|string| N[actual.isTextual and equals]
L -->|boolean| O[actual.isBoolean and value equals]
L -->|number| P[actual.isNumber and decimalValue compareTo == 0]
Reviews (3): Last reviewed commit: "fix(eventbridge): make null checks tight..." | Re-trigger Greptile
Summary
Fixes #1888
Problem
EventBridge pattern matching inverted AWS's type semantics for boolean and numeric values in
detail:{"detail": {"enabled": ["true"]}}(string pattern) wrongly matched the JSON booleantrue{"detail": {"enabled": [true]}}(boolean pattern) could never match anything["5"]matched the number5, while[5]matched nothingTwo defects in
EventBridgeService:matchesDetailNodecollapsed the event's detail value to aStringviaasText()before comparison, destroying type information so"true".equals("true")succeeded for the string pattern.matchesSingleElementhad noisBoolean()/isNumber()branches, so non-textual pattern literals fell through toreturn false.Fix
JsonNodeend-to-end (matchesArrayField(JsonNode pattern, JsonNode actual)); envelope fields (source, detail-type, account, region, resources), which are inherently strings, delegate through a thinTextNode-wrapping overload.matchesExactValue: strings match strings, booleans match booleans by value, numbers match numbers by normalized numeric value (verified against real AWS:[3.0e2]matches300.0).prefix,suffix,equals-ignore-case) now only apply when the event value is actually a string, matching AWS behavior.existschecks key presence regardless of value type; a key carrying the JSON literalnullcounts as present (exists: truematches it,exists: falsedoes not). The pre-existing code treated null-valued keys as absent (NullNode.asText(defaultValue)returns the default, so the old stringified value was Javanull).[null]literal pattern requires the key to be present with anullvalue; a missing key does not match (verified against real AWS).anything-butreusesmatchesExactValue, which also fixes a latent bug: numericanything-but: [5]previously excluded nothing because only textual elements were checked.Both
TestEventPatternand real rule dispatch funnel through the samematchesDetailNode, so one fix covers both paths.Type of change
fix:)feat:)feat!:orfix!:)Checklist
./mvnw testpasses locally