Skip to content

feat(flags): add semver targeting to local evaluation#166

Merged
dmarticus merged 4 commits intomasterfrom
feat/semver-targeting
Mar 4, 2026
Merged

feat(flags): add semver targeting to local evaluation#166
dmarticus merged 4 commits intomasterfrom
feat/semver-targeting

Conversation

@dmarticus
Copy link
Contributor

@dmarticus dmarticus commented Mar 2, 2026

Summary

  • Add 9 semver comparison operators to the property matching logic used during local feature flag evaluation
  • Implement inline semver parsing without external dependencies (~170 lines)
  • Add comprehensive test coverage (~650 lines)

This matches the behavior already implemented in:

Operators Added

Operator Description Example
semver_eq Exact match 1.2.3 == 1.2.3
semver_neq Not equal 1.2.3 != 1.2.4
semver_gt Greater than 1.2.4 > 1.2.3
semver_gte Greater than or equal 1.2.3 >= 1.2.3
semver_lt Less than 1.2.2 < 1.2.3
semver_lte Less than or equal 1.2.3 <= 1.2.3
semver_tilde Patch-level range ~1.2.3 → >=1.2.3 <1.3.0
semver_caret Compatible-with range ^1.2.3 → >=1.2.3 <2.0.0
semver_wildcard Wildcard range 1.2.* → >=1.2.0 <1.3.0

Semver Parsing

The SemanticVersion helper class handles:

  • v/V prefix stripping (v1.2.31.2.3)
  • Whitespace trimming
  • Pre-release suffix stripping (1.2.3-alpha1.2.3)
  • Build metadata stripping (1.2.3+build.1231.2.3)
  • Partial versions (1.21.2.0)
  • Extra components ignored (1.2.3.41.2.3)

No external semver library is used—parsing is implemented inline per SDK guidelines.

Test plan

  • 132 unit tests for SemanticVersion parsing and comparison
  • 80 integration tests for semver operators in LocalEvaluator
  • All existing LocalEvaluator tests pass (258 total)
  • Build succeeds on both netstandard2.1 and net8.0
  • Tests cover all operators with true/false cases
  • Tests cover edge cases: v-prefix, whitespace, pre-release suffixes, partial versions, leading zeros
  • Tests cover error handling: missing keys, null values, invalid semver

🤖 Generated with Claude Code

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

posthog-go Compliance Report

Date: 2026-03-04 23:16:06 UTC
Duration: 105789ms

✅ All Tests Passed!

29/29 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 609ms
Format Validation.Event Has Uuid 607ms
Format Validation.Event Has Lib Properties 607ms
Format Validation.Distinct Id Is String 607ms
Format Validation.Token Is Present 607ms
Format Validation.Custom Properties Preserved 607ms
Format Validation.Event Has Timestamp 607ms
Retry Behavior.Retries On 503 5612ms
Retry Behavior.Does Not Retry On 400 2610ms
Retry Behavior.Does Not Retry On 401 2609ms
Retry Behavior.Respects Retry After Header 5614ms
Retry Behavior.Implements Backoff 15623ms
Retry Behavior.Retries On 500 5613ms
Retry Behavior.Retries On 502 5612ms
Retry Behavior.Retries On 504 5613ms
Retry Behavior.Max Retries Respected 15620ms
Deduplication.Generates Unique Uuids 616ms
Deduplication.Preserves Uuid On Retry 5608ms
Deduplication.Preserves Uuid And Timestamp On Retry 10615ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 5614ms
Deduplication.No Duplicate Events In Batch 611ms
Deduplication.Different Events Have Different Uuids 610ms
Compression.Sends Gzip When Enabled 606ms
Batch Format.Uses Proper Batch Structure 607ms
Batch Format.Flush With No Events Sends Nothing 605ms
Batch Format.Multiple Events Batched Together 611ms
Error Handling.Does Not Retry On 403 2609ms
Error Handling.Does Not Retry On 413 2609ms
Error Handling.Retries On 408 5613ms

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds semantic-version (semver) targeting support to the local feature flag evaluator, aligning Go SDK behavior with existing implementations in other PostHog components.

Changes:

  • Bump SDK version to 1.11.0.
  • Add 9 semver operators to matchProperty, plus inline semver parsing and range bound computation.
  • Add extensive unit/integration test coverage for semver parsing and operator behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
version.go Version bump to reflect new feature flag targeting capabilities.
featureflags.go Implements semver operators in matchProperty and adds semver parsing/range helpers.
feature_flags_matching_test.go Adds parsing/comparison tests and operator-level matching tests for semver behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

featureflags.go Outdated

overrideParsed, err := parseSemver(overrideStr)
if err != nil {
return false, &InconclusiveMatchError{fmt.Sprintf("person property value '%s' is not a valid semver", overrideStr)}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message says "person property value", but matchProperty is used for both person and group properties and doesn't check the property type here. Consider changing this to a neutral "property value" (and likewise in the other semver operator branches) to avoid misleading diagnostics.

Suggested change
return false, &InconclusiveMatchError{fmt.Sprintf("person property value '%s' is not a valid semver", overrideStr)}
return false, &InconclusiveMatchError{fmt.Sprintf("property value '%s' is not a valid semver", overrideStr)}

Copilot uses AI. Check for mistakes.
featureflags.go Outdated
Comment on lines +1106 to +1107
return false, &InconclusiveMatchError{fmt.Sprintf("person property value '%s' is not a valid semver", overrideStr)}
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message says "person property value", but matchProperty is used for both person and group properties and doesn't check the property type here. Consider changing this to a neutral "property value" to avoid misleading diagnostics.

Copilot uses AI. Check for mistakes.
featureflags.go Outdated
Comment on lines +1131 to +1132
return false, &InconclusiveMatchError{fmt.Sprintf("person property value '%s' is not a valid semver", overrideStr)}
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message says "person property value", but matchProperty is used for both person and group properties and doesn't check the property type here. Consider changing this to a neutral "property value" to avoid misleading diagnostics.

Copilot uses AI. Check for mistakes.
featureflags.go Outdated
Comment on lines +1156 to +1157
return false, &InconclusiveMatchError{fmt.Sprintf("person property value '%s' is not a valid semver", overrideStr)}
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message says "person property value", but matchProperty is used for both person and group properties and doesn't check the property type here. Consider changing this to a neutral "property value" to avoid misleading diagnostics.

Copilot uses AI. Check for mistakes.
featureflags.go Outdated
Comment on lines +1320 to +1324
if idx := strings.Index(text, "-"); idx >= 0 {
text = text[:idx]
}
if idx := strings.Index(text, "+"); idx >= 0 {
text = text[:idx]
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The semver suffix stripping treats the first '-' or '+' anywhere in the string as a pre-release/build delimiter. This means malformed inputs like "1-2.3" or "1.-2.3" can be truncated and parsed as valid versions (e.g. "1"). Consider validating the core version format (digits/dots only) before stripping suffixes so invalid cores fail parsing instead of silently changing meaning.

Copilot uses AI. Check for mistakes.
Comment on lines +1341 to +1345
minor := 0
if len(parts) > 1 && parts[1] != "" {
minor, err = strconv.Atoi(parts[1])
if err != nil {
return semverTuple{}, fmt.Errorf("invalid semver: minor version '%s' is not a number", parts[1])
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic defaults a missing/empty minor component to 0 (and similarly for patch below), which makes malformed strings like "1..3" or "1." parse successfully. If the intent is only to support shortened forms like "1" and "1.2", consider returning an error when a component is present but empty (i.e. a "." was provided with no digits).

Copilot uses AI. Check for mistakes.
- Change "person property value" to "property value" in error messages
  since matchProperty is used for both person and group properties
- Validate core version format before stripping pre-release/build suffixes
  to reject malformed inputs like "1-2.3" or "-alpha"
- Return error when a version component is present but empty (e.g., "1..3"
  or "1.") instead of silently defaulting to 0
- Add tests for newly rejected malformed inputs
@dmarticus dmarticus merged commit 1e277f9 into master Mar 4, 2026
11 of 12 checks passed
@dmarticus dmarticus deleted the feat/semver-targeting branch March 4, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants