From 2ad2a18d54a1c8be3d02b010edcf10e27acb5759 Mon Sep 17 00:00:00 2001 From: Austin DeNoble Date: Fri, 3 Nov 2023 18:50:46 -0400 Subject: [PATCH] update validator to handle typebox schemas where we are using literals --- .../__tests__/createIndex.validation.test.ts | 4 +- src/validator.ts | 52 ++++++++++++++++--- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/control/__tests__/createIndex.validation.test.ts b/src/control/__tests__/createIndex.validation.test.ts index 7d397fdd..60950946 100644 --- a/src/control/__tests__/createIndex.validation.test.ts +++ b/src/control/__tests__/createIndex.validation.test.ts @@ -180,7 +180,7 @@ describe('createIndex argument validations', () => { expect(toThrow).rejects.toThrowError(PineconeArgumentError); expect(toThrow).rejects.toThrowError( - 'The argument to createIndex accepts multiple types. Either 1) 2) 3)' + "The argument to createIndex had type errors: property 'cloud' is a constant which must be equal to one of: 'gcp', 'aws', 'azure'." ); }); @@ -196,7 +196,7 @@ describe('createIndex argument validations', () => { expect(toThrow).rejects.toThrowError(PineconeArgumentError); expect(toThrow).rejects.toThrowError( - 'The argument to createIndex accepts multiple types. Either 1) 2) 3)' + "The argument to createIndex had type errors: property 'cloud' is a constant which must be equal to one of: 'gcp', 'aws', 'azure'." ); }); diff --git a/src/validator.ts b/src/validator.ts index d2251575..25202421 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -97,12 +97,46 @@ const typeErrors = ( messageParts: Array ) => { const typeErrorsList: Array = []; + const anyOfConstPropErrors: Array = errors.filter( + (error) => + error.schemaPath.indexOf('anyOf') > -1 && + error.keyword === 'const' && + error.instancePath.length > 0 + ); let errorCount = 0; + // handle possible literal types first + const propErrorGroups: { [key: string]: Array } = {}; + if (anyOfConstPropErrors.length > 0) { + for (const error of anyOfConstPropErrors) { + const constValue = error.instancePath.slice(1); + + if (propErrorGroups[constValue]) { + propErrorGroups[constValue].push(error); + } else { + propErrorGroups[constValue] = [error]; + } + } + const properties = Object.keys(propErrorGroups); + + properties.forEach((property) => { + const constValueErrors = propErrorGroups[property]; + + typeErrorsList.push( + `property '${property}' is a constant which must be equal to one of: ` + + Object.values(constValueErrors) + .map((group) => `'${group.params.allowedValue}'`) + .join(', ') + ); + }); + } + + // typebox also emits type errors for each value of a literal so we want to exclude these + const anyOfKeys = Object.keys(propErrorGroups); for (let i = 0; i < errors.length; i++) { const e = errors[i]; - if (e.keyword === 'type') { + if (e.keyword === 'type' && !anyOfKeys.includes(e.instancePath.slice(1))) { errorCount += 1; if (errorCount <= maxErrors) { formatIndividualError(e, typeErrorsList); @@ -181,13 +215,19 @@ const validationErrors = ( }; export const errorFormatter = (subject: string, errors: Array) => { - const anyOfErrors = errors.filter( + const messageParts: Array = []; + + const anyOfArgumentErrors = errors.filter( (error) => - error.schemaPath.indexOf('anyOf') > -1 && error.keyword !== 'anyOf' + error.schemaPath.indexOf('anyOf') > -1 && + error.keyword !== 'anyOf' && + error.keyword !== 'const' && + error.keyword !== 'type' ); - if (anyOfErrors.length > 0) { + + if (anyOfArgumentErrors.length > 0) { const groups = {}; - for (const error of anyOfErrors) { + for (const error of anyOfArgumentErrors) { const schemaPathMatch = schemaPathGroupNumberRegex.exec(error.schemaPath); const groupNumber = schemaPathMatch ? schemaPathMatch[1] : 'unknown'; // Remove the anyOf portion of the schema path to avoid infinite loop @@ -214,8 +254,6 @@ export const errorFormatter = (subject: string, errors: Array) => { ); } - const messageParts: Array = []; - neverErrors(subject, errors, messageParts); missingPropertiesErrors(subject, errors, messageParts); typeErrors(subject, errors, messageParts);