-
Notifications
You must be signed in to change notification settings - Fork 3
fix!: update-error-handling-across-packages #524
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,40 @@ | ||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||
| '@forgerock/storage': major | ||||||||||||||||||||||||||||||||||||||
| '@forgerock/sdk-oidc': major | ||||||||||||||||||||||||||||||||||||||
| '@forgerock/journey-client': major | ||||||||||||||||||||||||||||||||||||||
| '@forgerock/oidc-client': major | ||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| **BREAKING CHANGE**: Journey client methods now return `GenericError` instead of `undefined` for error cases. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| ## What Changed | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| The `start`, `next`, `resume`, and `terminate` methods now return a `GenericError` object instead of `undefined` when encountering unknown step types or error conditions. This aligns the journey client with the DaVinci client's error handling patterns. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| ### Return Type Changes | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| | Method | Before | After | | ||||||||||||||||||||||||||||||||||||||
| | ----------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------- | | ||||||||||||||||||||||||||||||||||||||
| | `start` | `JourneyStep \| JourneyLoginSuccess \| JourneyLoginFailure \| undefined` | `JourneyStep \| JourneyLoginSuccess \| JourneyLoginFailure \| GenericError` | | ||||||||||||||||||||||||||||||||||||||
| | `next` | `JourneyStep \| JourneyLoginSuccess \| JourneyLoginFailure \| undefined` | `JourneyStep \| JourneyLoginSuccess \| JourneyLoginFailure \| GenericError` | | ||||||||||||||||||||||||||||||||||||||
| | `resume` | `JourneyStep \| JourneyLoginSuccess \| JourneyLoginFailure \| undefined` | `JourneyStep \| JourneyLoginSuccess \| JourneyLoginFailure \| GenericError` | | ||||||||||||||||||||||||||||||||||||||
| | `terminate` | `void` | `void \| GenericError` | | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| ## Migration Guide | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Before: | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||||||||||||||||
| const step = await client.start(); | ||||||||||||||||||||||||||||||||||||||
| if (step) { | ||||||||||||||||||||||||||||||||||||||
| // Use step | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| After: const result = await client.start(); | ||||||||||||||||||||||||||||||||||||||
| if ('error' in result) { | ||||||||||||||||||||||||||||||||||||||
| // Handle error | ||||||||||||||||||||||||||||||||||||||
| console.error(result.message); | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| // Use step | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+25
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Migration guide has a formatting issue. The "After:" section on line 33 is incorrectly placed inside the code block. This will render incorrectly in the changeset documentation. 📝 Suggested fix Before:
```ts
const step = await client.start();
if (step) {
// Use step
}
+```
-After: const result = await client.start();
+After:
+
+```ts
+const result = await client.start();
if ('error' in result) {
// Handle error
console.error(result.message);
} else {
// Use step
}
```📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,12 +8,19 @@ | |||||||||||||||||||||||||
| import { callbackType } from '@forgerock/sdk-types'; | ||||||||||||||||||||||||||
| import { afterEach, describe, expect, test, vi } from 'vitest'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import type { Step } from '@forgerock/sdk-types'; | ||||||||||||||||||||||||||
| import type { GenericError, Step } from '@forgerock/sdk-types'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import { journey } from './client.store.js'; | ||||||||||||||||||||||||||
| import { createJourneyStep } from './step.utils.js'; | ||||||||||||||||||||||||||
| import { JourneyClientConfig } from './config.types.js'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Type guard to check if a result is a GenericError | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| function isGenericError(result: unknown): result is GenericError { | ||||||||||||||||||||||||||
| return typeof result === 'object' && result !== null && 'error' in result && 'type' in result; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+17
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent type guard implementations. This type guard checks for ♻️ Suggested alignment function isGenericError(result: unknown): result is GenericError {
- return typeof result === 'object' && result !== null && 'error' in result && 'type' in result;
+ return typeof result === 'object' && result !== null && 'error' in result && 'message' in result;
}Or consider extracting a shared 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Create a singleton mock instance for storage | ||||||||||||||||||||||||||
| const mockStorageInstance = { | ||||||||||||||||||||||||||
| get: vi.fn(), | ||||||||||||||||||||||||||
|
|
@@ -65,13 +72,16 @@ describe('journey-client', () => { | |||||||||||||||||||||||||
| const client = await journey({ config: mockConfig }); | ||||||||||||||||||||||||||
| const step = await client.start(); | ||||||||||||||||||||||||||
| expect(step).toBeDefined(); | ||||||||||||||||||||||||||
| expect(isGenericError(step)).toBe(false); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| expect(mockFetch).toHaveBeenCalledTimes(1); | ||||||||||||||||||||||||||
| const request = mockFetch.mock.calls[0][0] as Request; | ||||||||||||||||||||||||||
| // TODO: This should be /journeys?_action=start, but the current implementation calls /authenticate | ||||||||||||||||||||||||||
| expect(request.url).toBe('https://test.com/json/realms/root/authenticate'); | ||||||||||||||||||||||||||
| expect(step).toHaveProperty('type', 'Step'); | ||||||||||||||||||||||||||
| expect(step && step.payload).toEqual(mockStepResponse); | ||||||||||||||||||||||||||
| if (!isGenericError(step)) { | ||||||||||||||||||||||||||
| expect(step.payload).toEqual(mockStepResponse); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| test('next() should send the current step and return the next step', async () => { | ||||||||||||||||||||||||||
|
|
@@ -101,6 +111,7 @@ describe('journey-client', () => { | |||||||||||||||||||||||||
| const client = await journey({ config: mockConfig }); | ||||||||||||||||||||||||||
| const nextStep = await client.next(initialStep, {}); | ||||||||||||||||||||||||||
| expect(nextStep).toBeDefined(); | ||||||||||||||||||||||||||
| expect(isGenericError(nextStep)).toBe(false); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| expect(mockFetch).toHaveBeenCalledTimes(1); | ||||||||||||||||||||||||||
| const request = mockFetch.mock.calls[0][0] as Request; | ||||||||||||||||||||||||||
|
|
@@ -109,7 +120,9 @@ describe('journey-client', () => { | |||||||||||||||||||||||||
| expect(request.method).toBe('POST'); | ||||||||||||||||||||||||||
| expect(await request.json()).toEqual(initialStep.payload); | ||||||||||||||||||||||||||
| expect(nextStep).toHaveProperty('type', 'Step'); | ||||||||||||||||||||||||||
| expect(nextStep && nextStep.payload).toEqual(nextStepPayload); | ||||||||||||||||||||||||||
| if (!isGenericError(nextStep)) { | ||||||||||||||||||||||||||
| expect(nextStep.payload).toEqual(nextStepPayload); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| test('redirect() should store the step and call location.assign', async () => { | ||||||||||||||||||||||||||
|
|
@@ -169,7 +182,9 @@ describe('journey-client', () => { | |||||||||||||||||||||||||
| expect(request.method).toBe('POST'); | ||||||||||||||||||||||||||
| expect(await request.json()).toEqual(previousStepPayload); | ||||||||||||||||||||||||||
| expect(step).toHaveProperty('type', 'Step'); | ||||||||||||||||||||||||||
| expect(step && step.payload).toEqual(nextStepPayload); | ||||||||||||||||||||||||||
| if (!isGenericError(step)) { | ||||||||||||||||||||||||||
| expect(step.payload).toEqual(nextStepPayload); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| test('should correctly resume with a plain Step object from storage', async () => { | ||||||||||||||||||||||||||
|
|
@@ -198,7 +213,9 @@ describe('journey-client', () => { | |||||||||||||||||||||||||
| expect(request.method).toBe('POST'); | ||||||||||||||||||||||||||
| expect(await request.json()).toEqual(plainStepPayload); // Expect the plain payload to be sent | ||||||||||||||||||||||||||
| expect(step).toHaveProperty('type', 'Step'); // The returned step should still be an JourneyStep instance | ||||||||||||||||||||||||||
| expect(step && step.payload).toEqual(nextStepPayload); | ||||||||||||||||||||||||||
| if (!isGenericError(step)) { | ||||||||||||||||||||||||||
| expect(step.payload).toEqual(nextStepPayload); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| test('should throw an error if a previous step is required but not found', async () => { | ||||||||||||||||||||||||||
|
|
@@ -234,7 +251,9 @@ describe('journey-client', () => { | |||||||||||||||||||||||||
| const url = new URL(request.url); | ||||||||||||||||||||||||||
| expect(url.origin + url.pathname).toBe('https://test.com/json/realms/root/authenticate'); | ||||||||||||||||||||||||||
| expect(step).toHaveProperty('type', 'Step'); | ||||||||||||||||||||||||||
| expect(step && step.payload).toEqual(mockStepResponse); | ||||||||||||||||||||||||||
| if (!isGenericError(step)) { | ||||||||||||||||||||||||||
| expect(step.payload).toEqual(mockStepResponse); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: ForgeRock/ping-javascript-sdk
Length of output: 55
🏁 Script executed:
Repository: ForgeRock/ping-javascript-sdk
Length of output: 497
🏁 Script executed:
Repository: ForgeRock/ping-javascript-sdk
Length of output: 1877
🏁 Script executed:
Repository: ForgeRock/ping-javascript-sdk
Length of output: 1706
🏁 Script executed:
Repository: ForgeRock/ping-javascript-sdk
Length of output: 55
🏁 Script executed:
Repository: ForgeRock/ping-javascript-sdk
Length of output: 1695
🏁 Script executed:
Repository: ForgeRock/ping-javascript-sdk
Length of output: 1229
🏁 Script executed:
Repository: ForgeRock/ping-javascript-sdk
Length of output: 134
🏁 Script executed:
Repository: ForgeRock/ping-javascript-sdk
Length of output: 522
🏁 Script executed:
Repository: ForgeRock/ping-javascript-sdk
Length of output: 361
Remove
@forgerock/storagefrom this changeset.The changeset marks
@forgerock/storageas a major version bump, but the storage package has no changes in this PR. The storage API's transition to returnGenericError | nullwas already completed in SDKS-4361 and is documented in the separatehip-owls-rule.mdchangeset. The breaking changes described here apply only to journey-client methods, not storage.🤖 Prompt for AI Agents