-
Notifications
You must be signed in to change notification settings - Fork 12
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
Adds server error messages to form actions bar #788
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
9b7f37e
Pass error down to form actions
zephraph 2495e3a
Merge branch 'main' into form-error-msgs
zephraph 1fd9b0a
Move error normalization to the API package
zephraph 592b898
Remove old getServerError references
zephraph ec7f710
Fix tests
zephraph 662eaf2
Add error to form actions
zephraph ce1fc05
Adjust layout for side modal, add more error messages
zephraph 961a093
Fix error positioning
zephraph 64464ce
tweak styles further to remove need for spacer
zephraph 0b04815
typesafe handleErrors (#808)
david-crespo 2515b49
Merge branch 'main' into form-error-msgs
zephraph 4f32610
automatically update packer-id
github-actions[bot] 753559e
Clean up global error handling
zephraph 63a7cd8
remove console.log
zephraph File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { navToLogin } from './nav-to-login' | ||
import type { ApiMethods, ErrorResponse, Error } from '.' | ||
import { camelCaseToWords, capitalize } from '@oxide/util' | ||
|
||
const errorCodeFormatter = | ||
(method: keyof ApiMethods) => | ||
(errorCode: string, _: Error): string | undefined => { | ||
switch (errorCode) { | ||
case 'Forbidden': | ||
return 'Action not authorized' | ||
|
||
// TODO: This is a temporary fix for the API; better messages should be provided from there | ||
case 'ObjectAlreadyExists': | ||
if (method.endsWith('Post')) { | ||
let resource = camelCaseToWords(method).slice(-2)[0] | ||
resource = resource.endsWith('s') ? resource.slice(0, -1) : resource | ||
return `${resource} name already exists` | ||
} | ||
return undefined | ||
default: | ||
return undefined | ||
} | ||
} | ||
|
||
export const handleErrors = (method: keyof ApiMethods) => (resp: ErrorResponse) => { | ||
// TODO is this a valid failure condition? | ||
if (!resp) throw 'unknown server error' | ||
|
||
// if logged out, hit /login to trigger login redirect | ||
if (resp.status === 401) { | ||
// TODO-usability: for background requests, a redirect to login without | ||
// warning could come as a surprise to the user, especially because | ||
// sometimes background requests are not directly triggered by a user | ||
// action, e.g., polling or refetching when window regains focus | ||
navToLogin({ includeCurrent: true }) | ||
} | ||
// we need to rethrow because that's how react-query knows it's an error | ||
throw formatServerError(resp, errorCodeFormatter(method)) | ||
} | ||
|
||
function formatServerError( | ||
resp: ErrorResponse, | ||
msgFromCode: (errorCode: string, error: Error) => string | undefined | ||
): ErrorResponse { | ||
const code = resp.error.errorCode | ||
const codeMsg = code && msgFromCode(code, resp.error) | ||
const serverMsg = resp.error.message | ||
|
||
resp.error.message = | ||
codeMsg || getParseError(serverMsg) || serverMsg || 'Unknown server error' | ||
|
||
return resp | ||
} | ||
|
||
function getParseError(message: string | undefined): string | undefined { | ||
if (!message) return undefined | ||
const inner = /^unable to parse body: (.+) at line \d+ column \d+$/.exec(message)?.[1] | ||
return inner && capitalize(inner) | ||
} | ||
|
||
// -- TESTS ---------------- | ||
|
||
if (import.meta.vitest) { | ||
const { describe, it, expect } = import.meta.vitest | ||
const parseError = { | ||
error: { | ||
requestId: '1', | ||
errorCode: null, | ||
message: 'unable to parse body: hello there, you have an error at line 129 column 4', | ||
}, | ||
} as ErrorResponse | ||
|
||
const alreadyExists = { | ||
error: { | ||
requestId: '2', | ||
errorCode: 'ObjectAlreadyExists', | ||
message: 'whatever', | ||
}, | ||
} as ErrorResponse | ||
|
||
describe('getParseError', () => { | ||
it('extracts nice part of error message', () => { | ||
expect(getParseError(parseError.error.message)).toEqual( | ||
'Hello there, you have an error' | ||
) | ||
}) | ||
|
||
it('returns undefined if error does not match pattern', () => { | ||
expect(getParseError('some nonsense')).toBeUndefined() | ||
}) | ||
}) | ||
|
||
describe('getServerError', () => { | ||
it('extracts message from parse errors', () => { | ||
expect(formatServerError(parseError, () => undefined).error.message).toEqual( | ||
'Hello there, you have an error' | ||
) | ||
}) | ||
|
||
it('uses message from code map if error code matches', () => { | ||
expect( | ||
formatServerError(alreadyExists, (code) => | ||
code === 'ObjectAlreadyExists' ? 'that already exists' : undefined | ||
) | ||
).toEqual('that already exists') | ||
}) | ||
|
||
it('falls back to server error message if code not found', () => { | ||
expect(formatServerError(alreadyExists, () => undefined).error.message).toEqual( | ||
'whatever' | ||
) | ||
}) | ||
}) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
This'll go away once we convert this form