Skip to content

Commit

Permalink
Feature/global segments 103 (#104)
Browse files Browse the repository at this point in the history
* add supported spec version header

* improvement: rename internal cache to make room

* feat: segments data model

* feat: early implementation of segments support

* improvement: switch to yielding ienumerable

* fail if segment key not found in segments

* add unit tests for segments
  • Loading branch information
daveleek authored Sep 1, 2022
1 parent 757e26a commit 314f624
Show file tree
Hide file tree
Showing 22 changed files with 235 additions and 26 deletions.
3 changes: 3 additions & 0 deletions src/Unleash/Communication/UnleashApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,11 @@ private static void SetRequestHeaders(HttpRequestMessage requestMessage, Unleash
const string appNameHeader = "UNLEASH-APPNAME";
const string instanceIdHeader = "UNLEASH-INSTANCEID";

const string supportedSpecVersionHeader = "Unleash-Client-Spec";

requestMessage.Headers.TryAddWithoutValidation(appNameHeader, headers.AppName);
requestMessage.Headers.TryAddWithoutValidation(instanceIdHeader, headers.InstanceTag);
requestMessage.Headers.TryAddWithoutValidation(supportedSpecVersionHeader, headers.SupportedSpecVersion);

SetCustomHeaders(requestMessage, headers.CustomHttpHeaders);
SetCustomHeaders(requestMessage, headers.CustomHttpHeaderProvider?.CustomHeaders);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ internal class UnleashApiClientRequestHeaders
public string InstanceTag { get; set; }
public Dictionary<string,string> CustomHttpHeaders { get; set; }
public IUnleashCustomHttpHeaderProvider CustomHttpHeaderProvider { get; set; }
public string SupportedSpecVersion { get; internal set; }
}
}
21 changes: 20 additions & 1 deletion src/Unleash/DefaultUnleash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private bool CheckIsEnabled(string toggleName, UnleashContext context, bool defa
else
{
var enhancedContext = context.ApplyStaticFields(settings);
enabled = featureToggle.Strategies.Any(s => GetStrategyOrUnknown(s.Name).IsEnabled(s.Parameters, enhancedContext, s.Constraints));
enabled = featureToggle.Strategies.Any(s => GetStrategyOrUnknown(s.Name).IsEnabled(s.Parameters, enhancedContext, ResolveConstraints(s).Union(s.Constraints)));
}

RegisterCount(toggleName, enabled);
Expand Down Expand Up @@ -202,6 +202,25 @@ private IStrategy GetStrategyOrUnknown(string strategy)
: UnknownStrategy;
}

private IEnumerable<Constraint> ResolveConstraints(ActivationStrategy activationStrategy)
{
foreach (var segmentId in activationStrategy.Segments)
{
var segment = services.ToggleCollection.Instance.GetSegmentById(segmentId);
if (segment != null)
{
foreach (var constraint in segment.Constraints)
{
yield return constraint;
}
}
else
{
yield return null;
}
}
}

public void Dispose()
{
services?.Dispose();
Expand Down
4 changes: 3 additions & 1 deletion src/Unleash/Internal/ActivationStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ public class ActivationStrategy
public string Name { get; }
public Dictionary<string, string> Parameters { get; }
public List<Constraint> Constraints { get; }
public List<string> Segments { get; }

public ActivationStrategy(string name, Dictionary<string, string> parameters, List<Constraint> constraints = null)
public ActivationStrategy(string name, Dictionary<string, string> parameters, List<Constraint> constraints = null, List<string> segments = null)
{
Name = name;
Parameters = parameters;
Constraints = constraints ?? new List<Constraint>();
Segments = segments ?? new List<string>();
}
}
}
17 changes: 17 additions & 0 deletions src/Unleash/Internal/Segment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Unleash.Internal
{
public class Segment
{
public string Id { get; }
public List<Constraint> Constraints { get; }
public Segment(string id, List<Constraint> constraints = null)
{
Id = id;
Constraints = constraints ?? new List<Constraint>();
}
}
}
29 changes: 24 additions & 5 deletions src/Unleash/Internal/ToggleCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,44 @@ public class ToggleCollection
{
public int Version = 1;

private readonly Dictionary<string, FeatureToggle> cache;
private readonly Dictionary<string, FeatureToggle> togglesCache;

public ToggleCollection(ICollection<FeatureToggle> features = null)
private readonly Dictionary<string, Segment> segmentsCache;

public ToggleCollection(ICollection<FeatureToggle> features = null, ICollection<Segment> segments = null)
{
Features = features ?? new List<FeatureToggle>(0);
cache = new Dictionary<string, FeatureToggle>(Features.Count);
Segments = segments ?? new List<Segment>(0);

togglesCache = new Dictionary<string, FeatureToggle>(Features.Count);
segmentsCache = new Dictionary<string, Segment>(Segments.Count);

foreach (var featureToggle in Features) {
cache.Add(featureToggle.Name, featureToggle);
togglesCache.Add(featureToggle.Name, featureToggle);
}

foreach (var segment in Segments)
{
segmentsCache.Add(segment.Id, segment);
}
}

public ICollection<FeatureToggle> Features { get; }

public ICollection<Segment> Segments { get; }

public FeatureToggle GetToggleByName(string name)
{
return cache.TryGetValue(name, out var value)
return togglesCache.TryGetValue(name, out var value)
? value
: null;
}

public Segment GetSegmentById(string id)
{
return segmentsCache.TryGetValue(id, out var value)
? value
: null;
}
}
}
5 changes: 4 additions & 1 deletion src/Unleash/Internal/UnleashServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ internal class UnleashServices : IDisposable
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly IUnleashScheduledTaskManager scheduledTaskManager;

const string supportedSpecVersion = "4.2.0";

internal CancellationToken CancellationToken { get; }
internal IUnleashContextProvider ContextProvider { get; }
internal ThreadSafeToggleCollection ToggleCollection { get; }
Expand Down Expand Up @@ -59,7 +61,8 @@ public UnleashServices(UnleashSettings settings, Dictionary<string, IStrategy> s
AppName = settings.AppName,
InstanceTag = settings.InstanceTag,
CustomHttpHeaders = settings.CustomHttpHeaders,
CustomHttpHeaderProvider = settings.UnleashCustomHttpHeaderProvider
CustomHttpHeaderProvider = settings.UnleashCustomHttpHeaderProvider,
SupportedSpecVersion = supportedSpecVersion
}, settings.ProjectId);
}
else
Expand Down
2 changes: 1 addition & 1 deletion src/Unleash/Strategies/ApplicationHostnameStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext cont
return false;
}

public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, List<Constraint> constraints)
public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, IEnumerable<Constraint> constraints)
{
return StrategyUtils.IsEnabled(this, parameters, context, constraints);
}
Expand Down
16 changes: 10 additions & 6 deletions src/Unleash/Strategies/ConstraintUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,24 @@ public class ConstraintUtils
{ Operator.SEMVER_LT, new SemverConstraintOperator() },
};

public static bool Validate(List<Constraint> constraints, UnleashContext context)
public static bool Validate(IEnumerable<Constraint> constraints, UnleashContext context)
{
if (constraints?.Count > 0)
{
return constraints.TrueForAll(c => ValidateConstraint(c, context));
}
else
// No need to check count - all returns true if no elements
if (constraints == null)
{
return true;
}

return constraints.All(c => ValidateConstraint(c, context));
}

private static bool ValidateConstraint(Constraint constraint, UnleashContext context)
{
if (constraint == null)
{
return false;
}

var contextValue = context.GetByName(constraint.ContextName);
if (constraint.Operator == null)
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/Unleash/Strategies/DefaultStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext cont
return true;
}

public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, List<Constraint> constraints)
public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, IEnumerable<Constraint> constraints)
{
return StrategyUtils.IsEnabled(this, parameters, context, constraints);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Unleash/Strategies/FlexibleRolloutStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public FlexibleRolloutStrategy(Func<string> randomGenerator)
this.randomGenerator = randomGenerator;
}

public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, List<Constraint> constraints)
public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, IEnumerable<Constraint> constraints)
{
return StrategyUtils.IsEnabled(this, parameters, context, constraints);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Unleash/Strategies/GradualRolloutRandomStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext cont
return percentage >= randomNumber;
}

public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, List<Constraint> constraints)
public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, IEnumerable<Constraint> constraints)
{
return StrategyUtils.IsEnabled(this, parameters, context, constraints);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Unleash/Strategies/GradualRolloutSessionIdStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext cont
return percentage > 0 && normalizedSessionId <= percentage;
}

public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, List<Constraint> constraints)
public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, IEnumerable<Constraint> constraints)
{
return StrategyUtils.IsEnabled(this, parameters, context, constraints);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Unleash/Strategies/GradualRolloutUserIdStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext cont
return percentage > 0 && normalizedUserId <= percentage;
}

public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, List<Constraint> constraints)
public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, IEnumerable<Constraint> constraints)
{
return StrategyUtils.IsEnabled(this, parameters, context, constraints);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Unleash/Strategies/IStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ public interface IStrategy
/// <param name="context"></param>
/// <param name="constraints"></param>
/// <returns></returns>
bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, List<Constraint> constraints);
bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, IEnumerable<Constraint> constraints);
}
}
2 changes: 1 addition & 1 deletion src/Unleash/Strategies/RemoteAddressStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext cont
return false;
}

public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, List<Constraint> constraints)
public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, IEnumerable<Constraint> constraints)
{
return StrategyUtils.IsEnabled(this, parameters, context, constraints);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Unleash/Strategies/StrategyUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static int GetPercentage(string percentage)
return p;
}

public static bool IsEnabled(IStrategy strategy, Dictionary<string, string> parameters, UnleashContext context, List<Constraint> constraints)
public static bool IsEnabled(IStrategy strategy, Dictionary<string, string> parameters, UnleashContext context, IEnumerable<Constraint> constraints)
{
return ConstraintUtils.Validate(constraints, context) && strategy.IsEnabled(parameters, context);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Unleash/Strategies/UnknownStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext cont
return false;
}

public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, List<Constraint> constraints)
public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, IEnumerable<Constraint> constraints)
{
return StrategyUtils.IsEnabled(this, parameters, context, constraints);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Unleash/Strategies/UserWithIdStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext cont
return idsLocal.IndexOf(userLocal, StringComparison.Ordinal) > -1;
}

public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, List<Constraint> constraints)
public bool IsEnabled(Dictionary<string, string> parameters, UnleashContext context, IEnumerable<Constraint> constraints)
{
return StrategyUtils.IsEnabled(this, parameters, context, constraints);
}
Expand Down
Loading

0 comments on commit 314f624

Please sign in to comment.