Skip to content

Commit 43e9d9c

Browse files
fix(flagd): Return parse error when Rpc Resolver receives a data loss exception (#554)
Signed-off-by: Kyle Julian <[email protected]>
1 parent d35917f commit 43e9d9c

File tree

2 files changed

+61
-3
lines changed

2 files changed

+61
-3
lines changed

src/OpenFeature.Contrib.Providers.Flagd/Resolver/Rpc/RpcResolver.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,8 @@ private FeatureProviderException GetOFException(RpcException e)
444444
return new FeatureProviderException(ErrorType.ProviderNotReady, e.Status.Detail, e);
445445
case StatusCode.InvalidArgument:
446446
return new FeatureProviderException(ErrorType.TypeMismatch, e.Status.Detail, e);
447+
case StatusCode.DataLoss:
448+
return new FeatureProviderException(ErrorType.ParseError, e.Status.Detail, e);
447449
default:
448450
return new FeatureProviderException(ErrorType.General, e.Status.Detail, e);
449451
}

test/OpenFeature.Contrib.Providers.Flagd.Test/Resolver/Rpc/RpcResolverTests.cs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Threading;
34
using System.Threading.Tasks;
45
using Grpc.Core;
56
using NSubstitute;
7+
using NSubstitute.ExceptionExtensions;
8+
using OpenFeature.Constant;
69
using OpenFeature.Contrib.Providers.Flagd.Resolver.Rpc;
10+
using OpenFeature.Error;
711
using OpenFeature.Flagd.Grpc.Evaluation;
812
using OpenFeature.Model;
913
using Xunit;
@@ -71,7 +75,7 @@ public async Task HandleEvents_CallsFlagdProviderEventHandler()
7175
Assert.True(autoResetEvent.WaitOne(TestTimeoutMilliseconds));
7276

7377
Assert.NotNull(flagdProviderEvent);
74-
Assert.Equal(Constant.ProviderEventTypes.ProviderReady, flagdProviderEvent.EventType);
78+
Assert.Equal(ProviderEventTypes.ProviderReady, flagdProviderEvent.EventType);
7579
Assert.Contains("key1", flagdProviderEvent.FlagsChanged);
7680
Assert.Contains("key1", flagdProviderEvent.FlagsChanged);
7781
Assert.Contains("key2", flagdProviderEvent.FlagsChanged);
@@ -111,7 +115,7 @@ public async Task HandleEvents_WithNoFlags_CallsFlagdProviderEventHandler()
111115
Assert.True(autoResetEvent.WaitOne(TestTimeoutMilliseconds));
112116

113117
Assert.NotNull(flagdProviderEvent);
114-
Assert.Equal(Constant.ProviderEventTypes.ProviderReady, flagdProviderEvent.EventType);
118+
Assert.Equal(ProviderEventTypes.ProviderReady, flagdProviderEvent.EventType);
115119
Assert.Empty(flagdProviderEvent.FlagsChanged);
116120
Assert.Equal(Structure.Empty, flagdProviderEvent.SyncMetadata);
117121
}
@@ -152,7 +156,7 @@ public async Task HandleEvents_WhenConfigChanged_CallsFlagdProviderEventHandler(
152156
Assert.True(autoResetEvent.WaitOne(TestTimeoutMilliseconds));
153157

154158
Assert.NotNull(flagdProviderEvent);
155-
Assert.Equal(Constant.ProviderEventTypes.ProviderConfigurationChanged, flagdProviderEvent.EventType);
159+
Assert.Equal(ProviderEventTypes.ProviderConfigurationChanged, flagdProviderEvent.EventType);
156160
Assert.Contains("key1", flagdProviderEvent.FlagsChanged);
157161
Assert.Equal(Structure.Empty, flagdProviderEvent.SyncMetadata);
158162
}
@@ -245,6 +249,58 @@ public async Task HandleEvents_WhenConfigChanged_DeletesCacheItem()
245249
mockCache.Received().Delete("key1");
246250
}
247251

252+
[Theory]
253+
[MemberData(nameof(ResolveValueDataLossData))]
254+
internal async Task ResolveValue_WhenDataLossError_ReturnsParseError(Func<RpcResolver, Task> act, Action<Service.ServiceClient> setup)
255+
{
256+
// Arrange
257+
var mockGrpcClient = Substitute.For<Service.ServiceClient>();
258+
setup(mockGrpcClient);
259+
260+
var config = new FlagdConfig();
261+
var resolver = new RpcResolver(mockGrpcClient, config, null);
262+
263+
// Act
264+
var ex = await Assert.ThrowsAsync<FeatureProviderException>(() => act(resolver));
265+
266+
// Assert
267+
Assert.Equal(ErrorType.ParseError, ex.ErrorType);
268+
Assert.Equal("Parse error", ex.Message);
269+
}
270+
271+
public static IEnumerable<object[]> ResolveValueDataLossData()
272+
{
273+
const string flagKey = "key";
274+
const string errorMessage = "Parse error";
275+
var rpcException = new RpcException(new Status(StatusCode.DataLoss, errorMessage));
276+
277+
yield return new object[]
278+
{
279+
new Func<RpcResolver, Task>(r => r.ResolveBooleanValueAsync(flagKey, false)),
280+
new Action<Service.ServiceClient>(client => client.ResolveBooleanAsync(Arg.Any<ResolveBooleanRequest>()).Throws(rpcException))
281+
};
282+
yield return new object[]
283+
{
284+
new Func<RpcResolver, Task>(r => r.ResolveStringValueAsync(flagKey, "def")),
285+
new Action<Service.ServiceClient>(client => client.ResolveStringAsync(Arg.Any<ResolveStringRequest>()).Throws(rpcException))
286+
};
287+
yield return new object[]
288+
{
289+
new Func<RpcResolver, Task>(r => r.ResolveIntegerValueAsync(flagKey, 3)),
290+
new Action<Service.ServiceClient>(client => client.ResolveIntAsync(Arg.Any<ResolveIntRequest>()).Throws(rpcException))
291+
};
292+
yield return new object[]
293+
{
294+
new Func<RpcResolver, Task>(r => r.ResolveDoubleValueAsync(flagKey, 3.5)),
295+
new Action<Service.ServiceClient>(client => client.ResolveFloatAsync(Arg.Any<ResolveFloatRequest>()).Throws(rpcException))
296+
};
297+
yield return new object[]
298+
{
299+
new Func<RpcResolver, Task>(r => r.ResolveStructureValueAsync(flagKey, new Value(Structure.Builder().Set("value1", true).Build()))),
300+
new Action<Service.ServiceClient>(client => client.ResolveObjectAsync(Arg.Any<ResolveObjectRequest>()).Throws(rpcException))
301+
};
302+
}
303+
248304
private static Service.ServiceClient SetupGrpcStream(List<EventStreamResponse> responses)
249305
{
250306
var mockGrpcClient = Substitute.For<Service.ServiceClient>();

0 commit comments

Comments
 (0)