Conversation
Generic inbound filters can only match releases by glob, so a range such as "every release below 2.0" is not expressible. Add a "version" op that parses constraints like ">=1.2.0, <2.0.0" or "~>1.2" and compares them against the version part of a Sentry release. Versions order by semver precedence, so a pre-release ranks below its final release and build codes are ignored. A release without a version, such as a commit hash, never matches. Entries that do not parse are skipped, like invalid glob patterns. Old Relays read the op as unsupported, so a filter using it is inert there and other filters keep working. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Keep only ">", ">=", "<", and "<=". A value without an operator, "=", "!=", and "~>" are no longer parsed, so they are skipped like any invalid entry. Drop the semver crate and order versions with the release parser's own components instead. This handles four components and compares pre-release tags as text, which is how Sentry orders releases. Missing components are zero, so ">1.2" matches "1.2.3", like Sentry's release search. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replace the dedicated "version" op with a release type on the data.
Getters return release fields as Val::Release, and the gt, gte, lt,
and lte conditions order that type by version instead of as text. The
eq and glob conditions keep treating a release as a string, so
existing release filters are unchanged.
Sentry can now emit {"op": "gte", "name": "event.release", "value":
"1.2.0"} without a new op in the DSL. A range is an "and" of two
conditions. Relays that predate this change compare the same JSON
lexicographically, which is accepted since managed Relays update within
days.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Keep the item getters, the sampling context, and the schema untouched. The generic filter wrapper that already adds the envelope client IP retypes the four release paths as Val::Release, so only generic inbound filters compare releases by version. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Move the version ordering into a Release type local to relay-filter, and evaluate the gt, gte, lt, and lte conditions there. A string field in a generic filter comparison is parsed as a release on both sides and ordered by version; any other field compares as the condition does itself. No release paths are listed, and relay-protocol is untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replace the release parser with a Semver type built on the semver crate. A release must carry a full semantic version, optionally behind a Sentry package prefix such as "myapp@1.2.3". Ordering follows semver precedence, so build metadata is ignored and pre-release identifiers compare per the spec. Add an integration test that filters events by a release range through a processing Relay. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replace the semver interpretation of gt, gte, lt, and lte in generic
filters with a dedicated "semver" condition, like "glob" and "cidr".
The comparator is a field of the condition, so the author can validate
it and the version before emitting the condition:
{"op": "semver", "name": "event.release",
"comparator": "gte", "value": "1.2.0"}
Comparators are eq, gt, gte, lt, and lte. An unknown comparator or a
value that is not a semantic version makes the condition unsupported,
which relay_validate_rule_condition reports, and it never matches.
The filter crate needs no special evaluation anymore and goes back to
RuleCondition::matches.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Test each layer for what only it owns. The Semver type covers parsing and precedence. The condition covers a truth table over all comparators, the inputs that never match, and the unknown comparator. One integration test proves the filter end to end with two events. Drop the filter crate test, since the crate has no production change, and the roundtrip test, since the snapshot covers the serialized shape. Reduce the Semver type to parsing and one comparison method. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
| /// | ||
| /// A release is either a version such as `1.2.3-rc.1+build` or a Sentry release with a package, | ||
| /// such as `myapp@1.2.3`. The version must be a full semantic version with three components. | ||
| pub(crate) struct Semver(Version); |
There was a problem hiding this comment.
Why do we need another wrapper here?
There is already sentry_release_parser::Release, can we use that?
There was a problem hiding this comment.
sentry_release_parser::Release afaik matches more as it allows the package specifiers. Depending whether that's wanted I think we either can go with sentry_release_parser::Release or just semver.
I almost assume that supporting the package specifiers is an upside. Though not sure how the matching for partial versions is. For example:
org.example.FooApp@1.0 >= 1.0 # is this true if the package is missing? Should it be?
There was a problem hiding this comment.
Switched to using sentry_release_parser
| /// The comparison to apply between the field and the value. | ||
| pub comparator: SemverComparator, | ||
| /// The semantic version to compare the field against. | ||
| pub value: String, |
There was a problem hiding this comment.
As with IpNetworks, we should make value a parsed semver type (Version or whatever). This then requires Serialize + Deserialize, but it is better than parsing on every evaluation.
| /// | ||
| /// A release is either a version such as `1.2.3-rc.1+build` or a Sentry release with a package, | ||
| /// such as `myapp@1.2.3`. The version must be a full semantic version with three components. | ||
| pub(crate) struct Semver(Version); |
There was a problem hiding this comment.
This can be public as the module itself isn't public. We generally try to avoid these restricted visibility modifiers as pub(crate).
| /// | ||
| /// A release is either a version such as `1.2.3-rc.1+build` or a Sentry release with a package, | ||
| /// such as `myapp@1.2.3`. The version must be a full semantic version with three components. | ||
| pub(crate) struct Semver(Version); |
There was a problem hiding this comment.
sentry_release_parser::Release afaik matches more as it allows the package specifiers. Depending whether that's wanted I think we either can go with sentry_release_parser::Release or just semver.
I almost assume that supporting the package specifiers is an upside. Though not sure how the matching for partial versions is. For example:
org.example.FooApp@1.0 >= 1.0 # is this true if the package is missing? Should it be?
Use sentry-release-parser instead of the semver crate, so the condition reads releases the way Sentry does. Versions have one to four components, and missing components are zero. Define how packages compare. A value without a package matches releases of every package. A value with a package only matches releases of that package. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The module is private, so restricted visibility adds nothing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Make the value of the semver condition a parsed type, like IpNetworks, so the release is parsed when the condition is deserialized and not on every evaluation. A string without a version still deserializes and keeps its text, so one bad condition cannot fail a project config. The condition reports it as unsupported and never matches. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
semverrule condition, next toglobandcidr. It compares the version of a release, so generic inbound filters can express version ranges.{"op": "semver", "name": "event.release", "comparator": "gte", "value": "1.2.0"}. Comparators areeq,gt,gte,lt, andlte.eqcompares by version, so it ignores the build code.sentry-release-parser, the way Sentry reads them. A version has one to four numeric components, and missing components are zero, so1.2compares as1.2.0. A pre-release ranks below its final release, and build codes are ignored. A release without a version, such as a commit hash, never matches.valuewithout a package, such as1.2.0, matches releases of every package. Avaluewith a package, such asmyapp@1.2.0, only matches releases of that package.