mirror of
https://github.com/hashicorp/vault.git
synced 2025-11-29 14:41:09 +01:00
[UI] API Service Error Parsing (#30454)
* adds error parsing method to api service * replaces apiErrorMessage util instances with api service parseError * removes apiErrorMessage util and tests * removes ApiError type * fixes issue in isLocalStorageSupported error handling
This commit is contained in:
parent
70c0a7af97
commit
38396b5882
@ -13,7 +13,6 @@ import { allSupportedAuthBackends, supportedAuthBackends } from 'vault/helpers/s
|
|||||||
import { task } from 'ember-concurrency';
|
import { task } from 'ember-concurrency';
|
||||||
import { waitFor } from '@ember/test-waiters';
|
import { waitFor } from '@ember/test-waiters';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @module AuthForm
|
* @module AuthForm
|
||||||
@ -190,8 +189,8 @@ export default Component.extend(DEFAULTS, {
|
|||||||
this.set('token', response.auth.clientToken);
|
this.set('token', response.auth.clientToken);
|
||||||
this.send('doSubmit');
|
this.send('doSubmit');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = yield apiErrorMessage(e);
|
const { message } = yield this.api.parseError(e);
|
||||||
this.set('error', `Token unwrap failed: ${error}`);
|
this.set('error', `Token unwrap failed: ${message}`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
import { task } from 'ember-concurrency';
|
import { task } from 'ember-concurrency';
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
router: service(),
|
router: service(),
|
||||||
@ -28,8 +27,8 @@ export default Component.extend({
|
|||||||
this.set('unwrapData', response.auth || response.data);
|
this.set('unwrapData', response.auth || response.data);
|
||||||
this.controlGroup.deleteControlGroupToken(this.model.id);
|
this.controlGroup.deleteControlGroupToken(this.model.id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = yield apiErrorMessage(e);
|
const { message } = yield this.api.parseError(e);
|
||||||
this.error = `Token unwrap failed: ${error}`;
|
this.error = `Token unwrap failed: ${message}`;
|
||||||
}
|
}
|
||||||
}).drop(),
|
}).drop(),
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import Component from '@glimmer/component';
|
|||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
|
||||||
|
|
||||||
import type ApiService from 'vault/services/api';
|
import type ApiService from 'vault/services/api';
|
||||||
import type FlashMessageService from 'vault/services/flash-messages';
|
import type FlashMessageService from 'vault/services/flash-messages';
|
||||||
@ -60,7 +59,8 @@ export default class ToolsHash extends Component {
|
|||||||
this.sum = sum || '';
|
this.sum = sum || '';
|
||||||
this.flashMessages.success('Hash was successful.');
|
this.flashMessages.success('Hash was successful.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.errorMessage = await apiErrorMessage(error);
|
const { message } = await this.api.parseError(error);
|
||||||
|
this.errorMessage = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import Component from '@glimmer/component';
|
|||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
|
||||||
import { addSeconds } from 'date-fns';
|
import { addSeconds } from 'date-fns';
|
||||||
|
|
||||||
import type ApiService from 'vault/services/api';
|
import type ApiService from 'vault/services/api';
|
||||||
@ -56,8 +55,9 @@ export default class ToolsLookup extends Component {
|
|||||||
const data = await this.api.sys.readWrappingProperties(payload);
|
const data = await this.api.sys.readWrappingProperties(payload);
|
||||||
this.lookupData = data;
|
this.lookupData = data;
|
||||||
this.flashMessages.success('Lookup was successful.');
|
this.flashMessages.success('Lookup was successful.');
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
this.errorMessage = await apiErrorMessage(error);
|
const { message } = await this.api.parseError(e);
|
||||||
|
this.errorMessage = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import Component from '@glimmer/component';
|
|||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
|
||||||
|
|
||||||
import type ApiService from 'vault/services/api';
|
import type ApiService from 'vault/services/api';
|
||||||
import type FlashMessageService from 'vault/services/flash-messages';
|
import type FlashMessageService from 'vault/services/flash-messages';
|
||||||
@ -52,7 +51,8 @@ export default class ToolsRandom extends Component {
|
|||||||
this.randomBytes = randomBytes || '';
|
this.randomBytes = randomBytes || '';
|
||||||
this.flashMessages.success('Generated random bytes successfully.');
|
this.flashMessages.success('Generated random bytes successfully.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.errorMessage = await apiErrorMessage(error);
|
const { message } = await this.api.parseError(error);
|
||||||
|
this.errorMessage = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import Component from '@glimmer/component';
|
|||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
|
||||||
|
|
||||||
import type ApiService from 'vault/services/api';
|
import type ApiService from 'vault/services/api';
|
||||||
import type FlashMessageService from 'vault/services/flash-messages';
|
import type FlashMessageService from 'vault/services/flash-messages';
|
||||||
@ -46,7 +45,8 @@ export default class ToolsRewrap extends Component {
|
|||||||
this.rewrappedToken = wrapInfo?.token || '';
|
this.rewrappedToken = wrapInfo?.token || '';
|
||||||
this.flashMessages.success('Rewrap was successful.');
|
this.flashMessages.success('Rewrap was successful.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.errorMessage = await apiErrorMessage(error);
|
const { message } = await this.api.parseError(error);
|
||||||
|
this.errorMessage = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import Component from '@glimmer/component';
|
|||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
|
||||||
|
|
||||||
import type ApiService from 'vault/services/api';
|
import type ApiService from 'vault/services/api';
|
||||||
import type FlashMessageService from 'vault/services/flash-messages';
|
import type FlashMessageService from 'vault/services/flash-messages';
|
||||||
@ -54,7 +53,8 @@ export default class ToolsUnwrap extends Component {
|
|||||||
};
|
};
|
||||||
this.flashMessages.success('Unwrap was successful.');
|
this.flashMessages.success('Unwrap was successful.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.errorMessage = await apiErrorMessage(error);
|
const { message } = await this.api.parseError(error);
|
||||||
|
this.errorMessage = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { service } from '@ember/service';
|
|||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import { stringify } from 'core/helpers/stringify';
|
import { stringify } from 'core/helpers/stringify';
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
|
||||||
|
|
||||||
import type ApiService from 'vault/services/api';
|
import type ApiService from 'vault/services/api';
|
||||||
import type FlashMessageService from 'vault/services/flash-messages';
|
import type FlashMessageService from 'vault/services/flash-messages';
|
||||||
@ -87,7 +86,8 @@ export default class ToolsWrap extends Component {
|
|||||||
this.token = wrapInfo?.token || '';
|
this.token = wrapInfo?.token || '';
|
||||||
this.flashMessages.success('Wrap was successful.');
|
this.flashMessages.success('Wrap was successful.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.errorMessage = await apiErrorMessage(error);
|
const { message } = await this.api.parseError(error);
|
||||||
|
this.errorMessage = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,7 +81,8 @@ async save() {
|
|||||||
this.router.transitionTo('another.route');
|
this.router.transitionTo('another.route');
|
||||||
}
|
}
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
this.error = await apiErrorMessage(error);
|
const { message } = await this.api.parseError(error);
|
||||||
|
this.errorMessage = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@ -3,8 +3,6 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ApiError } from 'vault/api';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
isLocalStorageSupported() {
|
isLocalStorageSupported() {
|
||||||
try {
|
try {
|
||||||
@ -13,10 +11,13 @@ export default {
|
|||||||
window.localStorage.removeItem(key);
|
window.localStorage.removeItem(key);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = e as ApiError;
|
|
||||||
// modify the e object so we can customize the error message.
|
// modify the e object so we can customize the error message.
|
||||||
// e.message is readOnly.
|
// e.message is readOnly.
|
||||||
error.errors = [`This is likely due to your browser's cookie settings.`];
|
Object.defineProperty(e, 'errors', {
|
||||||
|
value: [`This is likely due to your browser's cookie settings.`],
|
||||||
|
writable: false,
|
||||||
|
});
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,15 +15,16 @@ import {
|
|||||||
HTTPQuery,
|
HTTPQuery,
|
||||||
HTTPRequestInit,
|
HTTPRequestInit,
|
||||||
RequestOpts,
|
RequestOpts,
|
||||||
|
ResponseError,
|
||||||
} from '@hashicorp/vault-client-typescript';
|
} from '@hashicorp/vault-client-typescript';
|
||||||
import config from '../config/environment';
|
import config from 'vault/config/environment';
|
||||||
import { waitForPromise } from '@ember/test-waiters';
|
import { waitForPromise } from '@ember/test-waiters';
|
||||||
|
|
||||||
import type AuthService from 'vault/services/auth';
|
import type AuthService from 'vault/services/auth';
|
||||||
import type NamespaceService from 'vault/services/namespace';
|
import type NamespaceService from 'vault/services/namespace';
|
||||||
import type ControlGroupService from 'vault/services/control-group';
|
import type ControlGroupService from 'vault/services/control-group';
|
||||||
import type FlashMessageService from 'vault/services/flash-messages';
|
import type FlashMessageService from 'vault/services/flash-messages';
|
||||||
import type { ApiError, HeaderMap, XVaultHeaders } from 'vault/api';
|
import type { HeaderMap, XVaultHeaders } from 'vault/api';
|
||||||
|
|
||||||
export default class ApiService extends Service {
|
export default class ApiService extends Service {
|
||||||
@service('auth') declare readonly authService: AuthService;
|
@service('auth') declare readonly authService: AuthService;
|
||||||
@ -103,27 +104,6 @@ export default class ApiService extends Service {
|
|||||||
this.controlGroup.deleteControlGroupToken(controlGroupToken.accessor);
|
this.controlGroup.deleteControlGroupToken(controlGroupToken.accessor);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
formatErrorResponse = async (context: ResponseContext) => {
|
|
||||||
const response = context.response.clone();
|
|
||||||
const { headers, status, statusText } = response;
|
|
||||||
|
|
||||||
// backwards compatibility with Ember Data
|
|
||||||
if (status >= 400) {
|
|
||||||
const error: ApiError = (await response?.json()) || {};
|
|
||||||
error.httpStatus = response?.status;
|
|
||||||
error.path = context.url;
|
|
||||||
// typically the Vault API error response looks like { errors: ['some error message'] }
|
|
||||||
// but sometimes (eg RespondWithStatusCode) it's { data: { error: 'some error message' } }
|
|
||||||
if (error?.data?.error && !error.errors) {
|
|
||||||
// normalize the errors from RespondWithStatusCode
|
|
||||||
error.errors = [error.data.error];
|
|
||||||
}
|
|
||||||
return new Response(JSON.stringify(error), { headers, status, statusText });
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
// --- End Middleware ---
|
// --- End Middleware ---
|
||||||
|
|
||||||
configuration = new Configuration({
|
configuration = new Configuration({
|
||||||
@ -134,7 +114,6 @@ export default class ApiService extends Service {
|
|||||||
{ pre: this.setHeaders },
|
{ pre: this.setHeaders },
|
||||||
{ post: this.showWarnings },
|
{ post: this.showWarnings },
|
||||||
{ post: this.deleteControlGroupToken },
|
{ post: this.deleteControlGroupToken },
|
||||||
{ post: this.formatErrorResponse },
|
|
||||||
],
|
],
|
||||||
fetchApi: (...args: [Request]) => {
|
fetchApi: (...args: [Request]) => {
|
||||||
return waitForPromise(window.fetch(...args));
|
return waitForPromise(window.fetch(...args));
|
||||||
@ -172,4 +151,34 @@ export default class ApiService extends Service {
|
|||||||
const { context } = requestContext;
|
const { context } = requestContext;
|
||||||
context.query = { ...context.query, ...params };
|
context.query = { ...context.query, ...params };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// accepts an error response and returns { status, message, response, path }
|
||||||
|
// message is built as error.errors joined with a comma, error.message or a fallback message
|
||||||
|
// path is the url of the request, minus the origin -> /v1/sys/wrapping/unwrap
|
||||||
|
async parseError(e: unknown, fallbackMessage = 'An error occurred, please try again') {
|
||||||
|
if (e instanceof ResponseError) {
|
||||||
|
const { status, url } = e.response;
|
||||||
|
const error = await e.response.json();
|
||||||
|
// typically the Vault API error response looks like { errors: ['some error message'] }
|
||||||
|
// but sometimes (eg RespondWithStatusCode) it's { data: { error: 'some error message' } }
|
||||||
|
const errors = error.data?.error && !error.errors ? [error.data.error] : error.errors;
|
||||||
|
const message = errors && typeof errors[0] === 'string' ? errors.join(', ') : error.message;
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: message || fallbackMessage,
|
||||||
|
status,
|
||||||
|
path: url.replace(document.location.origin, ''),
|
||||||
|
response: error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// log out generic error for ease of debugging in dev env
|
||||||
|
if (config.environment === 'development') {
|
||||||
|
console.log('API Error:', e); // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: (e as Error)?.message || fallbackMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this util was derived from error-message and updated to handle the error context returned from the api service
|
|
||||||
* once Ember Data is fully removed, the error-message util will also be removed
|
|
||||||
* for all requests made with the api service, use this util to display error messages from server
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { ErrorContext, ApiError } from 'vault/api';
|
|
||||||
import ENV from 'vault/config/environment';
|
|
||||||
|
|
||||||
// accepts an error and returns error.errors joined with a comma, error.message or a fallback message
|
|
||||||
export default async function (error: unknown, fallbackMessage = 'An error occurred, please try again') {
|
|
||||||
const messageOrFallback = (message?: string) => message || fallbackMessage;
|
|
||||||
|
|
||||||
// log out the error for ease of debugging in dev env
|
|
||||||
if (ENV.environment === 'development') {
|
|
||||||
console.error('API Error:', error); // eslint-disable-line no-console
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((error as ErrorContext).response instanceof Response) {
|
|
||||||
const apiError: ApiError = await (error as ErrorContext).response?.json();
|
|
||||||
|
|
||||||
if (apiError.errors && typeof apiError.errors[0] === 'string') {
|
|
||||||
return apiError.errors.join(', ');
|
|
||||||
}
|
|
||||||
return messageOrFallback(apiError.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageOrFallback((error as Error)?.message);
|
|
||||||
}
|
|
||||||
@ -6,7 +6,6 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import { task, timeout } from 'ember-concurrency';
|
import { task, timeout } from 'ember-concurrency';
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
|
||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
@ -90,7 +89,8 @@ export default class MessagesList extends Component {
|
|||||||
this.router.transitionTo('vault.cluster.config-ui.messages.message.details', id);
|
this.router.transitionTo('vault.cluster.config-ui.messages.message.details', id);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.errorBanner = yield apiErrorMessage(error);
|
const { message } = yield this.api.parseError(error);
|
||||||
|
this.errorBanner = message;
|
||||||
this.invalidFormAlert = 'There was an error submitting this form.';
|
this.invalidFormAlert = 'There was an error submitting this form.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @module Page::MessageDetails
|
* @module Page::MessageDetails
|
||||||
@ -37,8 +36,8 @@ export default class MessageDetails extends Component {
|
|||||||
this.customMessages.fetchMessages();
|
this.customMessages.fetchMessages();
|
||||||
this.flashMessages.success(`Successfully deleted ${message.title}.`);
|
this.flashMessages.success(`Successfully deleted ${message.title}.`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errorMessage = await apiErrorMessage(e);
|
const { message } = await this.api.parseError(e);
|
||||||
this.flashMessages.danger(errorMessage);
|
this.flashMessages.danger(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { task, timeout } from 'ember-concurrency';
|
|||||||
import { dateFormat } from 'core/helpers/date-format';
|
import { dateFormat } from 'core/helpers/date-format';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
|
||||||
import { isAfter } from 'date-fns';
|
import { isAfter } from 'date-fns';
|
||||||
import timestamp from 'core/utils/timestamp';
|
import timestamp from 'core/utils/timestamp';
|
||||||
|
|
||||||
@ -102,8 +101,8 @@ export default class MessagesList extends Component {
|
|||||||
this.customMessages.fetchMessages();
|
this.customMessages.fetchMessages();
|
||||||
this.flashMessages.success(`Successfully deleted ${message.title}.`);
|
this.flashMessages.success(`Successfully deleted ${message.title}.`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errorMessage = yield apiErrorMessage(e);
|
const { message } = yield this.api.parseError(e);
|
||||||
this.flashMessages.danger(errorMessage);
|
this.flashMessages.danger(message);
|
||||||
} finally {
|
} finally {
|
||||||
this.messageToDelete = null;
|
this.messageToDelete = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupTest } from 'ember-qunit';
|
import { setupTest } from 'ember-qunit';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
import config from 'vault/config/environment';
|
||||||
|
import { ResponseError } from '@hashicorp/vault-client-typescript';
|
||||||
|
|
||||||
module('Unit | Service | api', function (hooks) {
|
module('Unit | Service | api', function (hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
@ -136,22 +138,6 @@ module('Unit | Service | api', function (hooks) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it should format error response', async function (assert) {
|
|
||||||
const e = { data: { error: 'Something went wrong' } };
|
|
||||||
const response = new Response(JSON.stringify(e), { status: 400 });
|
|
||||||
|
|
||||||
const errorResponse = await this.apiService.formatErrorResponse({ response, url: this.url });
|
|
||||||
const error = await errorResponse.json();
|
|
||||||
const expectedError = {
|
|
||||||
...e,
|
|
||||||
httpStatus: 400,
|
|
||||||
path: this.url,
|
|
||||||
errors: ['Something went wrong'],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert.deepEqual(error, expectedError, 'Error is reformated and returned');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it should build headers', async function (assert) {
|
test('it should build headers', async function (assert) {
|
||||||
const headerMap = {
|
const headerMap = {
|
||||||
token: 'foobar',
|
token: 'foobar',
|
||||||
@ -183,4 +169,63 @@ module('Unit | Service | api', function (hooks) {
|
|||||||
'All supported headers are set'
|
'All supported headers are set'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module('Error parsing', function (hooks) {
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
this.response = {
|
||||||
|
errors: ['first error', 'second error'],
|
||||||
|
message: 'there were some errors',
|
||||||
|
};
|
||||||
|
this.getErrorResponse = () =>
|
||||||
|
new ResponseError({
|
||||||
|
status: 404,
|
||||||
|
url: `${document.location.origin}/v1/test/error/parsing`,
|
||||||
|
json: () => Promise.resolve(this.response),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should correctly parse message from error', async function (assert) {
|
||||||
|
let e = await this.apiService.parseError(this.getErrorResponse());
|
||||||
|
assert.strictEqual(e.message, 'first error, second error', 'Builds message from errors');
|
||||||
|
|
||||||
|
this.response.errors = [];
|
||||||
|
e = await this.apiService.parseError(this.getErrorResponse());
|
||||||
|
assert.strictEqual(e.message, 'there were some errors', 'Returns message when errors are empty');
|
||||||
|
|
||||||
|
const error = new Error('some js type error');
|
||||||
|
e = await this.apiService.parseError(error);
|
||||||
|
assert.strictEqual(e.message, error.message, 'Returns message from generic Error');
|
||||||
|
|
||||||
|
e = await this.apiService.parseError('some random error');
|
||||||
|
assert.strictEqual(e.message, 'An error occurred, please try again', 'Returns default fallback');
|
||||||
|
|
||||||
|
const fallback = 'Everything is broken, sorry';
|
||||||
|
e = await this.apiService.parseError('some random error', fallback);
|
||||||
|
assert.strictEqual(e.message, fallback, 'Returns custom fallback');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should return status', async function (assert) {
|
||||||
|
const { status } = await this.apiService.parseError(this.getErrorResponse());
|
||||||
|
assert.strictEqual(status, 404, 'Returns the status code from the response');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should return path', async function (assert) {
|
||||||
|
const { path } = await this.apiService.parseError(this.getErrorResponse());
|
||||||
|
assert.strictEqual(path, '/v1/test/error/parsing', 'Returns the path from the request url');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should return error response', async function (assert) {
|
||||||
|
const { response } = await this.apiService.parseError(this.getErrorResponse());
|
||||||
|
assert.deepEqual(response, this.response, 'Returns the original error response');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should log out error in development environment', async function (assert) {
|
||||||
|
const consoleStub = sinon.stub(console, 'log');
|
||||||
|
sinon.stub(config, 'environment').value('development');
|
||||||
|
const error = new Error('some js type error');
|
||||||
|
await this.apiService.parseError(error);
|
||||||
|
assert.true(consoleStub.calledWith('API Error:', error));
|
||||||
|
sinon.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,62 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { module, test } from 'qunit';
|
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
|
||||||
import ENV from 'vault/config/environment';
|
|
||||||
import sinon from 'sinon';
|
|
||||||
|
|
||||||
module('Unit | Util | api-error-message', function (hooks) {
|
|
||||||
hooks.beforeEach(function () {
|
|
||||||
this.apiError = {
|
|
||||||
errors: ['first error', 'second error'],
|
|
||||||
message: 'there were some errors',
|
|
||||||
};
|
|
||||||
this.getErrorContext = () => ({ response: new Response(JSON.stringify(this.apiError)) });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it should return errors from ErrorContext', async function (assert) {
|
|
||||||
const message = await apiErrorMessage(this.getErrorContext());
|
|
||||||
assert.strictEqual(message, 'first error, second error');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it should return message from ErrorContext when errors are empty', async function (assert) {
|
|
||||||
this.apiError.errors = [];
|
|
||||||
const message = await apiErrorMessage(this.getErrorContext());
|
|
||||||
assert.strictEqual(message, 'there were some errors');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it should return fallback message for ErrorContext without errors or message', async function (assert) {
|
|
||||||
this.apiError = {};
|
|
||||||
const message = await apiErrorMessage(this.getErrorContext());
|
|
||||||
assert.strictEqual(message, 'An error occurred, please try again');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it should return message from Error', async function (assert) {
|
|
||||||
const error = new Error('some js type error');
|
|
||||||
const message = await apiErrorMessage(error);
|
|
||||||
assert.strictEqual(message, error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it should return default fallback', async function (assert) {
|
|
||||||
const message = await apiErrorMessage('some random error');
|
|
||||||
assert.strictEqual(message, 'An error occurred, please try again');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it should return custom fallback message', async function (assert) {
|
|
||||||
const fallback = 'Everything is broken, sorry';
|
|
||||||
const message = await apiErrorMessage('some random error', fallback);
|
|
||||||
assert.strictEqual(message, fallback);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it should log out error in development environment', async function (assert) {
|
|
||||||
const consoleStub = sinon.stub(console, 'error');
|
|
||||||
sinon.stub(ENV, 'environment').value('development');
|
|
||||||
const error = new Error('some js type error');
|
|
||||||
await apiErrorMessage(error);
|
|
||||||
assert.true(consoleStub.calledWith('API Error:', error));
|
|
||||||
sinon.restore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
15
ui/types/vault/api.d.ts
vendored
15
ui/types/vault/api.d.ts
vendored
@ -3,21 +3,6 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ErrorContext } from '@hashicorp/vault-client-typescript';
|
|
||||||
|
|
||||||
// re-exporting for convenience since it is associated to ApiError
|
|
||||||
export { ErrorContext };
|
|
||||||
export interface ApiError {
|
|
||||||
httpStatus: number;
|
|
||||||
path: string;
|
|
||||||
message: string;
|
|
||||||
errors: Array<string | { [key: string]: unknown; title?: string; message?: string }>;
|
|
||||||
data?: {
|
|
||||||
[key: string]: unknown;
|
|
||||||
error?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WrapInfo {
|
export interface WrapInfo {
|
||||||
accessor: string;
|
accessor: string;
|
||||||
creation_path: string;
|
creation_path: string;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user