Skip to content

fix(Engine): Evaluation fails when a numeric trait meets a condition value of another type - #206

Draft
emyller wants to merge 1 commit into
mainfrom
fix/int-trait-decimal-condition
Draft

emyller wants to merge 1 commit into
mainfrom
fix/int-trait-decimal-condition

Conversation

@emyller

@emyller emyller commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Currently slop.

🤖 Generated with Claude Code

…value of another type

Integer traits now share the 64-bit integer comparison path, and float
traits handle unparseable condition values, so such conditions no longer
match instead of throwing FormatException. This matches
flagsmith-flag-engine, which the Flagsmith API uses for remote evaluation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@emyller
emyller requested a review from a team as a code owner October 28, 2026 20:19
@emyller
emyller requested review from khvn26 and removed request for a team October 28, 2026 20:19
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Walkthrough

Integer conditions now use LongOperations. Double conditions return false when parsing the condition value raises FormatException. Tests cover numeric comparisons and equality when the trait value or condition value is non-numeric.

Priority: Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: Moderate · up to 099c1

Out-of-range condition values can still abort local evaluation, including double conditions on older supported runtimes. Handle both conversion failures before merging.

- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

Major · Return a non-match when integer conversion overflows. · Engine.cs:398

Flagsmith.Engine/Engine.cs:398
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Return a non-match when integer conversion overflows.

If an Int32 or Int64 trait meets a condition value such as "9223372036854775808", InvariantConvert.ToInt64 throws OverflowException. This catch handles only FormatException, so GetEvaluationResult aborts instead of returning a non-match. Handle OverflowException here as well. (learn.microsoft.com)


Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: d13c6f2a-57eb-42e4-8de0-938e112071d6
Commits

Reviewing files that changed from the base of the PR and between e2c5320 and 099c12d.

Files selected for processing: 3
  • Flagsmith.Engine/Engine.cs
  • Flagsmith.Engine/Utils/InvariantConvert.cs
  • Flagsmith.EngineTest/EngineTest.cs
Files with no reviewable changes: 1
  • Flagsmith.Engine/Utils/InvariantConvert.cs

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment on lines +414 to +432
private static bool DoubleOperations(double contextValue, Condition condition)
{
switch (condition.Operator)
double conditionValue;
try
{
case Operator.Equal: return contextValue == InvariantConvert.ToInt32(condition.Value.String);
case Operator.NotEqual: return contextValue != InvariantConvert.ToInt32(condition.Value.String);
case Operator.GreaterThan: return contextValue > InvariantConvert.ToInt32(condition.Value.String);
case Operator.GreaterThanInclusive: return contextValue >= InvariantConvert.ToInt32(condition.Value.String);
case Operator.LessThan: return contextValue < InvariantConvert.ToInt32(condition.Value.String);
case Operator.LessThanInclusive: return contextValue <= InvariantConvert.ToInt32(condition.Value.String);
default: throw new ArgumentException("Invalid Operator");
conditionValue = InvariantConvert.ToDouble(condition.Value.String);
}
catch (FormatException)
{
return false;
}
}

private static bool DoubleOperations(double contextValue, Condition condition)
{
switch (condition.Operator)
{
case Operator.Equal: return contextValue == InvariantConvert.ToDouble(condition.Value.String);
case Operator.NotEqual: return contextValue != InvariantConvert.ToDouble(condition.Value.String);
case Operator.GreaterThan: return contextValue > InvariantConvert.ToDouble(condition.Value.String);
case Operator.GreaterThanInclusive: return contextValue >= InvariantConvert.ToDouble(condition.Value.String);
case Operator.LessThan: return contextValue < InvariantConvert.ToDouble(condition.Value.String);
case Operator.LessThanInclusive: return contextValue <= InvariantConvert.ToDouble(condition.Value.String);
case Operator.Equal: return contextValue == conditionValue;
case Operator.NotEqual: return contextValue != conditionValue;
case Operator.GreaterThan: return contextValue > conditionValue;
case Operator.GreaterThanInclusive: return contextValue >= conditionValue;
case Operator.LessThan: return contextValue < conditionValue;
case Operator.LessThanInclusive: return contextValue <= conditionValue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor · Stability & Availability · Quick win

Supported by static analysis

Script executed:

sed -n '410,440p' Flagsmith.Engine/Engine.cs
cat Flagsmith.Engine/Utils/InvariantConvert.cs
rg -n 'TargetFramework|TargetFrameworks|ToDouble\(' --glob '*.csproj' --glob '*.cs' Flagsmith.Engine Flagsmith.EngineTest

Repository: Flagsmith/flagsmith-dotnet-client

Length of output: 4186


Script executed:

set -o pipefail
printf '%s\n' '--- Engine dispatch and evaluation flow ---'
sed -n '1,190p' Flagsmith.Engine/Engine.cs
sed -n '190,380p' Flagsmith.Engine/Engine.cs
printf '%s\n' '--- MatchingFunctionName and DoubleOperations context ---'
sed -n '380,440p' Flagsmith.Engine/Engine.cs
printf '%s\n' '--- Relevant tests and project metadata ---'
rg -n -F --glob '*.cs' -e 'GetEvaluationResult' -e 'MatchingFunctionName' -e 'DoubleOperations' -e 'Trait' -e 'double' Flagsmith.EngineTest
cat Flagsmith.Engine/Flagsmith.Engine.csproj
cat Flagsmith.EngineTest/EngineTest.csproj
printf '%s\n' '--- Available .NET runtime metadata ---'
if command -v dotnet >/dev/null 2>&1; then dotnet --info; else printf '%s\n' 'dotnet executable unavailable'; fi

Repository: Flagsmith/flagsmith-dotnet-client

Length of output: 22973


Script executed:

set -o pipefail
printf '%s\n' '--- dispatch and callers ---'
rg -n -F --glob '*.cs' -e 'GetEvaluationResult' -e 'MatchingFunctionName' -e 'DoubleOperations' -e 'Trait' Flagsmith.Engine Flagsmith.EngineTest
sed -n '250,440p' Flagsmith.Engine/Engine.cs
printf '%s\n' '--- project files ---'
cat Flagsmith.Engine/Flagsmith.Engine.csproj
cat Flagsmith.EngineTest/EngineTest.csproj
printf '%s\n' '--- runtime availability ---'
command -v dotnet >/dev/null 2>&1 && dotnet --info || printf '%s\n' 'dotnet executable unavailable'

Repository: Flagsmith/flagsmith-dotnet-client

Length of output: 14018


Script executed:

set -o pipefail
printf '%s\n' '--- Engine flow ---'
sed -n '1,190p' Flagsmith.Engine/Engine.cs
sed -n '190,440p' Flagsmith.Engine/Engine.cs
printf '%s\n' '--- References and tests ---'
rg -n -F --glob '*.cs' -e 'GetEvaluationResult' -e 'MatchingFunctionName' -e 'DoubleOperations' -e 'System.Double' -e 'double' Flagsmith.Engine Flagsmith.EngineTest
printf '%s\n' '--- Target frameworks ---'
cat Flagsmith.Engine/Flagsmith.Engine.csproj
cat Flagsmith.EngineTest/EngineTest.csproj
printf '%s\n' '--- Available runtime ---'
if command -v dotnet >/dev/null 2>&1; then dotnet --info; else printf '%s\n' 'dotnet executable unavailable'; fi

Repository: Flagsmith/flagsmith-dotnet-client

Length of output: 22939


Catch OverflowException in DoubleOperations.

GetEvaluationResult can route a System.Double trait to DoubleOperations. On .NET Framework and pre-.NET Core 3 runtimes, Convert.ToDouble("1e309", CultureInfo.InvariantCulture) throws OverflowException. The current handler catches only FormatException, so evaluation can fail on older runtimes supported by the netstandard2.0 library target.

Suggested fix
            catch (FormatException)
            {
                return false;
            }
+           catch (OverflowException)
+           {
+               return false;
+           }
Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static bool DoubleOperations(double contextValue, Condition condition)
{
switch (condition.Operator)
double conditionValue;
try
{
case Operator.Equal: return contextValue == InvariantConvert.ToInt32(condition.Value.String);
case Operator.NotEqual: return contextValue != InvariantConvert.ToInt32(condition.Value.String);
case Operator.GreaterThan: return contextValue > InvariantConvert.ToInt32(condition.Value.String);
case Operator.GreaterThanInclusive: return contextValue >= InvariantConvert.ToInt32(condition.Value.String);
case Operator.LessThan: return contextValue < InvariantConvert.ToInt32(condition.Value.String);
case Operator.LessThanInclusive: return contextValue <= InvariantConvert.ToInt32(condition.Value.String);
default: throw new ArgumentException("Invalid Operator");
conditionValue = InvariantConvert.ToDouble(condition.Value.String);
}
catch (FormatException)
{
return false;
}
}
private static bool DoubleOperations(double contextValue, Condition condition)
{
switch (condition.Operator)
{
case Operator.Equal: return contextValue == InvariantConvert.ToDouble(condition.Value.String);
case Operator.NotEqual: return contextValue != InvariantConvert.ToDouble(condition.Value.String);
case Operator.GreaterThan: return contextValue > InvariantConvert.ToDouble(condition.Value.String);
case Operator.GreaterThanInclusive: return contextValue >= InvariantConvert.ToDouble(condition.Value.String);
case Operator.LessThan: return contextValue < InvariantConvert.ToDouble(condition.Value.String);
case Operator.LessThanInclusive: return contextValue <= InvariantConvert.ToDouble(condition.Value.String);
case Operator.Equal: return contextValue == conditionValue;
case Operator.NotEqual: return contextValue != conditionValue;
case Operator.GreaterThan: return contextValue > conditionValue;
case Operator.GreaterThanInclusive: return contextValue >= conditionValue;
case Operator.LessThan: return contextValue < conditionValue;
case Operator.LessThanInclusive: return contextValue <= conditionValue;
private static bool DoubleOperations(double contextValue, Condition condition)
{
double conditionValue;
try
{
conditionValue = InvariantConvert.ToDouble(condition.Value.String);
}
catch (FormatException)
{
return false;
}
catch (OverflowException)
{
return false;
}
switch (condition.Operator)
{
case Operator.Equal: return contextValue == conditionValue;
case Operator.NotEqual: return contextValue != conditionValue;
case Operator.GreaterThan: return contextValue > conditionValue;
case Operator.GreaterThanInclusive: return contextValue >= conditionValue;
case Operator.LessThan: return contextValue < conditionValue;
case Operator.LessThanInclusive: return contextValue <= conditionValue;

@emyller
emyller marked this pull request as draft October 28, 2026 20:42
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.

1 participant