Skip to content
Open
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
2 changes: 1 addition & 1 deletion DevCycle.SDK.Server.Local.Benchmark/BenchmarkTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ private DevCycleLocalClient createTestClient()
mockHttp.When("https://events*")
.Respond(HttpStatusCode.Created, "application/json",
"{}");
var localBucketing = new LocalBucketing();
var localBucketing = new WASMLocalBucketing();
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var sdkKey = $"dvc_server_{Guid.NewGuid().ToString().Replace('-','_')}_hash";
localBucketing.StoreConfig(sdkKey, config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private Tuple<EnvironmentConfigManager, MockHttpMessageHandler, MockedRequest> g
var sdkKey = $"server-{Guid.NewGuid()}";
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var cfgManager = new EnvironmentConfigManager(sdkKey, new DevCycleLocalOptions(),
loggerFactory, new LocalBucketing(), restClientOptions: new DevCycleRestClientOptions()
loggerFactory, new WASMLocalBucketing(), restClientOptions: new DevCycleRestClientOptions()
{ ConfigureMessageHandler = _ => mockHttp },
initializedHandler: isError
? (isRetryableError ? DidInitializeSubscriberFailFirstConfigFetch : DidNotInitializeSubscriber)
Expand Down
2 changes: 1 addition & 1 deletion DevCycle.SDK.Server.Local.MSTests/EventQueueTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private Tuple<EventQueue, MockHttpMessageHandler, MockedRequest> getTestQueue(bo
builder.AddConsole();
builder.SetMinimumLevel(logLevel);
});
var localBucketing = new LocalBucketing();
var localBucketing = new WASMLocalBucketing();
string config = new string(Fixtures.Config());
localBucketing.StoreConfig(sdkKey, config);
localBucketing.SetPlatformData(JsonConvert.SerializeObject(new PlatformData()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static DevCycleLocalClient getTestClient(DevCycleLocalOptions options = n
mockHttp.When("https://events*")
.Respond(HttpStatusCode.Created, "application/json",
"{}");
var localBucketing = new LocalBucketing();
var localBucketing = new WASMLocalBucketing();
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var sdkKey = $"dvc_server_{Guid.NewGuid().ToString().Replace('-', '_')}_hash";
localBucketing.StoreConfig(sdkKey, config);
Expand Down
95 changes: 95 additions & 0 deletions DevCycle.SDK.Server.Local/Api/CLocalBucketing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System.Collections.Generic;
using DevCycle.SDK.Server.Common.Model;
using DevCycle.SDK.Server.Common.Model.Local;

namespace DevCycle.SDK.Server.Local.Api;

public class CLocalBucketing : ILocalBucketing
{
public string ClientUUID { get; }

public CLocalBucketing()
{
ClientUUID = System.Guid.NewGuid().ToString();
}

public void InitEventQueue(string sdkKey, string options)
{
throw new System.NotImplementedException();
}

public BucketedUserConfig GenerateBucketedConfig(string sdkKey, string user)
{
throw new System.NotImplementedException();
}

public int EventQueueSize(string sdkKey)
{
throw new System.NotImplementedException();
}

public void QueueEvent(string sdkKey, string user, string eventString)
{
throw new System.NotImplementedException();
}

public void QueueAggregateEvent(string sdkKey, string eventString, string variableVariationMapStr)
{
throw new System.NotImplementedException();
}

public List<FlushPayload> FlushEventQueue(string sdkKey)
{
throw new System.NotImplementedException();
}

public void OnPayloadSuccess(string sdkKey, string payloadId)
{
throw new System.NotImplementedException();
}

public void OnPayloadFailure(string sdkKey, string payloadId, bool retryable)
{
throw new System.NotImplementedException();
}

public void StoreConfig(string sdkKey, string config)
{
throw new System.NotImplementedException();
}

public void SetPlatformData(string platformData)
{
throw new System.NotImplementedException();
}

public string GetVariable(string sdkKey, string userJSON, string key, TypeEnum variableType, bool shouldTrackEvent)
{
throw new System.NotImplementedException();
}

public byte[] GetVariable(byte[] serializedParams)
{
throw new System.NotImplementedException();
}

public string GetConfigMetadata(string sdkKey)
{
throw new System.NotImplementedException();
}

public void SetClientCustomData(string sdkKey, string customData)
{
throw new System.NotImplementedException();
}

public void StartFlush()
{
throw new System.NotImplementedException();
}

public void EndFlush()
{
throw new System.NotImplementedException();
}
}
47 changes: 27 additions & 20 deletions DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class DevCycleLocalClientBuilder : DevCycleClientBuilder<DevCycleLocalCli
DevCycleLocalClientBuilder>
{
private EnvironmentConfigManager configManager;
private LocalBucketing localBucketing;
private ILocalBucketing localBucketing;

protected override DevCycleLocalClientBuilder BuilderInstance => this;

Expand All @@ -28,7 +28,7 @@ public DevCycleLocalClientBuilder SetConfigManager(EnvironmentConfigManager envi
return this;
}

public DevCycleLocalClientBuilder SetLocalBucketing(LocalBucketing localBucketingWrapper)
public DevCycleLocalClientBuilder SetLocalBucketing(ILocalBucketing localBucketingWrapper)
{
localBucketing = localBucketingWrapper;
return this;
Expand All @@ -43,7 +43,7 @@ public DevCycleLocalClientBuilder SetInitializedSubscriber(

public override DevCycleLocalClient Build()
{
localBucketing ??= new LocalBucketing();
localBucketing ??= new WASMLocalBucketing();

options ??= new DevCycleLocalOptions();

Expand Down Expand Up @@ -71,7 +71,7 @@ public class DevCycleLocalClient : DevCycleBaseClient
private readonly string sdkKey;
private readonly EnvironmentConfigManager configManager;
private readonly EventQueue eventQueue;
private readonly LocalBucketing localBucketing;
private readonly ILocalBucketing localBucketing;
private readonly ILogger logger;
private readonly Timer timer;
private bool closing;
Expand All @@ -83,7 +83,7 @@ internal DevCycleLocalClient(
DevCycleLocalOptions dvcLocalOptions,
ILoggerFactory loggerFactory,
EnvironmentConfigManager configManager,
LocalBucketing localBucketing,
ILocalBucketing localBucketing,
DevCycleRestClientOptions restClientOptions = null
)
{
Expand Down Expand Up @@ -341,25 +341,32 @@ public override Task<Variable<T>> Variable<T>(DevCycleUser user, string key, T d
try
{
var paramsBuffer = paramsPb.ToByteArray();
try
{
byte[] variableData = localBucketing.GetVariable(serializedParams: paramsBuffer);
if (variableData == null)
{
return Task.FromResult(Common.Model.Variable<T>.InitializeFromVariable(null, key, defaultValue,
DefaultReasonDetails.UserNotTargeted));
}

byte[] variableData = localBucketing.GetVariableForUserProtobuf(serializedParams: paramsBuffer);

if (variableData == null)
SDKVariable_PB sdkVariable = SDKVariable_PB.Parser.ParseFrom(variableData);
var evalReason = new EvalReason(sdkVariable.Eval.Reason, sdkVariable.Eval.Details,
sdkVariable.Eval.TargetId);
existingVariable = GetVariable<T>(sdkVariable, defaultValue, evalReason);
return Task.FromResult(existingVariable);
}
catch (NotImplementedException)
{
return Task.FromResult(Common.Model.Variable<T>.InitializeFromVariable(null, key, defaultValue, DefaultReasonDetails.UserNotTargeted));
// C Local bucketing
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

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

The catch block for NotImplementedException is empty with only a comment indicating 'C Local bucketing'. After catching this exception, the code falls through to line 369 which returns a default variable with DefaultReasonDetails.Error. This flow is unclear. If this is intentional fallback behavior for C Local bucketing, consider either: (1) adding an explicit comment explaining why the error default is returned, or (2) implementing the C Local bucketing path here if it differs from the error case.

Suggested change
// C Local bucketing
// C Local bucketing is not implemented yet.
// Fall through to return the error default below.

Copilot uses AI. Check for mistakes.
}

SDKVariable_PB sdkVariable = SDKVariable_PB.Parser.ParseFrom(variableData);
var evalReason = new EvalReason(sdkVariable.Eval.Reason, sdkVariable.Eval.Details, sdkVariable.Eval.TargetId);
existingVariable = GetVariable<T>(sdkVariable, defaultValue, evalReason);
}
catch (Exception e)
{
logger.LogError("Unexpected exception getting variable: {Exception}", e.Message);
return Task.FromResult(Common.Model.Variable<T>.InitializeFromVariable(null, key, defaultValue, DefaultReasonDetails.Error));
}

return Task.FromResult(existingVariable);
return Task.FromResult(Common.Model.Variable<T>.InitializeFromVariable(null, key, defaultValue, DefaultReasonDetails.Error));
}

public async Task<Variable<T>> VariableAsync<T>(DevCycleUser user, string key, T defaultValue)
Expand Down Expand Up @@ -387,7 +394,7 @@ public async Task<Variable<T>> VariableAsync<T>(DevCycleUser user, string key, T
var type = Common.Model.Variable<T>.DetermineType(defaultValue);
VariableType_PB variableType = TypeEnumToVariableTypeProtobuf(type);

VariableForUserParams_PB paramsPb = new VariableForUserParams_PB()
VariableForUserParams_PB paramsPb = new VariableForUserParams_PB
{
SdkKey = sdkKey,
User = userPb,
Expand All @@ -407,19 +414,19 @@ public async Task<Variable<T>> VariableAsync<T>(DevCycleUser user, string key, T

try
{
System.Exception beforeError = null;
Exception beforeError = null;
try
{
hookContext = await evalHooksRunner.RunBeforeAsync(hooks, hookContext);
}
catch (System.Exception e)
catch (Exception e)
{
beforeError = e;
}
Comment on lines +422 to 425
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

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

Generic catch clause.

Copilot uses AI. Check for mistakes.

var paramsBuffer = paramsPb.ToByteArray();

byte[] variableData = localBucketing.GetVariableForUserProtobuf(serializedParams: paramsBuffer);
byte[] variableData = localBucketing.GetVariable(serializedParams: paramsBuffer);

if (variableData == null)
{
Expand All @@ -431,7 +438,7 @@ public async Task<Variable<T>> VariableAsync<T>(DevCycleUser user, string key, T

SDKVariable_PB sdkVariable = SDKVariable_PB.Parser.ParseFrom(variableData);
var evalReason = new EvalReason(sdkVariable.Eval.Reason, sdkVariable.Eval.Details, sdkVariable.Eval.TargetId);
existingVariable = GetVariable<T>(sdkVariable, defaultValue, evalReason);
existingVariable = GetVariable(sdkVariable, defaultValue, evalReason);
string featureValue = sdkVariable.Feature?.IsNull == false ? sdkVariable.Feature.Value : null;
variableMetadata.FeatureId = featureValue;

Expand Down
4 changes: 2 additions & 2 deletions DevCycle.SDK.Server.Local/Api/EventQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal class EventQueue
{
private readonly DevCycleLocalOptions localOptions;
private readonly DevCycleEventsApiClient devCycleEventsApiClient;
private readonly LocalBucketing localBucketing;
private readonly ILocalBucketing localBucketing;
private readonly string sdkKey;
private bool closing;

Expand All @@ -32,7 +32,7 @@ public EventQueue(
string sdkKey,
DevCycleLocalOptions localOptions,
ILoggerFactory loggerFactory,
LocalBucketing localBucketing,
ILocalBucketing localBucketing,
DevCycleRestClientOptions restClientOptions = null
)
{
Expand Down
28 changes: 28 additions & 0 deletions DevCycle.SDK.Server.Local/Api/ILocalBucketing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Collections.Generic;
using DevCycle.SDK.Server.Common.Model;
using DevCycle.SDK.Server.Common.Model.Local;

namespace DevCycle.SDK.Server.Local.Api;

public interface ILocalBucketing
{
public string ClientUUID { get; }
public void InitEventQueue(string sdkKey, string options);
public BucketedUserConfig GenerateBucketedConfig(string sdkKey, string user);
public int EventQueueSize(string sdkKey);
public void QueueEvent(string sdkKey, string user, string eventString);
public void QueueAggregateEvent(string sdkKey, string eventString, string variableVariationMapStr);
public List<FlushPayload> FlushEventQueue(string sdkKey);
public void OnPayloadSuccess(string sdkKey, string payloadId);
public void OnPayloadFailure(string sdkKey, string payloadId, bool retryable);

public void StoreConfig(string sdkKey, string config);
public void SetPlatformData(string platformData);
public string GetVariable(string sdkKey, string userJSON, string key, TypeEnum variableType, bool shouldTrackEvent);
public byte[] GetVariable(byte[] serializedParams);
public string GetConfigMetadata(string sdkKey);
public void SetClientCustomData(string sdkKey, string customData);
public void StartFlush();
public void EndFlush();

}
Loading