Skip to content

Commit 9062dd8

Browse files
[FSSDK-9128] fix: Handle ODP INVALID_IDENTIFIER_EXCEPTION code (#348)
* Add code attribute to extension object * Add unit test for DataFetchingException & INVALID_IDENTIFIER_EXCEPTION instead of InvalidIdentifierException * Fix PR review request * Whoopsie: Forgot to lint before commit * Add more check per PR request
1 parent 0aa7137 commit 9062dd8

File tree

3 files changed

+238
-56
lines changed

3 files changed

+238
-56
lines changed

OptimizelySDK.Tests/OdpTests/OdpSegmentApiManagerTest.cs

Lines changed: 214 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -53,28 +53,28 @@ public void Setup()
5353
public void ShouldParseSuccessfulResponse()
5454
{
5555
const string RESPONSE_JSON = @"
56-
{
57-
""data"": {
58-
""customer"": {
59-
""audiences"": {
60-
""edges"": [
61-
{
62-
""node"": {
63-
""name"": ""has_email"",
64-
""state"": ""qualified"",
65-
}
66-
},
67-
{
68-
""node"": {
69-
""name"": ""has_email_opted_in"",
70-
""state"": ""not-qualified""
56+
{
57+
""data"": {
58+
""customer"": {
59+
""audiences"": {
60+
""edges"": [
61+
{
62+
""node"": {
63+
""name"": ""has_email"",
64+
""state"": ""qualified"",
65+
}
66+
},
67+
{
68+
""node"": {
69+
""name"": ""has_email_opted_in"",
70+
""state"": ""not-qualified""
71+
}
72+
},
73+
]
74+
},
7175
}
72-
},
73-
]
74-
},
75-
}
76-
}
77-
}";
76+
}
77+
}";
7878

7979
var response = new OdpSegmentApiManager().DeserializeSegmentsFromJson(RESPONSE_JSON);
8080

@@ -110,38 +110,207 @@ public void ShouldHandleAttemptToDeserializeInvalidJsonResponse()
110110
}
111111

112112
[Test]
113-
public void ShouldParseErrorResponse()
113+
public void ShouldParseInvalidIdentifierExceptionResponse()
114114
{
115115
const string RESPONSE_JSON = @"
116-
{
117-
""errors"": [
118-
{
119-
""message"": ""Exception while fetching data (/customer) : Exception: could not resolve _fs_user_id = not-real-user-id"",
120-
""locations"": [
121-
{
122-
""line"": 2,
123-
""column"": 3
116+
{
117+
""errors"": [
118+
{
119+
""message"": ""Exception while fetching data (/customer) : Exception: could not resolve _fs_user_id = not-real-user-id"",
120+
""locations"": [
121+
{
122+
""line"": 2,
123+
""column"": 3
124+
}
125+
],
126+
""path"": [
127+
""customer""
128+
],
129+
""extensions"": {
130+
""code"": ""INVALID_IDENTIFIER_EXCEPTION"",
131+
""classification"": ""DataFetchingException""
132+
}
133+
}
134+
],
135+
""data"": {
136+
""customer"": null
124137
}
125-
],
126-
""path"": [
127-
""customer""
128-
],
129-
""extensions"": {
130-
""classification"": ""InvalidIdentifierException""
131-
}
132-
}
133-
],
134-
""data"": {
135-
""customer"": null
136-
}
137-
}";
138+
}";
138139

139140
var response = new OdpSegmentApiManager().DeserializeSegmentsFromJson(RESPONSE_JSON);
140141

141142
Assert.IsNull(response.Data.Customer);
142143
Assert.IsNotNull(response.Errors);
143-
Assert.AreEqual(response.Errors[0].Extensions.Classification,
144-
"InvalidIdentifierException");
144+
Assert.AreEqual("DataFetchingException",
145+
response.Errors[0].Extensions.Classification);
146+
Assert.AreEqual("INVALID_IDENTIFIER_EXCEPTION",
147+
response.Errors[0].Extensions.Code
148+
);
149+
}
150+
151+
[Test]
152+
public void ShouldParseOnlyFirstErrorInvalidIdentifierExceptionResponse()
153+
{
154+
const string RESPONSE_JSON = @"
155+
{
156+
""errors"": [
157+
{
158+
""message"": ""Exception while fetching data (/customer) : Exception: could not resolve _fs_user_id = not-real-user-id"",
159+
""locations"": [
160+
{
161+
""line"": 2,
162+
""column"": 3
163+
}
164+
],
165+
""path"": [
166+
""customer""
167+
],
168+
""extensions"": {
169+
""code"": ""INVALID_IDENTIFIER_EXCEPTION"",
170+
""classification"": ""DataFetchingException""
171+
}
172+
},
173+
{
174+
""message"": ""Second Ignored Exception while fetching data (/desks) : Exception: yet another exception not yet known"",
175+
""locations"": [
176+
{
177+
""line"": 4,
178+
""column"": 5
179+
}
180+
],
181+
""path"": [
182+
""desks/wooden""
183+
],
184+
""extensions"": {
185+
""code"": ""SECOND_IGNORED_UNPLANNED_EXCEPTION"",
186+
""classification"": ""IgnoredNewException""
187+
}
188+
}
189+
],
190+
""data"": {
191+
""customer"": null
192+
}
193+
}";
194+
var httpClient = HttpClientTestUtil.MakeHttpClient(HttpStatusCode.OK, RESPONSE_JSON);
195+
var manager =
196+
new OdpSegmentApiManager(_mockLogger.Object, _mockErrorHandler.Object, httpClient);
197+
198+
var segments = manager.FetchSegments(
199+
VALID_ODP_PUBLIC_KEY,
200+
ODP_GRAPHQL_HOST,
201+
Constants.FS_USER_ID,
202+
"tester-156",
203+
_segmentsToCheck);
204+
205+
206+
Assert.IsNull(segments);
207+
_mockLogger.Verify(
208+
l => l.Log(LogLevel.WARN, "Audience segments fetch failed (invalid identifier)"),
209+
Times.Once);
210+
}
211+
212+
[Test]
213+
public void ShouldParseOnlyFirstErrorThatOdpThrowsAtUsResponse()
214+
{
215+
const string RESPONSE_JSON = @"
216+
{
217+
""errors"": [
218+
{
219+
""message"": ""Exception while fetching data (/chairs) : Exception: could not the chair = musical"",
220+
""locations"": [
221+
{
222+
""line"": 4,
223+
""column"": 1
224+
}
225+
],
226+
""path"": [
227+
""chair/musical""
228+
],
229+
""extensions"": {
230+
""classification"": ""YetKnownOdpException""
231+
}
232+
},
233+
{
234+
""message"": ""Second Ignored Exception while fetching data (/desks) : Exception: yet another exception not yet known"",
235+
""locations"": [
236+
{
237+
""line"": 4,
238+
""column"": 5
239+
}
240+
],
241+
""path"": [
242+
""desks/wooden""
243+
],
244+
""extensions"": {
245+
""code"": ""SECOND_IGNORED_UNPLANNED_EXCEPTION"",
246+
""classification"": ""IgnoredNewException""
247+
}
248+
}
249+
],
250+
""data"": {
251+
""customer"": null
252+
}
253+
}";
254+
var httpClient = HttpClientTestUtil.MakeHttpClient(HttpStatusCode.OK, RESPONSE_JSON);
255+
var manager =
256+
new OdpSegmentApiManager(_mockLogger.Object, _mockErrorHandler.Object, httpClient);
257+
258+
var segments = manager.FetchSegments(
259+
VALID_ODP_PUBLIC_KEY,
260+
ODP_GRAPHQL_HOST,
261+
Constants.FS_USER_ID,
262+
"tester-325",
263+
_segmentsToCheck);
264+
265+
266+
Assert.IsNull(segments);
267+
_mockLogger.Verify(
268+
l => l.Log(LogLevel.ERROR,
269+
"Audience segments fetch failed (YetKnownOdpException)"),
270+
Times.Once);
271+
}
272+
273+
274+
[Test]
275+
public void ShouldParseBadExtensionsShapeInResponse()
276+
{
277+
const string RESPONSE_JSON = @"
278+
{
279+
""errors"": [
280+
{
281+
""message"": ""Exception while fetching data (/chairs) : Exception: could not the chair = musical"",
282+
""locations"": [
283+
{
284+
""line"": 4,
285+
""column"": 1
286+
}
287+
],
288+
""path"": [
289+
""chair/musical""
290+
],
291+
""extensions"": { }
292+
}
293+
],
294+
""data"": {
295+
""customer"": null
296+
}
297+
}";
298+
var httpClient = HttpClientTestUtil.MakeHttpClient(HttpStatusCode.OK, RESPONSE_JSON);
299+
var manager =
300+
new OdpSegmentApiManager(_mockLogger.Object, _mockErrorHandler.Object, httpClient);
301+
302+
var segments = manager.FetchSegments(
303+
VALID_ODP_PUBLIC_KEY,
304+
ODP_GRAPHQL_HOST,
305+
Constants.FS_USER_ID,
306+
"tester-895",
307+
_segmentsToCheck);
308+
309+
310+
Assert.IsNull(segments);
311+
_mockLogger.Verify(
312+
l => l.Log(LogLevel.ERROR, "Audience segments fetch failed (decode error)"),
313+
Times.Once);
145314
}
146315

147316
[Test]

OptimizelySDK/Odp/Entity/Extension.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 Optimizely
2+
* Copyright 2022-2023 Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,5 +25,10 @@ public class Extension
2525
/// Named exception type from the error
2626
/// </summary>
2727
public string Classification { get; set; }
28+
29+
/// <summary>
30+
/// Code of exception
31+
/// </summary>
32+
public string Code { get; set; }
2833
}
2934
}

OptimizelySDK/Odp/OdpSegmentApiManager.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,17 @@ public string[] FetchSegments(string apiKey, string apiHost, string userKey,
117117

118118
if (segments.HasErrors)
119119
{
120-
var errors = string.Join(";", segments.Errors.Select(e => e.ToString()));
121-
122-
_logger.Log(LogLevel.ERROR, $"{AUDIENCE_FETCH_FAILURE_MESSAGE} ({errors})");
120+
var firstError = segments.Errors.First();
121+
if (firstError.Extensions?.Code == "INVALID_IDENTIFIER_EXCEPTION")
122+
{
123+
var message = $"{AUDIENCE_FETCH_FAILURE_MESSAGE} (invalid identifier)";
124+
_logger.Log(LogLevel.WARN, message);
125+
}
126+
else
127+
{
128+
var errorMessage = firstError.Extensions?.Classification ?? "decode error";
129+
_logger.Log(LogLevel.ERROR, $"{AUDIENCE_FETCH_FAILURE_MESSAGE} ({errorMessage})");
130+
}
123131

124132
return null;
125133
}
@@ -131,10 +139,10 @@ public string[] FetchSegments(string apiKey, string apiHost, string userKey,
131139
return null;
132140
}
133141

134-
return segments.Data.Customer.Audiences.Edges.
135-
Where(e => e.Node.State == BaseCondition.QUALIFIED).
136-
Select(e => e.Node.Name).
137-
ToArray();
142+
return segments.Data.Customer.Audiences.Edges
143+
.Where(e => e.Node.State == BaseCondition.QUALIFIED)
144+
.Select(e => e.Node.Name)
145+
.ToArray();
138146
}
139147

140148
/// <summary>
@@ -155,9 +163,9 @@ IEnumerable segmentsToCheck
155163
""userId"": ""{userValue}"",
156164
""audiences"": {audiences}
157165
}
158-
}".Replace("{userKey}", userKey).
159-
Replace("{userValue}", userValue).
160-
Replace("{audiences}", JsonConvert.SerializeObject(segmentsToCheck));
166+
}".Replace("{userKey}", userKey)
167+
.Replace("{userValue}", userValue)
168+
.Replace("{audiences}", JsonConvert.SerializeObject(segmentsToCheck));
161169
}
162170

163171
/// <summary>

0 commit comments

Comments
 (0)