Skip to content

Commit 7f59df3

Browse files
feature: add semantic version types (#236)
* Added the compare version method semantic version methods and lessOrEqual and GreaterOrEqual method * nit fix * code refact * Added Semantic versioning and some unit tests * Added additional unit tests * alternate implementation * Refact and nit fix * Updated throwing exception * moved all common logic to single method to avoid code repitetion * Added documentation * refactored baseCondition * replaced assert that with particular expected condition * added invalid scenarios * added invalid tests and condition to deal them Co-authored-by: Sohail Hussain <mirza.sohailhussain@gmail.com>
1 parent 2df2bfb commit 7f59df3

File tree

10 files changed

+612
-31
lines changed

10 files changed

+612
-31
lines changed

OptimizelySDK.Net35/OptimizelySDK.Net35.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454
</Compile>
5555
<Compile Include="..\OptimizelySDK\AudienceConditions\BaseCondition.cs">
5656
<Link>AudienceConditions\BaseCondition.cs</Link>
57+
</Compile>
58+
<Compile Include="..\OptimizelySDK\AudienceConditions\SemanticVersion.cs">
59+
<Link>AudienceConditions\SemanticVersion.cs</Link>
5760
</Compile>
5861
<Compile Include="..\OptimizelySDK\AudienceConditions\EmptyCondition.cs">
5962
<Link>AudienceConditions\EmptyCondition.cs</Link>

OptimizelySDK.Net40/OptimizelySDK.Net40.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656
</Compile>
5757
<Compile Include="..\OptimizelySDK\AudienceConditions\BaseCondition.cs">
5858
<Link>AudienceConditions\BaseCondition.cs</Link>
59+
</Compile>
60+
<Compile Include="..\OptimizelySDK\AudienceConditions\SemanticVersion.cs">
61+
<Link>AudienceConditions\SemanticVersion.cs</Link>
5962
</Compile>
6063
<Compile Include="..\OptimizelySDK\AudienceConditions\EmptyCondition.cs">
6164
<Link>AudienceConditions\EmptyCondition.cs</Link>

OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<Compile Include="..\OptimizelySDK\AudienceConditions\AndCondition.cs" />
1818
<Compile Include="..\OptimizelySDK\AudienceConditions\AudienceIdCondition.cs" />
1919
<Compile Include="..\OptimizelySDK\AudienceConditions\BaseCondition.cs" />
20+
<Compile Include="..\OptimizelySDK\AudienceConditions\SemanticVersion.cs" />
2021
<Compile Include="..\OptimizelySDK\AudienceConditions\EmptyCondition.cs" />
2122
<Compile Include="..\OptimizelySDK\AudienceConditions\ICondition.cs" />
2223
<Compile Include="..\OptimizelySDK\AudienceConditions\NotCondition.cs" />

OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
<Compile Include="..\OptimizelySDK\AudienceConditions\BaseCondition.cs">
2929
<Link>AudienceConditions\BaseCondition.cs</Link>
3030
</Compile>
31+
<Compile Include="..\OptimizelySDK\AudienceConditions\SemanticVersion.cs">
32+
<Link>AudienceConditions\SemanticVersion.cs</Link>
33+
</Compile>
3134
<Compile Include="..\OptimizelySDK\AudienceConditions\EmptyCondition.cs">
3235
<Link>AudienceConditions\EmptyCondition.cs</Link>
3336
</Compile>

OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs

Lines changed: 279 additions & 0 deletions
Large diffs are not rendered by default.

OptimizelySDK/AudienceConditions/BaseCondition.cs

Lines changed: 103 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,26 @@ public class BaseCondition : ICondition
7777
return ExactEvaluator;
7878
case AttributeMatchTypes.EXIST:
7979
return ExistEvaluator;
80+
case AttributeMatchTypes.GREATER_OR_EQUAL:
81+
return GreaterOrEqualThanEvaluator;
8082
case AttributeMatchTypes.GREATER_THAN:
8183
return GreaterThanEvaluator;
84+
case AttributeMatchTypes.LESS_OR_EQUAL:
85+
return LessOrEqualThanEvaluator;
8286
case AttributeMatchTypes.LESS_THAN:
8387
return LessThanEvaluator;
8488
case AttributeMatchTypes.SUBSTRING:
8589
return SubstringEvaluator;
90+
case AttributeMatchTypes.SEMVER_EQ:
91+
return SemanticVersionEqualEvaluator;
92+
case AttributeMatchTypes.SEMVER_GE:
93+
return SemanticVersionGreaterOrEqualEvaluator;
94+
case AttributeMatchTypes.SEMVER_GT:
95+
return SemanticVersionGreaterEvaluator;
96+
case AttributeMatchTypes.SEMVER_LE:
97+
return SemanticVersionLessOrEqualEvaluator;
98+
case AttributeMatchTypes.SEMVER_LT:
99+
return SemanticVersionLessEvaluator;
86100
case null:
87101
return ExactEvaluator;
88102
}
@@ -129,7 +143,31 @@ public class BaseCondition : ICondition
129143

130144
public bool? GreaterThanEvaluator(object attributeValue, ILogger logger)
131145
{
132-
if (!Validator.IsValidNumericValue(Value))
146+
int? result = NumberEvaluator(attributeValue, logger);
147+
return result == null ? null : (bool?)(result > 0);
148+
}
149+
150+
public bool? GreaterOrEqualThanEvaluator(object attributeValue, ILogger logger)
151+
{
152+
int? result = NumberEvaluator(attributeValue, logger);
153+
return result == null ? null : (bool?)(result >= 0);
154+
}
155+
156+
public bool? LessThanEvaluator(object attributeValue, ILogger logger)
157+
{
158+
int? result = NumberEvaluator(attributeValue, logger);
159+
return result == null ? null : (bool?)(result < 0);
160+
}
161+
162+
public bool? LessOrEqualThanEvaluator(object attributeValue, ILogger logger)
163+
{
164+
int? result = NumberEvaluator(attributeValue, logger);
165+
return result == null ? null : (bool?)(result <= 0);
166+
}
167+
168+
public bool? SubstringEvaluator(object attributeValue, ILogger logger)
169+
{
170+
if (!(Value is string))
133171
{
134172
logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK.");
135173
return null;
@@ -141,24 +179,59 @@ public class BaseCondition : ICondition
141179
return null;
142180
}
143181

144-
if (!Validator.IsNumericType(attributeValue))
182+
if (!(attributeValue is string))
145183
{
146184
logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}"".");
147185
return null;
148186
}
149187

150-
if (!Validator.IsValidNumericValue(attributeValue))
151-
{
152-
logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because the number value for user attribute ""{Name}"" is not in the range [-2^53, +2^53].");
153-
return null;
154-
}
188+
var attrValue = (string)attributeValue;
189+
return attrValue != null && attrValue.Contains((string)Value);
190+
}
191+
192+
/// <summary>
193+
/// Validates the value for exact conditions.
194+
/// </summary>
195+
/// <param name="value">Value to validate</param>
196+
/// <returns>true if the type of value is valid for exact conditions, false otherwise.</returns>
197+
public bool IsValueTypeValidForExactConditions(object value)
198+
{
199+
return value is string || value is bool || Validator.IsNumericType(value);
200+
}
155201

156-
return Convert.ToDouble(attributeValue) > Convert.ToDouble(Value);
202+
public bool? SemanticVersionEqualEvaluator(object attributeValue, ILogger logger)
203+
{
204+
int? result = SemanticVersionEvaluator(attributeValue, logger);
205+
return result == null ? null : (bool?)(result == 0);
157206
}
158207

159-
public bool? LessThanEvaluator(object attributeValue, ILogger logger)
208+
public bool? SemanticVersionGreaterEvaluator(object attributeValue, ILogger logger)
160209
{
161-
if (!Validator.IsValidNumericValue(Value))
210+
int? result = SemanticVersionEvaluator(attributeValue, logger);
211+
return result == null ? null : (bool?)(result > 0);
212+
}
213+
214+
public bool? SemanticVersionGreaterOrEqualEvaluator(object attributeValue, ILogger logger)
215+
{
216+
int? result = SemanticVersionEvaluator(attributeValue, logger);
217+
return result == null ? null : (bool?)(result >= 0);
218+
}
219+
220+
public bool? SemanticVersionLessEvaluator(object attributeValue, ILogger logger)
221+
{
222+
int? result = SemanticVersionEvaluator(attributeValue, logger);
223+
return result == null ? null : (bool?)(result < 0);
224+
}
225+
226+
public bool? SemanticVersionLessOrEqualEvaluator(object attributeValue, ILogger logger)
227+
{
228+
int? result = SemanticVersionEvaluator(attributeValue, logger);
229+
return result == null ? null : (bool?)(result <= 0);
230+
}
231+
232+
public int? SemanticVersionEvaluator(object attributeValue, ILogger logger)
233+
{
234+
if (!(Value is string))
162235
{
163236
logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK.");
164237
return null;
@@ -170,24 +243,28 @@ public class BaseCondition : ICondition
170243
return null;
171244
}
172245

173-
if (!Validator.IsNumericType(attributeValue))
246+
if (!(attributeValue is string))
174247
{
175248
logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}"".");
176249
return null;
177250
}
178251

179-
if (!Validator.IsValidNumericValue(attributeValue))
252+
try
253+
{
254+
var conditionalVersion = new SemanticVersion(Value.ToString());
255+
var userSemanticVersion = new SemanticVersion(attributeValue.ToString());
256+
257+
return userSemanticVersion.CompareTo(conditionalVersion);
258+
}
259+
catch
180260
{
181-
logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because the number value for user attribute ""{Name}"" is not in the range [-2^53, +2^53].");
182261
return null;
183262
}
184-
185-
return Convert.ToDouble(attributeValue) < Convert.ToDouble(Value);
186263
}
187264

188-
public bool? SubstringEvaluator(object attributeValue, ILogger logger)
265+
public int? NumberEvaluator(object attributeValue, ILogger logger)
189266
{
190-
if (!(Value is string))
267+
if (!Validator.IsValidNumericValue(Value))
191268
{
192269
logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK.");
193270
return null;
@@ -199,24 +276,21 @@ public class BaseCondition : ICondition
199276
return null;
200277
}
201278

202-
if (!(attributeValue is string))
279+
if (!Validator.IsNumericType(attributeValue))
203280
{
204281
logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}"".");
205282
return null;
206283
}
207284

208-
var attrValue = (string)attributeValue;
209-
return attrValue != null && attrValue.Contains((string)Value);
210-
}
285+
if (!Validator.IsValidNumericValue(attributeValue))
286+
{
287+
logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because the number value for user attribute ""{Name}"" is not in the range [-2^53, +2^53].");
288+
return null;
289+
}
290+
double userValue = Convert.ToDouble(attributeValue);
291+
double conditionalValue = Convert.ToDouble(Value);
211292

212-
/// <summary>
213-
/// Validates the value for exact conditions.
214-
/// </summary>
215-
/// <param name="value">Value to validate</param>
216-
/// <returns>true if the type of value is valid for exact conditions, false otherwise.</returns>
217-
public bool IsValueTypeValidForExactConditions(object value)
218-
{
219-
return value is string || value is bool || Validator.IsNumericType(value);
293+
return userValue.CompareTo(conditionalValue);
220294
}
221295

222296
/// <summary>

0 commit comments

Comments
 (0)