Skip to content

Commit 86c00eb

Browse files
committed
fix: update-storage-client-error-handling
1 parent af4bf4c commit 86c00eb

12 files changed

Lines changed: 306 additions & 35 deletions

File tree

packages/davinci-client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@forgerock/sdk-oidc": "workspace:*",
2828
"@forgerock/sdk-request-middleware": "workspace:*",
2929
"@forgerock/sdk-types": "workspace:*",
30+
"@forgerock/sdk-utilities": "workspace:*",
3031
"@forgerock/storage": "workspace:*",
3132
"@reduxjs/toolkit": "catalog:",
3233
"effect": "catalog:effect",

packages/davinci-client/src/lib/client.store.ts

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
import { CustomLogger, logger as loggerFn, LogLevel } from '@forgerock/sdk-logger';
1111
import { createStorage } from '@forgerock/storage';
12+
import { isGenericError } from '@forgerock/sdk-utilities';
1213

1314
import { createClientStore, handleUpdateValidateError, RootState } from './client.store.utils.js';
1415
import { nodeSlice } from './node.slice.js';
@@ -126,7 +127,17 @@ export async function davinci<ActionType extends ActionTypes = ActionTypes>({
126127

127128
if (serverSlice && serverSlice.status === 'continue') {
128129
return async () => {
129-
await serverInfo.set(serverSlice);
130+
const setResult = await serverInfo.set(serverSlice);
131+
if (isGenericError(setResult)) {
132+
log.error(setResult.message ?? setResult.error);
133+
return {
134+
error: {
135+
message: setResult.message ?? 'Failed to store server info for external IDP flow',
136+
type: 'internal_error',
137+
},
138+
} as InternalErrorResponse;
139+
}
140+
return;
130141
};
131142
}
132143
return async () => {
@@ -195,27 +206,45 @@ export async function davinci<ActionType extends ActionTypes = ActionTypes>({
195206
}: {
196207
continueToken: string;
197208
}): Promise<InternalErrorResponse | NodeStates> => {
198-
try {
199-
const storedServerInfo = (await serverInfo.get()) as ContinueNode['server'];
200-
await store.dispatch(
201-
davinciApi.endpoints.resume.initiate({ continueToken, serverInfo: storedServerInfo }),
202-
);
203-
await serverInfo.remove();
209+
const storedServerInfo = await serverInfo.get();
204210

205-
const node = nodeSlice.selectSlice(store.getState());
211+
if (storedServerInfo === null) {
212+
log.error('No server info found in storage for resume operation');
213+
return {
214+
error: {
215+
message:
216+
'No server info found in storage. Social login needs server info which is saved in local storage. You may have cleared your browser data.',
217+
type: 'state_error',
218+
},
219+
type: 'internal_error',
220+
};
221+
}
206222

207-
return node;
208-
} catch {
209-
// logger.error('No url found in collector, social login needs a url in the collector');
223+
if (isGenericError(storedServerInfo)) {
224+
log.error(storedServerInfo.message ?? storedServerInfo.error);
210225
return {
211226
error: {
212227
message:
213-
'No url found in storage, social login needs a continue url which is saved in local storage. You may have cleared your browser data',
228+
storedServerInfo.message ??
229+
'Failed to retrieve server info from storage for resume operation',
214230
type: 'internal_error',
215231
},
216232
type: 'internal_error',
217233
};
218234
}
235+
236+
await store.dispatch(
237+
davinciApi.endpoints.resume.initiate({ continueToken, serverInfo: storedServerInfo }),
238+
);
239+
240+
const removeResult = await serverInfo.remove();
241+
if (isGenericError(removeResult)) {
242+
log.warn(removeResult.message ?? 'Failed to remove server info from storage after resume');
243+
}
244+
245+
const node = nodeSlice.selectSlice(store.getState());
246+
247+
return node;
219248
},
220249

221250
/**

packages/davinci-client/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
{
1414
"path": "../sdk-effects/storage"
1515
},
16+
{
17+
"path": "../sdk-utilities"
18+
},
1619
{
1720
"path": "../sdk-types"
1821
},

packages/davinci-client/tsconfig.lib.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
{
3535
"path": "../sdk-effects/storage/tsconfig.lib.json"
3636
},
37+
{
38+
"path": "../sdk-utilities/tsconfig.lib.json"
39+
},
3740
{
3841
"path": "../sdk-types/tsconfig.lib.json"
3942
},

packages/sdk-utilities/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
"test": "pnpm nx nxTest",
3737
"test:watch": "pnpm nx nxTest --watch"
3838
},
39+
"dependencies": {
40+
"@forgerock/sdk-types": "workspace:*"
41+
},
3942
"nx": {
4043
"tags": ["scope:sdk-utilities"]
4144
}

packages/sdk-utilities/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*
88
*/
99

10+
export * from './lib/error/index.js';
1011
export * from './lib/oidc/index.js';
1112
export * from './lib/strings/index.js';
1213
export * from './lib/url/index.js';
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
8+
import { describe, it, expect } from 'vitest';
9+
import { isGenericError } from './error.utils.js';
10+
import type { GenericError } from '@forgerock/sdk-types';
11+
12+
describe('isGenericError', () => {
13+
describe('success cases', () => {
14+
it('isGenericError_ValidGenericErrorWithRequiredFields_ReturnsTrue', () => {
15+
// Arrange
16+
const error: GenericError = {
17+
error: 'storage_error',
18+
type: 'unknown_error',
19+
};
20+
21+
// Act
22+
const result = isGenericError(error);
23+
24+
// Assert
25+
expect(result).toBe(true);
26+
});
27+
28+
it('isGenericError_ValidGenericErrorWithAllFields_ReturnsTrue', () => {
29+
// Arrange
30+
const error: GenericError = {
31+
code: 500,
32+
error: 'storage_error',
33+
message: 'Failed to store value',
34+
status: 500,
35+
type: 'unknown_error',
36+
};
37+
38+
// Act
39+
const result = isGenericError(error);
40+
41+
// Assert
42+
expect(result).toBe(true);
43+
});
44+
45+
it('isGenericError_ValidGenericErrorWithParseErrorType_ReturnsTrue', () => {
46+
// Arrange
47+
const error: GenericError = {
48+
error: 'Parsing_error',
49+
message: 'Error parsing value from session storage',
50+
type: 'parse_error',
51+
};
52+
53+
// Act
54+
const result = isGenericError(error);
55+
56+
// Assert
57+
expect(result).toBe(true);
58+
});
59+
});
60+
61+
describe('failure cases', () => {
62+
it('isGenericError_NullValue_ReturnsFalse', () => {
63+
// Arrange
64+
const value = null;
65+
66+
// Act
67+
const result = isGenericError(value);
68+
69+
// Assert
70+
expect(result).toBe(false);
71+
});
72+
73+
it('isGenericError_UndefinedValue_ReturnsFalse', () => {
74+
// Arrange
75+
const value = undefined;
76+
77+
// Act
78+
const result = isGenericError(value);
79+
80+
// Assert
81+
expect(result).toBe(false);
82+
});
83+
84+
it('isGenericError_PrimitiveString_ReturnsFalse', () => {
85+
// Arrange
86+
const value = 'error string';
87+
88+
// Act
89+
const result = isGenericError(value);
90+
91+
// Assert
92+
expect(result).toBe(false);
93+
});
94+
95+
it('isGenericError_PrimitiveNumber_ReturnsFalse', () => {
96+
// Arrange
97+
const value = 500;
98+
99+
// Act
100+
const result = isGenericError(value);
101+
102+
// Assert
103+
expect(result).toBe(false);
104+
});
105+
106+
it('isGenericError_EmptyObject_ReturnsFalse', () => {
107+
// Arrange
108+
const value = {};
109+
110+
// Act
111+
const result = isGenericError(value);
112+
113+
// Assert
114+
expect(result).toBe(false);
115+
});
116+
117+
it('isGenericError_ObjectMissingErrorProperty_ReturnsFalse', () => {
118+
// Arrange
119+
const value = {
120+
type: 'unknown_error',
121+
message: 'Some error message',
122+
};
123+
124+
// Act
125+
const result = isGenericError(value);
126+
127+
// Assert
128+
expect(result).toBe(false);
129+
});
130+
131+
it('isGenericError_ObjectMissingTypeProperty_ReturnsFalse', () => {
132+
// Arrange
133+
const value = {
134+
error: 'storage_error',
135+
message: 'Some error message',
136+
};
137+
138+
// Act
139+
const result = isGenericError(value);
140+
141+
// Assert
142+
expect(result).toBe(false);
143+
});
144+
145+
it('isGenericError_ObjectWithNonStringError_ReturnsFalse', () => {
146+
// Arrange
147+
const value = {
148+
error: 123,
149+
type: 'unknown_error',
150+
};
151+
152+
// Act
153+
const result = isGenericError(value);
154+
155+
// Assert
156+
expect(result).toBe(false);
157+
});
158+
159+
it('isGenericError_ArrayValue_ReturnsFalse', () => {
160+
// Arrange
161+
const value = ['error', 'type'];
162+
163+
// Act
164+
const result = isGenericError(value);
165+
166+
// Assert
167+
expect(result).toBe(false);
168+
});
169+
170+
it('isGenericError_ValidDataObject_ReturnsFalse', () => {
171+
// Arrange
172+
const value = {
173+
id: '123',
174+
name: 'test',
175+
data: { nested: 'value' },
176+
};
177+
178+
// Act
179+
const result = isGenericError(value);
180+
181+
// Assert
182+
expect(result).toBe(false);
183+
});
184+
});
185+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
8+
import type { GenericError } from '@forgerock/sdk-types';
9+
10+
/**
11+
* Type guard to check if a value is a GenericError.
12+
*
13+
* @param value - The value to check
14+
* @returns True if the value is a GenericError, false otherwise
15+
*/
16+
export function isGenericError(value: unknown): value is GenericError {
17+
return (
18+
typeof value === 'object' &&
19+
value !== null &&
20+
'error' in value &&
21+
typeof (value as GenericError).error === 'string' &&
22+
'type' in value
23+
);
24+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
8+
export { isGenericError } from './error.utils.js';

packages/sdk-utilities/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"files": [],
44
"include": [],
55
"references": [
6+
{
7+
"path": "../sdk-types"
8+
},
69
{
710
"path": "./tsconfig.lib.json"
811
},

0 commit comments

Comments
 (0)