Skip to content
Merged
2 changes: 1 addition & 1 deletion src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public FlagdProvider(FlagdConfig config)

if (_config.ResolverType == ResolverType.IN_PROCESS)
{
var jsonSchemaValidator = new JsonSchemaValidator(null, _config.Logger);
var jsonSchemaValidator = new JsonSchemaValidator(_config.Logger);
_resolver = new InProcessResolver(_config, jsonSchemaValidator);
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="schemas\json\flags.json" Link="Resources\flags.json" />
<EmbeddedResource Include="schemas\json\targeting.json" Link="Resources\targeting.json" />
</ItemGroup>
<ItemGroup>
<!-- The schema.proto file referenced here will be used to automatically generate the Grpc
client when executing 'dotnet build' -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

#nullable enable

namespace OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess;

internal sealed class FlagdJsonSchemaEmbeddedResourceReader : IFlagdJsonSchemaProvider
{
const string TargetingJsonResourceName = "OpenFeature.Contrib.Providers.Flagd.Resources.targeting.json";
const string FlagJsonResourceName = "OpenFeature.Contrib.Providers.Flagd.Resources.flags.json";

public Task<string> ReadSchemaAsync(FlagdSchema flagdSchema, CancellationToken cancellationToken = default)
{
return flagdSchema switch
{
FlagdSchema.Targeting => this.ReadAsStringAsync(TargetingJsonResourceName, cancellationToken),
FlagdSchema.Flags => this.ReadAsStringAsync(FlagJsonResourceName, cancellationToken),
_ => throw new ArgumentOutOfRangeException(nameof(flagdSchema), flagdSchema, null)
};
}

private async Task<string> ReadAsStringAsync(string resourceName, CancellationToken cancellationToken = default)
{
var assembly = typeof(FlagdJsonSchemaEmbeddedResourceReader).Assembly!;

using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream == null)
{
throw new InvalidOperationException($"Embedded resource not found: '{resourceName}'.");
}

using var streamReader = new StreamReader(stream);

#if NET8_0_OR_GREATER
return await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
#else
return await streamReader.ReadToEndAsync().ConfigureAwait(false);
#endif
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess;

internal enum FlagdSchema
{
Targeting,
Flags
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Threading;
using System.Threading.Tasks;

namespace OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess;

#nullable enable

internal interface IFlagdJsonSchemaProvider
{
Task<string> ReadSchemaAsync(FlagdSchema flagdSchema, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Threading;
using System.Threading.Tasks;

#nullable enable

namespace OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess;

internal interface IJsonSchemaValidator
{
Task InitializeAsync(CancellationToken cancellationToken = default);
void Validate(string configuration);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
Expand All @@ -8,62 +7,29 @@

namespace OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess;

internal interface IJsonSchemaValidator
{
Task InitializeAsync(CancellationToken cancellationToken = default);
void Validate(string configuration);
}

internal class JsonSchemaValidator : IJsonSchemaValidator
{
private readonly HttpClient _client;
private readonly ILogger _logger;
private readonly IFlagdJsonSchemaProvider _flagdJsonSchemaProvider;

private JsonSchema _validator;

internal JsonSchemaValidator(HttpClient client, ILogger logger)
internal JsonSchemaValidator(ILogger logger)
: this(logger, new FlagdJsonSchemaEmbeddedResourceReader())
{
if (client == null)
{
client = new HttpClient
{
BaseAddress = new Uri("https://flagd.dev"),
};
}
}

_client = client;
_logger = logger;
internal JsonSchemaValidator(ILogger logger, IFlagdJsonSchemaProvider flagdJsonSchemaProvider)
{
this._logger = logger;
this._flagdJsonSchemaProvider = flagdJsonSchemaProvider;
}

public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
try
{
var targetingTask = _client.GetAsync("/schema/v0/targeting.json", cancellationToken);
var flagTask = _client.GetAsync("/schema/v0/flags.json", cancellationToken);

await Task.WhenAll(targetingTask, flagTask).ConfigureAwait(false);

var targeting = targetingTask.Result;
var flag = flagTask.Result;

if (!targeting.IsSuccessStatusCode)
{
_logger.LogWarning("Unable to retrieve Flagd targeting JSON Schema, status code: {StatusCode}", targeting.StatusCode);
return;
}

if (!flag.IsSuccessStatusCode)
{
_logger.LogWarning("Unable to retrieve Flagd flags JSON Schema, status code: {StatusCode}", flag.StatusCode);
return;
}

#if NET5_0_OR_GREATER
var targetingJson = await targeting.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
#else
var targetingJson = await targeting.Content.ReadAsStringAsync().ConfigureAwait(false);
#endif

var targetingJson = await this._flagdJsonSchemaProvider.ReadSchemaAsync(FlagdSchema.Targeting, cancellationToken).ConfigureAwait(false);
var targetingSchema = await JsonSchema.FromJsonAsync(targetingJson, "targeting.json", schema =>
{
var schemaResolver = new JsonSchemaResolver(schema, new SystemTextJsonSchemaGeneratorSettings());
Expand All @@ -72,11 +38,7 @@ public async Task InitializeAsync(CancellationToken cancellationToken = default)
return resolver;
}, cancellationToken).ConfigureAwait(false);

#if NET5_0_OR_GREATER
var flagJson = await flag.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
#else
var flagJson = await flag.Content.ReadAsStringAsync().ConfigureAwait(false);
#endif
var flagJson = await this._flagdJsonSchemaProvider.ReadSchemaAsync(FlagdSchema.Flags, cancellationToken).ConfigureAwait(false);
var flagSchema = await JsonSchema.FromJsonAsync(flagJson, "flags.json", schema =>
{
var schemaResolver = new JsonSchemaResolver(schema, new SystemTextJsonSchemaGeneratorSettings());
Expand Down
Loading