Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement JSON assignment type (FF-999) #10

Merged
merged 10 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 27 additions & 18 deletions dot-net-sdk/EppoClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using eppo_sdk.store;
using eppo_sdk.tasks;
using eppo_sdk.validators;
using Newtonsoft.Json.Linq;
using NLog;

namespace eppo_sdk;
Expand All @@ -29,6 +30,11 @@ private EppoClient(ConfigurationStore configurationStore, EppoClientConfig eppoC
_fetchExperimentsTask = fetchExperimentsTask;
}

public JObject? GetJsonAssignment(string subjectKey, string flagKey, SubjectAttributes? subjectAttributes = null)
typotter marked this conversation as resolved.
Show resolved Hide resolved
{
return GetAssignment(subjectKey, flagKey, subjectAttributes ?? new SubjectAttributes())?.JsonValue();
}

public bool? GetBoolAssignment(string subjectKey, string flagKey, SubjectAttributes? subjectAttributes = null)
{
return GetAssignment(subjectKey, flagKey, subjectAttributes ?? new SubjectAttributes())?.BoolValue();
Expand All @@ -40,7 +46,7 @@ private EppoClient(ConfigurationStore configurationStore, EppoClientConfig eppoC
}


public int? GetIntegerAssignment(string subjectKey, string flagKey, SubjectAttributes? subjectAttributes = null)
public long? GetIntegerAssignment(string subjectKey, string flagKey, SubjectAttributes? subjectAttributes = null)
Copy link
Member

Choose a reason for hiding this comment

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

ty

{
return GetAssignment(subjectKey, flagKey, subjectAttributes ?? new SubjectAttributes())?.IntegerValue();
}
Expand All @@ -52,7 +58,7 @@ private EppoClient(ConfigurationStore configurationStore, EppoClientConfig eppoC
}


private EppoValue? GetAssignment(string subjectKey, string flagKey, SubjectAttributes subjectAttributes)
private HasEppoValue? GetAssignment(string subjectKey, string flagKey, SubjectAttributes subjectAttributes)
{
InputValidator.ValidateNotBlank(subjectKey, "Invalid argument: subjectKey cannot be blank");
InputValidator.ValidateNotBlank(flagKey, "Invalid argument: flagKey cannot be blank");
Expand Down Expand Up @@ -93,23 +99,26 @@ private EppoClient(ConfigurationStore configurationStore, EppoClientConfig eppoC

var assignedVariation =
GetAssignedVariation(subjectKey, flagKey, configuration.subjectShards, allocation.variations);
try
{
_eppoClientConfig.AssignmentLogger
.LogAssignment(new AssignmentLogData(
flagKey,
rule.allocationKey,
assignedVariation.typedValue.StringValue(),
subjectKey,
subjectAttributes
));
}
catch (Exception)
if (assignedVariation != null && !assignedVariation.IsNull())
{
// Ignore Exception
try
{
_eppoClientConfig.AssignmentLogger
.LogAssignment(new AssignmentLogData(
flagKey,
rule.allocationKey,
assignedVariation.StringValue() ?? "null",
subjectKey,
subjectAttributes
));
}
catch (Exception)
{
// Ignore Exception
}
}

return assignedVariation?.typedValue;
return assignedVariation;
}

private bool IsInExperimentSample(string subjectKey, string flagKey, int subjectShards,
Expand All @@ -126,10 +135,10 @@ private Variation GetAssignedVariation(string subjectKey, string flagKey, int su
return variations.Find(config => Shard.IsInRange(shard, config.shardRange))!;
}

public EppoValue GetSubjectVariationOverride(string subjectKey, ExperimentConfiguration experimentConfiguration)
public HasEppoValue GetSubjectVariationOverride(string subjectKey, ExperimentConfiguration experimentConfiguration)
{
var hexedSubjectKey = Shard.GetHex(subjectKey);
return experimentConfiguration.typedOverrides.GetValueOrDefault(hexedSubjectKey, new EppoValue());
return new HasEppoValue(experimentConfiguration.typedOverrides.GetValueOrDefault(hexedSubjectKey, null));
}

public static EppoClient Init(EppoClientConfig eppoClientConfig)
Expand Down
7 changes: 3 additions & 4 deletions dot-net-sdk/dto/Condition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@

namespace eppo_sdk.dto;

public class Condition
public class Condition : HasEppoValue
{
public string attribute { get; set; }

Check warning on line 8 in dot-net-sdk/dto/Condition.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'attribute' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public EppoValue value { get; set; }

[JsonProperty(PropertyName = "operator", NamingStrategyType = typeof(DefaultNamingStrategy))]
public OperatorType operatorType { get; set; }

public override string ToString()
{
return $"operator: {operatorType} | Attribute: {attribute} | value: {value}";
return $"operator: {operatorType} | Attribute: {attribute} | value: {Value}";
}
}
}
60 changes: 0 additions & 60 deletions dot-net-sdk/dto/EppoValue.cs

This file was deleted.

43 changes: 0 additions & 43 deletions dot-net-sdk/dto/EppoValueDeserializer.cs

This file was deleted.

3 changes: 2 additions & 1 deletion dot-net-sdk/dto/EppoValueType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ public enum EppoValueType
STRING,
BOOLEAN,
NULL,
ARRAY_OF_STRING
ARRAY_OF_STRING,
JSON
}
4 changes: 2 additions & 2 deletions dot-net-sdk/dto/ExperimentConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

public class ExperimentConfiguration
{
public string name { get; set; }

Check warning on line 5 in dot-net-sdk/dto/ExperimentConfiguration.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public bool enabled { get; set; }
public int subjectShards { get; set; }
public Dictionary<string, EppoValue> typedOverrides { get; set; }
public Dictionary<string, Object> typedOverrides { get; set; }

Check warning on line 8 in dot-net-sdk/dto/ExperimentConfiguration.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'typedOverrides' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public Dictionary<string, Allocation> allocations { get; set; }

Check warning on line 9 in dot-net-sdk/dto/ExperimentConfiguration.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'allocations' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public List<Rule> rules { get; set; }

Check warning on line 10 in dot-net-sdk/dto/ExperimentConfiguration.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'rules' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

public Allocation? GetAllocation(string allocationKey)
{
allocations.TryGetValue(allocationKey, out var value);
return value;
}
}
}
140 changes: 140 additions & 0 deletions dot-net-sdk/dto/HasEppoValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System.Globalization;
using System.Numerics;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;

namespace eppo_sdk.dto;


public class HasEppoValue
{

private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

private bool _typed;

private Object? _value;
public Object? Value
{
get { return _value; }
set
{
if (!_typed)
{
_type = InferTypeFromValue(value);
_value = value;
}
}
}
public Object? typedValue
{
get { return _value; }
set
{
_typed = true;
_type = InferTypeFromValue(value);
_value = value;
}
}
private EppoValueType _type;
public EppoValueType Type { get { return _type; } }

public bool? BoolValue() => _value != null ? (bool)_value : null;
public double? DoubleValue() => Convert.ToDouble(_value);
public long? IntegerValue() => _value != null ? (long)_value : null;
public string? StringValue() => _value != null ? (string)_value : null;
public List<string>? ArrayValue()
{
if (_value == null)
{
return null;
}

if (_value is JArray array)
{

return new List<string>(array.ToObject<string[]>());
}
return (List<string>)_value;
}
public JObject? JsonValue() => _value == null ? null : (JObject)_value;



private static EppoValueType InferTypeFromValue(Object? value)
{
if (value == null) return EppoValueType.NULL;

if (value is Array || value.GetType().IsArray || value is JArray || value is List<string> || value is IEnumerable<string>)
{
return EppoValueType.ARRAY_OF_STRING;
}
else if (value is bool || value is Boolean)
{
return EppoValueType.BOOLEAN;
}
else if (value is float || value is double || value is Double || value is float)
{
return EppoValueType.NUMBER;

}
else if (value is int || value is long || value is BigInteger)
{
return EppoValueType.INTEGER;

}
else if (value is string || value is String)
{
return EppoValueType.STRING;
}
else if (value is JObject)
{
return EppoValueType.JSON;
}
else
{
Type type = value!.GetType();
Logger.Error($"Unexpected value of type {type}");
Console.WriteLine($"Unexpected value of type {type}");
return EppoValueType.NULL;
}
}

public static HasEppoValue Bool(string? value) => new(value, EppoValueType.BOOLEAN);
public static HasEppoValue Bool(bool value) => new(value, EppoValueType.BOOLEAN);
public static HasEppoValue Number(string value) => new(value, EppoValueType.NUMBER);
public static HasEppoValue String(string? value) => new(value, EppoValueType.STRING);
public static HasEppoValue Integer(string value) => new(value, EppoValueType.INTEGER);
public static HasEppoValue Null() => new();

public HasEppoValue()
{
}

public HasEppoValue(object? value, EppoValueType type)
{
this.Value = value;
}

[JsonConstructor]
public HasEppoValue(object? value)
{
this.Value = value;
}

public HasEppoValue(List<string> array)
{
this.Value = array;
}

public bool IsNumeric() => _type == EppoValueType.NUMBER || _type == EppoValueType.INTEGER;

public bool IsNull() => EppoValueType.NULL.Equals(Type);

public static bool IsNullValue(HasEppoValue? value) =>
value == null /* null pointer */ ||
value.IsNull() /* parsed as a null JSON token type */ ||
value.Value == null; /* Value type is set but value is null */

}
2 changes: 1 addition & 1 deletion dot-net-sdk/dto/SubjectAttributes.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace eppo_sdk.dto;

public class SubjectAttributes: Dictionary<string, EppoValue>
public class SubjectAttributes: Dictionary<string, Object>
{

}
Loading
Loading