1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-14 00:19:16 +01:00

Chore: remove "dataPath" from data OpenAPI data errors. (#5272)

The `dataPath` was present (but not in the type) in previous versions of
the
error library that we use. But with the recent major upgrade, it's
been removed and the `instancePath` property has finally come into use.

This PR removes all the handling for the previous property and
replaces it with `instancePath`. Because the `dataPath` used full
stops and the `instancePath` uses slashes, we need to change a little
bit of the handling too.
This commit is contained in:
Thomas Heartman 2023-11-07 09:26:14 +01:00 committed by GitHub
parent 92e2b1890c
commit b3054c9277
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 29 additions and 47 deletions

View File

@ -38,7 +38,7 @@ class BadDataError extends UnleashError {
export default BadDataError; export default BadDataError;
const constructPath = (pathToParent: string, propertyName: string) => const constructPath = (pathToParent: string, propertyName: string) =>
[pathToParent, propertyName].filter(Boolean).join('.'); [pathToParent, propertyName].filter(Boolean).join('/');
const missingRequiredPropertyMessage = ( const missingRequiredPropertyMessage = (
pathToParentObject: string, pathToParentObject: string,
@ -74,10 +74,10 @@ const genericErrorMessage = (
propertyName: string, propertyName: string,
errorMessage: string = 'is invalid', errorMessage: string = 'is invalid',
) => { ) => {
const input = getProp(requestBody, propertyName); const input = getProp(requestBody, propertyName.split('/'));
const youSent = JSON.stringify(input); const youSent = JSON.stringify(input);
const description = `The \`.${propertyName}\` property ${errorMessage}. You sent ${youSent}.`; const description = `The \`${propertyName}\` property ${errorMessage}. You sent ${youSent}.`;
return { return {
description, description,
message: description, message: description,
@ -122,16 +122,11 @@ const enumMessage = (
}; };
}; };
// Sometimes, the error object contains a dataPath, even if it's not
type ActualErrorObject = ErrorObject & { dataPath?: string };
export const fromOpenApiValidationError = export const fromOpenApiValidationError =
(requestBody: object) => (requestBody: object) =>
(validationError: ActualErrorObject): ValidationErrorDescription => { (validationError: ErrorObject): ValidationErrorDescription => {
const { instancePath, params, message, dataPath } = validationError; const { instancePath, params, message } = validationError;
const propertyName = const propertyName = instancePath.substring('/body/'.length);
dataPath?.substring('.body.'.length) ??
instancePath.substring('/body/'.length);
switch (validationError.keyword) { switch (validationError.keyword) {
case 'required': case 'required':

View File

@ -50,8 +50,7 @@ describe('OpenAPI error conversion', () => {
it('Gives useful error messages for missing properties', () => { it('Gives useful error messages for missing properties', () => {
const error = { const error = {
keyword: 'required', keyword: 'required',
instancePath: '', instancePath: '/body',
dataPath: '.body',
schemaPath: '#/components/schemas/addonCreateUpdateSchema/required', schemaPath: '#/components/schemas/addonCreateUpdateSchema/required',
params: { params: {
missingProperty: 'enabled', missingProperty: 'enabled',
@ -75,8 +74,7 @@ describe('OpenAPI error conversion', () => {
it('Gives useful error messages for type errors', () => { it('Gives useful error messages for type errors', () => {
const error = { const error = {
keyword: 'type', keyword: 'type',
instancePath: '', instancePath: '/body/parameters',
dataPath: '.body.parameters',
schemaPath: schemaPath:
'#/components/schemas/addonCreateUpdateSchema/properties/parameters/type', '#/components/schemas/addonCreateUpdateSchema/properties/parameters/type',
params: { params: {
@ -101,13 +99,12 @@ describe('OpenAPI error conversion', () => {
expect(result.description).toContain(JSON.stringify(parameterValue)); expect(result.description).toContain(JSON.stringify(parameterValue));
}); });
it.each(['.body', '.body.subObject'])( it.each(['/body', '/body/subObject'])(
'Gives useful error messages for oneOf errors in %s', 'Gives useful error messages for oneOf errors in %s',
(dataPath) => { (instancePath) => {
const error = { const error = {
keyword: 'oneOf', keyword: 'oneOf',
instancePath: '', instancePath,
dataPath,
schemaPath: '#/components/schemas/createApiTokenSchema/oneOf', schemaPath: '#/components/schemas/createApiTokenSchema/oneOf',
params: { params: {
passingSchemas: null, passingSchemas: null,
@ -125,7 +122,7 @@ describe('OpenAPI error conversion', () => {
description: description:
// it provides the message // it provides the message
expect.stringContaining(error.message), expect.stringContaining(error.message),
path: dataPath.substring('.body.'.length), path: instancePath.substring('/body/'.length),
}); });
// it tells the user what happened // it tells the user what happened
@ -134,18 +131,17 @@ describe('OpenAPI error conversion', () => {
); );
// it tells the user what part of the request body this pertains to // it tells the user what part of the request body this pertains to
expect(result.description).toContain( expect(result.description).toContain(
dataPath === '.body' instancePath === '/body'
? 'root object' ? 'root object'
: `"${dataPath.substring('.body.'.length)}" property`, : `"${instancePath.substring('/body/'.length)}" property`,
); );
}, },
); );
it('Gives useful pattern error messages', () => { it('Gives useful pattern error messages', () => {
const error = { const error = {
instancePath: '',
keyword: 'pattern', keyword: 'pattern',
dataPath: '.body.description', instancePath: '/body/description',
schemaPath: schemaPath:
'#/components/schemas/addonCreateUpdateSchema/properties/description/pattern', '#/components/schemas/addonCreateUpdateSchema/properties/description/pattern',
params: { params: {
@ -199,9 +195,8 @@ describe('OpenAPI error conversion', () => {
it('Gives useful min/maxlength error messages', () => { it('Gives useful min/maxlength error messages', () => {
const error = { const error = {
instancePath: '',
keyword: 'maxLength', keyword: 'maxLength',
dataPath: '.body.description', instancePath: '/body/description',
schemaPath: schemaPath:
'#/components/schemas/addonCreateUpdateSchema/properties/description/maxLength', '#/components/schemas/addonCreateUpdateSchema/properties/description/maxLength',
params: { params: {
@ -230,8 +225,7 @@ describe('OpenAPI error conversion', () => {
it('Handles numerical min/max errors', () => { it('Handles numerical min/max errors', () => {
const error = { const error = {
keyword: 'maximum', keyword: 'maximum',
instancePath: '', instancePath: '/body/newprop',
dataPath: '.body.newprop',
schemaPath: schemaPath:
'#/components/schemas/addonCreateUpdateSchema/properties/newprop/maximum', '#/components/schemas/addonCreateUpdateSchema/properties/newprop/maximum',
params: { params: {
@ -265,9 +259,7 @@ describe('OpenAPI error conversion', () => {
const errors: [ErrorObject, ...ErrorObject[]] = [ const errors: [ErrorObject, ...ErrorObject[]] = [
{ {
keyword: 'maximum', keyword: 'maximum',
instancePath: '', instancePath: '/body/newprop',
// @ts-expect-error
dataPath: '.body.newprop',
schemaPath: schemaPath:
'#/components/schemas/addonCreateUpdateSchema/properties/newprop/maximum', '#/components/schemas/addonCreateUpdateSchema/properties/newprop/maximum',
params: { params: {
@ -279,8 +271,7 @@ describe('OpenAPI error conversion', () => {
}, },
{ {
keyword: 'required', keyword: 'required',
instancePath: '', instancePath: '/body',
dataPath: '.body',
schemaPath: schemaPath:
'#/components/schemas/addonCreateUpdateSchema/required', '#/components/schemas/addonCreateUpdateSchema/required',
params: { params: {
@ -312,8 +303,7 @@ describe('OpenAPI error conversion', () => {
it('gives useful messages for base-level properties', () => { it('gives useful messages for base-level properties', () => {
const openApiError = { const openApiError = {
keyword: 'additionalProperties', keyword: 'additionalProperties',
instancePath: '', instancePath: '/body',
dataPath: '.body',
schemaPath: schemaPath:
'#/components/schemas/addonCreateUpdateSchema/additionalProperties', '#/components/schemas/addonCreateUpdateSchema/additionalProperties',
params: { additionalProperty: 'bogus' }, params: { additionalProperty: 'bogus' },
@ -343,8 +333,7 @@ describe('OpenAPI error conversion', () => {
}; };
const openApiError = { const openApiError = {
keyword: 'additionalProperties', keyword: 'additionalProperties',
instancePath: '', instancePath: '/body/nestedObject/nested2',
dataPath: '.body.nestedObject.nested2',
schemaPath: schemaPath:
'#/components/schemas/addonCreateUpdateSchema/properties/nestedObject/properties/nested2/additionalProperties', '#/components/schemas/addonCreateUpdateSchema/properties/nestedObject/properties/nested2/additionalProperties',
params: { additionalProperty: 'extraPropertyName' }, params: { additionalProperty: 'extraPropertyName' },
@ -354,8 +343,8 @@ describe('OpenAPI error conversion', () => {
const error = fromOpenApiValidationError(request2)(openApiError); const error = fromOpenApiValidationError(request2)(openApiError);
expect(error).toMatchObject({ expect(error).toMatchObject({
description: expect.stringContaining('nestedObject.nested2'), description: expect.stringContaining('nestedObject/nested2'),
path: 'nestedObject.nested2.extraPropertyName', path: 'nestedObject/nested2/extraPropertyName',
}); });
expect(error.description).toContain( expect(error.description).toContain(
@ -368,12 +357,11 @@ describe('OpenAPI error conversion', () => {
it('Handles deeply nested properties gracefully', () => { it('Handles deeply nested properties gracefully', () => {
const error = { const error = {
keyword: 'type', keyword: 'type',
dataPath: '.body.nestedObject.a.b', instancePath: '/body/nestedObject/a/b',
schemaPath: schemaPath:
'#/components/schemas/addonCreateUpdateSchema/properties/nestedObject/properties/a/properties/b/type', '#/components/schemas/addonCreateUpdateSchema/properties/nestedObject/properties/a/properties/b/type',
params: { type: 'string' }, params: { type: 'string' },
message: 'should be string', message: 'should be string',
instancePath: '',
}; };
const result = fromOpenApiValidationError({ const result = fromOpenApiValidationError({
@ -381,8 +369,8 @@ describe('OpenAPI error conversion', () => {
})(error); })(error);
expect(result).toMatchObject({ expect(result).toMatchObject({
description: expect.stringMatching(/\bnestedObject.a.b\b/), description: expect.stringMatching(/\bnestedObject\/a\/b\b/),
path: 'nestedObject.a.b', path: 'nestedObject/a/b',
}); });
expect(result.description).toContain('[]'); expect(result.description).toContain('[]');
@ -391,11 +379,10 @@ describe('OpenAPI error conversion', () => {
it('Handles deeply nested properties on referenced schemas', () => { it('Handles deeply nested properties on referenced schemas', () => {
const error = { const error = {
keyword: 'type', keyword: 'type',
dataPath: '.body.nestedObject.a.b', instancePath: '/body/nestedObject/a/b',
schemaPath: '#/components/schemas/parametersSchema/type', schemaPath: '#/components/schemas/parametersSchema/type',
params: { type: 'object' }, params: { type: 'object' },
message: 'should be object', message: 'should be object',
instancePath: '',
}; };
const illegalValue = 'illegal string'; const illegalValue = 'illegal string';
@ -405,10 +392,10 @@ describe('OpenAPI error conversion', () => {
expect(result).toMatchObject({ expect(result).toMatchObject({
description: expect.stringContaining(illegalValue), description: expect.stringContaining(illegalValue),
path: 'nestedObject.a.b', path: 'nestedObject/a/b',
}); });
expect(result.description).toMatch(/\bnestedObject.a.b\b/); expect(result.description).toMatch(/\bnestedObject\/a\/b\b/);
}); });
}); });