Skip to content

Commit f072d7d

Browse files
committed
fix(journey-client): error handling in journey.api file to keep infra separate from pure utils
1 parent dcf367f commit f072d7d

4 files changed

Lines changed: 71 additions & 86 deletions

File tree

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import type { Step } from '@forgerock/sdk-types';
1919

2020
import { createJourneyStore } from './client.store.utils.js';
2121
import { configSlice } from './config.slice.js';
22-
import { journeyApi } from './journey.api.js';
22+
import { journeyApi, resolveRTKResponse } from './journey.api.js';
2323
import { createStorage } from '@forgerock/storage';
24-
import { createJourneyObject, resolveJourneyResult } from './journey.utils.js';
24+
import { createJourneyObject } from './journey.utils.js';
2525
import { wellknownApi } from './wellknown.api.js';
2626

2727
import type { JourneyStep } from './step.utils.js';
@@ -156,7 +156,14 @@ export async function journey<ActionType extends ActionTypes = ActionTypes>({
156156
const self: JourneyClient = {
157157
start: async (options?: StartParam) => {
158158
const { data, error } = await store.dispatch(journeyApi.endpoints.start.initiate(options));
159-
return resolveJourneyResult(data, error);
159+
160+
const result = resolveRTKResponse(data, error);
161+
162+
if ('error' in result) {
163+
return result;
164+
}
165+
166+
return createJourneyObject(result);
160167
},
161168

162169
/**
@@ -166,7 +173,14 @@ export async function journey<ActionType extends ActionTypes = ActionTypes>({
166173
const { data, error } = await store.dispatch(
167174
journeyApi.endpoints.next.initiate({ step, options }),
168175
);
169-
return resolveJourneyResult(data, error);
176+
177+
const result = resolveRTKResponse(data, error);
178+
179+
if ('error' in result) {
180+
return result;
181+
}
182+
183+
return createJourneyObject(result);
170184
},
171185

172186
// TODO: Remove the actual redirect from this method and just return the URL to the caller

packages/journey-client/src/lib/journey.api.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import { REQUESTED_WITH, getEndpointPath, stringify, resolve } from '@forgerock/
1010
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';
1111

1212
import type { Step } from '@forgerock/sdk-types';
13+
import type { GenericError } from '@forgerock/sdk-types';
1314
import type { logger as loggerFn } from '@forgerock/sdk-logger';
15+
import type { SerializedError } from '@reduxjs/toolkit';
1416
import type {
1517
BaseQueryApi,
1618
BaseQueryFn,
@@ -197,3 +199,51 @@ export const journeyApi = createApi({
197199
}),
198200
}),
199201
});
202+
203+
/**
204+
* Resolves an RTK Query response to a Step or GenericError.
205+
* Handles error evaluation and extraction of step data from error responses.
206+
*
207+
* @param data - The Step data returned by the RTK Query endpoint, if any
208+
* @param error - The error returned by the RTK Query endpoint, if any
209+
* @returns A Step if valid data exists, or a GenericError otherwise
210+
*/
211+
export function resolveRTKResponse(
212+
data: Step | undefined,
213+
error: FetchBaseQueryError | SerializedError | undefined,
214+
): Step | GenericError {
215+
// Check for FetchBaseQueryError with status
216+
if (error && 'status' in error) {
217+
const stepData = error.data;
218+
// If error.data is an object, return it as step data for login failure
219+
if (typeof stepData === 'object' && stepData !== null) {
220+
return stepData as Step;
221+
}
222+
const errMsg = 'error' in error ? error.error : JSON.stringify(error.data);
223+
return {
224+
error: 'request_failed',
225+
message: `Request failed: ${errMsg}`,
226+
type: 'unknown_error',
227+
};
228+
}
229+
230+
// Check for SerializedError
231+
if (error && 'message' in error) {
232+
return {
233+
error: 'request_failed',
234+
message: `Request failed: ${error.message ?? 'Unknown error'}`,
235+
type: 'unknown_error',
236+
};
237+
}
238+
239+
// Return data if available, otherwise no_response_data error
240+
if (data) {
241+
return data;
242+
}
243+
244+
return {
245+
error: 'no_response_data',
246+
message: 'No data received from server',
247+
type: 'unknown_error',
248+
};
249+
}

packages/journey-client/src/lib/journey.utils.test.ts

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { describe, expect, it } from 'vitest';
1010
import { StepType } from '../types.js';
1111
import { type Step } from '../index.js';
1212

13-
import { createJourneyObject, resolveJourneyResult } from './journey.utils.js';
13+
import { createJourneyObject } from './journey.utils.js';
1414
import type { JourneyLoginFailure } from './login-failure.utils.js';
1515

1616
describe('createJourneyObject', () => {
@@ -41,47 +41,16 @@ describe('createJourneyObject', () => {
4141
expect(result).toHaveProperty('type', StepType.LoginSuccess);
4242
expect(result).toHaveProperty('payload', successPayload);
4343
});
44-
});
45-
46-
describe('resolveJourneyResult - no_response_data', () => {
47-
it('returns no_response_data GenericError when no data and no error', () => {
48-
const result = resolveJourneyResult(undefined, undefined);
49-
50-
expect(result).toMatchObject({
51-
error: 'no_response_data',
52-
message: 'No data received from server',
53-
type: 'unknown_error',
54-
});
55-
});
56-
});
57-
58-
describe('toJourneyResult', () => {
59-
it('returns LoginFailure when FetchBaseQueryError has an object payload', () => {
60-
const result = resolveJourneyResult(undefined, { status: 500, data: { foo: 'bar' } });
61-
62-
expect(result).toHaveProperty('type', StepType.LoginFailure);
63-
expect(result).toHaveProperty('payload', { foo: 'bar' });
64-
});
65-
66-
it('returns request_failed GenericError for SerializedError', () => {
67-
const result = resolveJourneyResult(undefined, { message: 'Network failure' });
68-
69-
expect(result).toMatchObject({
70-
error: 'request_failed',
71-
message: 'Request failed: Network failure',
72-
type: 'unknown_error',
73-
});
74-
});
7544

76-
it('returns LoginFailure when FetchBaseQueryError contains a failure Step payload', () => {
45+
it('returns LoginFailure when provided a step without authId or successUrl', () => {
7746
const failurePayload: Step = {
7847
code: 401,
7948
message: 'Access Denied',
8049
reason: 'Unauthorized',
8150
detail: { failureUrl: 'https://example.com/failure' },
8251
};
8352

84-
const result = resolveJourneyResult(undefined, { status: 401, data: failurePayload });
53+
const result = createJourneyObject(failurePayload);
8554

8655
expect(result).not.toHaveProperty('error');
8756
expect(result).toHaveProperty('type', StepType.LoginFailure);

packages/journey-client/src/lib/journey.utils.ts

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
*/
77

88
import { StepType } from '@forgerock/sdk-types';
9-
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query';
10-
import type { SerializedError } from '@reduxjs/toolkit';
119

1210
import type { GenericError, Step } from '@forgerock/sdk-types';
1311

@@ -54,49 +52,3 @@ export function createJourneyObject(
5452
};
5553
}
5654
}
57-
58-
/**
59-
* Maps an RTK Query dispatch result to a JourneyResult.
60-
* Narrows RTK errors first; only calls createJourneyObject when data is confirmed present.
61-
*
62-
* @param data - The Step data returned by the RTK Query endpoint, if any
63-
* @param error - The error returned by the RTK Query endpoint, if any
64-
* @returns A JourneyResult representing the outcome of the journey step
65-
*/
66-
export function resolveJourneyResult(
67-
data: Step | undefined,
68-
error: FetchBaseQueryError | SerializedError | undefined,
69-
): JourneyResult {
70-
if (error && 'status' in error) {
71-
const stepData = error.data;
72-
// If error.data is an object, we treat it as login failure
73-
if (typeof stepData === 'object' && stepData !== null) {
74-
return createJourneyObject(stepData as Step);
75-
}
76-
77-
const errMsg = 'error' in error ? error.error : JSON.stringify(error.data);
78-
return {
79-
error: 'request_failed',
80-
message: `Request failed: ${errMsg}`,
81-
type: 'unknown_error',
82-
};
83-
}
84-
85-
if (error && 'message' in error) {
86-
return {
87-
error: 'request_failed',
88-
message: `Request failed: ${error.message ?? 'Unknown error'}`,
89-
type: 'unknown_error',
90-
};
91-
}
92-
93-
if (!data) {
94-
return {
95-
error: 'no_response_data',
96-
message: 'No data received from server',
97-
type: 'unknown_error',
98-
};
99-
}
100-
101-
return createJourneyObject(data);
102-
}

0 commit comments

Comments
 (0)