Skip to content

Commit

Permalink
feat: dotnet SDK Relay server (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
typotter authored Nov 14, 2024
1 parent a1d66c1 commit 387a010
Show file tree
Hide file tree
Showing 23 changed files with 614 additions and 0 deletions.
3 changes: 3 additions & 0 deletions package-testing/dotnet-sdk-relay/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
bin
obj
tmp
4 changes: 4 additions & 0 deletions package-testing/dotnet-sdk-relay/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"version": "0.2.0",
"configurations": []
}
18 changes: 18 additions & 0 deletions package-testing/dotnet-sdk-relay/EppoSDKRelay/AssignmentLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using eppo_sdk.dto;
using eppo_sdk.dto.bandit;
using eppo_sdk.logger;

internal class AssignmentLogger : IAssignmentLogger
{
public void LogAssignment(AssignmentLogData assignmentLogData)
{
Console.WriteLine("Assignment Log");
Console.WriteLine(assignmentLogData);
}

public void LogBanditAction(BanditLogEvent banditLogEvent)
{
Console.WriteLine("Bandit Action Log");
Console.WriteLine(banditLogEvent);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Mvc;

using EppoSDKRelay.DTO;
using eppo_sdk;
using System.Text.Json;
using Newtonsoft.Json.Linq;
using EppoSDKRelay.util;

namespace EppoSDKRelay.controllers;

[Route("flags/v1/assignment")]
public class AssignmentController : JsonControllerBase
{
[HttpPost]
public ActionResult<string> Post([FromBody] AssignmentRequest data)
{
// If there was a parsing error, this is how it is recorded. We want to fail this test so return an error
var errors = ModelState.Values.SelectMany(x => x.Errors);
foreach (var err in errors)
{
return JsonError(err.ErrorMessage);
}

var eppoClient = EppoClient.GetInstance();

var defaultValue = data.DefaultValue ?? "";
Dictionary<string, object> convertedAttributes = Values.ConvertJsonValuesToPrimitives(data.SubjectAttributes);

switch (data.AssignmentType)
{
case "STRING":
return JsonResult(eppoClient.GetStringAssignment(data.Flag, data.SubjectKey, convertedAttributes, defaultValue.ToString()!));

case "INTEGER":
var intResults = eppoClient.GetIntegerAssignment(data.Flag, data.SubjectKey, convertedAttributes, Convert.ToInt64(defaultValue.ToString()));
return JsonResult(intResults);

case "BOOLEAN":
return JsonResult(eppoClient.GetBooleanAssignment(data.Flag, data.SubjectKey, convertedAttributes, Convert.ToBoolean(defaultValue.ToString())));

case "NUMERIC":
return JsonResult(eppoClient.GetNumericAssignment(data.Flag, data.SubjectKey, convertedAttributes, Convert.ToDouble(defaultValue.ToString())));

case "JSON":
var jString = defaultValue.ToString();
var defaultJson = JObject.Parse(jString);
return JsonResult(eppoClient.GetJsonAssignment(data.Flag, data.SubjectKey, convertedAttributes, defaultJson));
}

return JsonError("Invalid Assignment Type " + data.AssignmentType);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Mvc;
using EppoSDKRelay.DTO;

using eppo_sdk;

namespace EppoSDKRelay.controllers;

[Route("bandits/v1/action")]
public class BanditController : JsonControllerBase
{
[HttpPost]
public ActionResult<string> Post([FromBody] BanditActionRequest data)
{
// If there was a parsing error, this is how it is recorded. We want to fail this tast so return an error
var errors = ModelState.Values.SelectMany(x => x.Errors);
foreach (var err in errors)
{
return JsonError(err.ErrorMessage);
}

var eppoClient = EppoClient.GetInstance();

if (data == null)
{
return JsonError("Data was not parsable");
}

var subject = data.GetSubjectContext();
var actions = data.GetActionContextDict();
var defaultVal = Convert.ToString(data.DefaultValue) ?? "";

var result = eppoClient.GetBanditAction(data.Flag,
subject,
actions,
defaultVal);
return JsonResult(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Mvc;

namespace EppoSDKRelay.controllers;

[Route("/")]
public class HealthController : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<string>> Check()
{
return new string[] { "hello", "world" };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Microsoft.AspNetCore.Mvc;

using EppoSDKRelay.DTO;
using System.Text.Json;
using Newtonsoft.Json.Linq;

namespace EppoSDKRelay.controllers;

public class JsonControllerBase : ControllerBase {

protected static readonly JsonSerializerOptions SerializeOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};

protected static ActionResult<string> JsonResult(object result)
{
// System.Text.Json does not play nicely with Newtonsoft types
// Since "Objects" implement IEnumerable, System.Text will try to encode
// the json object as an array. :(
if (result is JObject) {
result = ((JObject)result).ToObject<Dictionary<string, object>>();
}

var response = new TestResponse
{
Result = result
};
return JsonSerializer.Serialize(response, SerializeOptions);
}

protected static ActionResult<string> JsonError(String error)
{
return JsonSerializer.Serialize(new TestResponse
{
Error = error
}, SerializeOptions);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using eppo_sdk;
using Microsoft.AspNetCore.Mvc;

namespace EppoSDKRelay.controllers;

[Route("/sdk/reset")]
public class SDKController : ControllerBase
{
// POST sdk/reset
[HttpPost]
public ActionResult<IEnumerable<string>> Reset()
{
// Startup.InitEppoClient();
EppoClient.GetInstance().RefreshConfiguration();
return Ok();
}

}
6 changes: 6 additions & 0 deletions package-testing/dotnet-sdk-relay/EppoSDKRelay/DTO/Action.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace EppoSDKRelay.DTO;

public class Action : AttributeSet
{
public string ActionKey { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace EppoSDKRelay.DTO;

public class AssignmentRequest
{
public string Flag { get; set; }
public string SubjectKey { get; set; }
public string AssignmentType { get; set; }
public object DefaultValue { get; set; }
public Dictionary<string, Object> SubjectAttributes { get; set; }
}
28 changes: 28 additions & 0 deletions package-testing/dotnet-sdk-relay/EppoSDKRelay/DTO/AttributeSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace EppoSDKRelay.DTO;

using System.Text.Json;

public class AttributeSet
{
public Dictionary<string, object?> NumericAttributes { get; set; }
public Dictionary<string, object?> CategoricalAttributes { get; set; }

public Dictionary<string, object?> AttributeDictionary
{
get => new Dictionary<string, object?>[] { NumericAttributes, CategoricalAttributes }.SelectMany(dict => dict)
.ToDictionary(pair => pair.Key, pair => pair.Value);
}

public Dictionary<string, string?> CategoricalAttributesAsStrings
{
get
{
return CategoricalAttributes.ToDictionary(kvp => kvp.Key, kvp => Convert.ToString(kvp.Value));
}
}
public Dictionary<string, object?> NumericAttributesAsNumbers => NumericAttributes
.Where(kvp =>
(kvp.Value is JsonElement jsonElement) && jsonElement.ValueKind == JsonValueKind.Number)
.ToDictionary(kvp => kvp.Key, static kvp => (object?)((JsonElement)kvp.Value).GetDouble());

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using eppo_sdk.dto.bandit;
using EppoSDKRelay.util;

namespace EppoSDKRelay.DTO;

public class BanditActionRequest
{
public string Flag { get; set; }
public string DefaultValue { get; set; }
public string SubjectKey { get; set; }
public AttributeSet SubjectAttributes { get; set; }
public List<Action> Actions { get; set; }

public ContextAttributes GetSubjectContext()
{
return ContextAttributes.FromNullableAttributes(SubjectKey,
SubjectAttributes.CategoricalAttributesAsStrings,
SubjectAttributes.NumericAttributes);
}

public IDictionary<string, ContextAttributes> GetActionContextDict()
{
return Actions.ToDictionary(action => action.ActionKey,
action => ContextAttributes.FromDict(action.ActionKey, Values.ConvertJsonValuesToPrimitives(action.AttributeDictionary)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace EppoSDKRelay.DTO;

public class TestResponse
{
public object Result { get; set; }
public List<object> AssignmentLog { get; set; }
public List<object> BanditLog { get; set; }
public string Error { get; set; }
}
16 changes: 16 additions & 0 deletions package-testing/dotnet-sdk-relay/EppoSDKRelay/EppoSDKRelay.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Eppo.Sdk" Version="3.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
</ItemGroup>

</Project>
18 changes: 18 additions & 0 deletions package-testing/dotnet-sdk-relay/EppoSDKRelay/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace EppoSDKRelay;

public static class Program
{

static readonly String host = Environment.GetEnvironmentVariable("SDK_RELAY_HOST") ?? "localhost";
static readonly String port = Environment.GetEnvironmentVariable("SDK_RELAY_PORT") ?? "4000";
public static void Main(string[] args) =>
CreateHostBuilder(args).Build().Run();

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
builder.UseUrls("http://" + host + ":" + port);
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50672",
"sslPort": 44315
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5034",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7261;http://localhost:5034",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Loading

0 comments on commit 387a010

Please sign in to comment.