Skip to content

Commit

Permalink
Merge pull request #17 from featurehub-io/feature/upgrade-sdk-compliance
Browse files Browse the repository at this point in the history
[feature/cache-multi-value] Cache and Multi-Value support
  • Loading branch information
rvowles committed Nov 6, 2022
2 parents d77eb25 + 166a8e7 commit fe289b0
Show file tree
Hide file tree
Showing 55 changed files with 1,979 additions and 793 deletions.
315 changes: 245 additions & 70 deletions FeatureHubSDK/.gitignore

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions FeatureHubSDK/.openapi-generator-ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator

# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.

# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux

# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux

# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
.gitignore
IO.FeatureHub.SSE.sln
appveyor.yml
git_push.sh
12 changes: 12 additions & 0 deletions FeatureHubSDK/ClientContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public interface IClientContext
IClientContext Clear();

string GetAttr(string key, string defaultValue);

List<string> GetAttrs(string key);

string DefaultPercentageKey { get; }

Expand Down Expand Up @@ -166,6 +168,16 @@ public string GetAttr(string key, string defaultValue)
return defaultValue;
}

public List<string> GetAttrs(string key)
{
if (_attributes.ContainsKey(key))
{
return _attributes[key];
}

return new List<string>(0);
}


public string DefaultPercentageKey => _attributes.ContainsKey("session") ? _attributes["session"][0] : (_attributes.ContainsKey("userkey") ? _attributes["userkey"][0] : null);
public abstract IFeature this[string name] { get; }
Expand Down
36 changes: 35 additions & 1 deletion FeatureHubSDK/EventServiceListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks;
using IO.FeatureHub.SSE.Model;
using LaunchDarkly.EventSource;
using Newtonsoft.Json;

namespace FeatureHubSDK
{
Expand All @@ -28,12 +29,19 @@ public static class FeatureLogging
public static EventHandler<String> ErrorLogger;
}

class ConfigData
{
[JsonProperty("edge.stale")]
public Boolean stale { get; set; }
}

public class EventServiceListener : IEdgeService
{
private EventSource _eventSource;
private readonly IFeatureHubConfig _featureHost;
private readonly IFeatureRepositoryContext _repository;
private string _xFeatureHubHeader = null;
private bool _closed = false;

public EventServiceListener(IFeatureRepositoryContext repository, IFeatureHubConfig config)
{
Expand All @@ -47,6 +55,8 @@ public EventServiceListener(IFeatureRepositoryContext repository, IFeatureHubCon

public async Task ContextChange(string newHeader)
{
if (_closed) return;

if (_featureHost.ServerEvaluation)
{
if (newHeader != _xFeatureHubHeader)
Expand Down Expand Up @@ -99,6 +109,8 @@ public async Task ContextChange(string newHeader)

public void Init()
{
if (_closed) return;

var config = new Configuration(uri: new UriBuilder(_featureHost.Url).Uri,
backoffResetThreshold: TimeSpan.MaxValue,
delayRetryDuration: TimeSpan.Zero,
Expand Down Expand Up @@ -148,6 +160,25 @@ public void Init()
FeatureLogging.TraceLogger(this, "featurehub: renewing connection process started");
}
break;
case "config":
state = SSEResultState.Config;
if (args.Message.Data != null)
{
var configData = JsonConvert.DeserializeObject<ConfigData>(args.Message.Data);
if (configData.stale)
{
if (FeatureLogging.ErrorLogger != null)
{
FeatureLogging.ErrorLogger(this,
"featurehub: environment has gone stale, closing connection and won't reopen");
}
_closed = true;
_eventSource.Close();
}
}
break;
default:
state = null;
Expand All @@ -161,7 +192,10 @@ public void Init()
if (state == null) return;
_repository.Notify(state.Value, args.Message.Data);
if (state != SSEResultState.Config)
{
_repository.Notify(state.Value, args.Message.Data);
}
if (state == SSEResultState.Failure)
{
Expand Down
13 changes: 7 additions & 6 deletions FeatureHubSDK/FeatureHubSDK.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,21 @@
<RepositoryUrl>https://github.com/featurehub-io/featurehub-dotnet-sdk</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>FeatureFlags FeatureToggles SDK Flags Toggles RemoteConfig</PackageTags>
<PackageReleaseNotes>2.1.5 - FeatureHub 1.5.6 made names not required which broke the SDK.</PackageReleaseNotes>
<Version>2.1.5</Version>
<PackageVersion>2.1.5</PackageVersion>
<PackageReleaseNotes>2.2.0 - updates for caching support, update min dependencies.</PackageReleaseNotes>
<Version>2.2.0</Version>
<PackageVersion>2.2.0</PackageVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="IPNetwork2" Version="2.5.240" />
<PackageReference Include="JsonSubTypes" Version="1.8.0" />
<PackageReference Include="JsonSubTypes" Version="1.9.0" />
<PackageReference Include="LaunchDarkly.EventSource" Version="3.3.2" />
<PackageReference Include="murmurhash" Version="1.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NodaTime" Version="3.0.5" />
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="Polly.Extensions.Http" Version="3.0.0" />
<PackageReference Include="RestSharp" Version="106.12.0" />
<PackageReference Include="RestSharp" Version="106.13.0" />
<None Include="FeatureHub-icon.png" Pack="true" PackagePath="\" />
<None Include="../README.md" Pack="true" PackagePath="\" />
<PackageReference Include="SemanticVersioning" Version="1.3.0" />
Expand Down
60 changes: 25 additions & 35 deletions FeatureHubSDK/StrategyMatchers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public ApplyFeature(IPercentageCalculator percentageCalculator, IMatcherReposito
_matcherRepository = matcherRepository;
}

public Applied Apply(List<RolloutStrategy> strategies, string key, Guid featureValueId,
public Applied Apply(List<FeatureRolloutStrategy> strategies, string key, Guid featureValueId,
IClientContext context)
{
if (context != null && strategies != null && strategies.Count != 0)
Expand Down Expand Up @@ -144,28 +144,29 @@ public ApplyFeature(IPercentageCalculator percentageCalculator, IMatcherReposito
return new Applied(false, null);
}

protected bool MatchAttributes(IClientContext context, RolloutStrategy rsi)
protected bool MatchAttributes(IClientContext context, FeatureRolloutStrategy rsi)
{
foreach (var attr in rsi.Attributes)
{
var suppliedValue = context.GetAttr(attr.FieldName, null);
var suppliedValue = context.GetAttrs(attr.FieldName);
var suppliedEmpty = !suppliedValue.Any();

if (suppliedValue == null && attr.FieldName.ToLower().Equals("now"))
if (suppliedEmpty && attr.FieldName.ToLower().Equals("now"))
{
switch (attr.Type)
{
case RolloutStrategyFieldType.DATE:
suppliedValue = LocalDatePattern.Iso.Format(LocalDate.FromDateTime(DateTime.Now));
suppliedValue = new List<string> { LocalDatePattern.Iso.Format(LocalDate.FromDateTime(DateTime.Now)) };
break;
case RolloutStrategyFieldType.DATETIME:
suppliedValue = LocalDateTimePattern.GeneralIso.Format(LocalDateTime.FromDateTime(DateTime.Now));
suppliedValue = new List<string> { LocalDateTimePattern.GeneralIso.Format(LocalDateTime.FromDateTime(DateTime.Now)) };
break;
}
}

object val = attr.Values;

if (val == null && suppliedValue == null)
if (val == null && suppliedEmpty)
{
if (attr.Conditional != RolloutStrategyAttributeConditional.EQUALS)
{
Expand All @@ -175,12 +176,15 @@ protected bool MatchAttributes(IClientContext context, RolloutStrategy rsi)
continue; //skip
}

if (val == null || suppliedValue == null)
if (val == null || suppliedEmpty)
{
return false;
}

if (!_matcherRepository.FindMatcher(attr).Match(suppliedValue, attr))
var match = suppliedValue.Any(
sValue => _matcherRepository.FindMatcher(attr).Match(sValue, attr));

if (!match)
{
return false;
}
Expand All @@ -202,17 +206,17 @@ private string DeterminePercentageKey(IClientContext context, List<string> rsiPe

public interface IStrategyMatcher
{
bool Match(string suppliedValue, RolloutStrategyAttribute attr);
bool Match(string suppliedValue, FeatureRolloutStrategyAttribute attr);
}

public interface IMatcherRepository
{
IStrategyMatcher FindMatcher(RolloutStrategyAttribute attr);
IStrategyMatcher FindMatcher(FeatureRolloutStrategyAttribute attr);
}

public class MatcherRegistry : IMatcherRepository
{
public IStrategyMatcher FindMatcher(RolloutStrategyAttribute attr)
public IStrategyMatcher FindMatcher(FeatureRolloutStrategyAttribute attr)
{
switch (attr.Type)
{
Expand All @@ -230,16 +234,14 @@ public IStrategyMatcher FindMatcher(RolloutStrategyAttribute attr)
return new BooleanMatcher();
case RolloutStrategyFieldType.IPADDRESS:
return new IPNetworkMatcher();
case null:
return new FallthroughMatcher();
default:
return new FallthroughMatcher();
}
}

private class FallthroughMatcher : IStrategyMatcher
{
public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
public bool Match(string suppliedValue, FeatureRolloutStrategyAttribute attr)
{
return false;
}
Expand All @@ -248,7 +250,7 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)

internal class BooleanMatcher : IStrategyMatcher
{
public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
public bool Match(string suppliedValue, FeatureRolloutStrategyAttribute attr)
{
var val = "true".Equals(suppliedValue);

Expand All @@ -273,7 +275,7 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)

internal class StringMatcher : IStrategyMatcher
{
public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
public bool Match(string suppliedValue, FeatureRolloutStrategyAttribute attr)
{
var vals = attr.Values.Where(v => v != null).Select(v => v.ToString()).ToList();

Expand Down Expand Up @@ -301,8 +303,6 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
return !vals.Any(suppliedValue.Contains);
case RolloutStrategyAttributeConditional.REGEX:
return vals.Any(v => Regex.IsMatch(suppliedValue, v));
case null:
return false;
default:
return false;
}
Expand All @@ -311,7 +311,7 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)

internal class DateMatcher : IStrategyMatcher
{
public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
public bool Match(string suppliedValue, FeatureRolloutStrategyAttribute attr)
{
var suppliedDate = LocalDate.FromDateTime(DateTime.Parse(suppliedValue));
var vals = attr.Values.Where(v => v != null).Select(v => v.ToString()).ToList();
Expand All @@ -338,8 +338,6 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
return !vals.Any(v => suppliedDate.Equals(LocalDate.FromDateTime(DateTime.Parse(v.ToString()))));
case RolloutStrategyAttributeConditional.REGEX:
return vals.Any(v => Regex.IsMatch(suppliedValue, v));
case null:
return false;
default:
return false;
}
Expand All @@ -348,7 +346,7 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)

internal class DateTimeMatcher : IStrategyMatcher
{
public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
public bool Match(string suppliedValue, FeatureRolloutStrategyAttribute attr)
{
var suppliedDate = LocalDateTime.FromDateTime(DateTime.Parse(suppliedValue));
var vals = attr.Values.Where(v => v != null).Select(v => v.ToString()).ToList();
Expand All @@ -375,8 +373,6 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
return !vals.Any(v => suppliedDate.Equals(LocalDateTime.FromDateTime(DateTime.Parse(v.ToString()))));
case RolloutStrategyAttributeConditional.REGEX:
return vals.Any(v => Regex.IsMatch(suppliedValue, v));
case null:
return false;
default:
return false;
}
Expand All @@ -385,12 +381,12 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)

internal class NumberMatcher : IStrategyMatcher
{
private RolloutStrategyAttribute _attr;
private FeatureRolloutStrategyAttribute _attr;

private IEnumerable<decimal> DVals =>
_attr.Values.Where(v => v != null).Select(v => decimal.Parse(v.ToString())).ToList();

public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
public bool Match(string suppliedValue, FeatureRolloutStrategyAttribute attr)
{
this._attr = attr;
var dec = decimal.Parse(suppliedValue);
Expand All @@ -417,8 +413,6 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
return !DVals.Any(v => dec.Equals(v));
case RolloutStrategyAttributeConditional.REGEX:
break;
case null:
return false;
default:
return false;
}
Expand All @@ -429,7 +423,7 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)

internal class SemanticVersionMatcher : IStrategyMatcher
{
public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
public bool Match(string suppliedValue, FeatureRolloutStrategyAttribute attr)
{
var vals = attr.Values.Where(v => v != null).Select(v => new Version(v.ToString())).ToList();
var version = new Version(suppliedValue);
Expand All @@ -456,8 +450,6 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
return !vals.Any(v => version.Equals(v));
case RolloutStrategyAttributeConditional.REGEX:
break;
case null:
return false;
default:
return false;
}
Expand All @@ -468,7 +460,7 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)

internal class IPNetworkMatcher : IStrategyMatcher
{
public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
public bool Match(string suppliedValue, FeatureRolloutStrategyAttribute attr)
{
var vals = attr.Values.Where(v => v != null).Select(v => new IPNetworkProxy(v.ToString())).ToList();
var ip = new IPNetworkProxy(suppliedValue);
Expand All @@ -483,8 +475,6 @@ public bool Match(string suppliedValue, RolloutStrategyAttribute attr)
return !vals.Any(v => v.Contains(ip));
case RolloutStrategyAttributeConditional.REGEX:
return false;
case null:
return false;
default:
return false;
}
Expand Down
Loading

0 comments on commit fe289b0

Please sign in to comment.