diff --git a/src/compileValueSchema.ts b/src/compileValueSchema.ts index f5193c3..3c24c66 100644 --- a/src/compileValueSchema.ts +++ b/src/compileValueSchema.ts @@ -105,8 +105,10 @@ function compileOneOfSchema(compiler: Compiler, schema: OpenAPIOneOfSchema) { // Declare the variable to use as a result, then iterate over each schema const resultIdentifier = builders.identifier('result'); + const errorIdentifier = builders.identifier('error'); nodes.push( builders.variableDeclaration('let', [builders.variableDeclarator(resultIdentifier)]), + builders.variableDeclaration('let', [builders.variableDeclarator(errorIdentifier)]), ); schema.oneOf.forEach((subSchema, index) => { @@ -125,14 +127,50 @@ function compileOneOfSchema(compiler: Compiler, schema: OpenAPIOneOfSchema) { nodes.push( builders.ifStatement( - builders.unaryExpression( - '!', - builders.binaryExpression( - 'instanceof', - altIdentifier, - ValidationErrorIdentifier, - ), + builders.binaryExpression( + 'instanceof', + altIdentifier, + ValidationErrorIdentifier, ), + builders.blockStatement([ + // Repalce the error if the new error is more specific (longer path) + builders.ifStatement( + builders.logicalExpression( + '||', + builders.binaryExpression( + '===', + errorIdentifier, + builders.identifier('undefined'), + ), + builders.binaryExpression( + '<', + builders.memberExpression( + builders.memberExpression( + errorIdentifier, + builders.identifier('path'), + ), + builders.identifier('length'), + ), + builders.memberExpression( + builders.memberExpression( + altIdentifier, + builders.identifier('path'), + ), + builders.identifier('length'), + ), + ), + ), + builders.blockStatement([ + builders.expressionStatement( + builders.assignmentExpression( + '=', + errorIdentifier, + altIdentifier, + ), + ), + ]), + ), + ]), builders.blockStatement([ ...(index > 0 ? [ @@ -165,7 +203,33 @@ function compileOneOfSchema(compiler: Compiler, schema: OpenAPIOneOfSchema) { resultIdentifier, builders.identifier('undefined'), ), - builders.blockStatement([builders.returnStatement(error('expected to match one'))]), + builders.blockStatement([ + // If the path is longer than one level deep, then we return the error from the evaluation + // other we say that we expected to only match one of the schemas + builders.ifStatement( + builders.logicalExpression( + '&&', + errorIdentifier, + builders.binaryExpression( + '>', + builders.memberExpression( + builders.memberExpression( + errorIdentifier, + builders.identifier('path'), + ), + builders.identifier('length'), + ), + builders.binaryExpression( + '+', + builders.memberExpression(path, builders.identifier('length')), + builders.literal(1), + ), + ), + ), + builders.blockStatement([builders.returnStatement(errorIdentifier)]), + builders.returnStatement(error('expected to match one')), + ), + ]), ), ); diff --git a/src/tests/__snapshots__/compileValueSchema.test.ts.snap b/src/tests/__snapshots__/compileValueSchema.test.ts.snap index 5d72bdb..22a9f87 100644 --- a/src/tests/__snapshots__/compileValueSchema.test.ts.snap +++ b/src/tests/__snapshots__/compileValueSchema.test.ts.snap @@ -1074,19 +1074,31 @@ function obj2(path, value, context) { } function obj0(path, value, context) { let result; + let error; const alt0 = obj1(path, value, context); - if (!(alt0 instanceof ValidationError)) { + if (alt0 instanceof ValidationError) { + if (error === undefined || error.path.length < alt0.path.length) { + error = alt0; + } + } else { result = alt0; } const alt1 = obj2(path, value, context); - if (!(alt1 instanceof ValidationError)) { + if (alt1 instanceof ValidationError) { + if (error === undefined || error.path.length < alt1.path.length) { + error = alt1; + } + } else { if (result !== undefined) { return new ValidationError(path, 'expected to only match one of the schemas'); } result = alt1; } if (result === undefined) { - return new ValidationError(path, 'expected to match one'); + if (error && error.path.length > path.length + 1) { + return error; + } else + return new ValidationError(path, 'expected to match one'); } return result; }" diff --git a/tests/gitbook.test.ts b/tests/gitbook.test.ts index 76b6f4c..fa8ee52 100644 --- a/tests/gitbook.test.ts +++ b/tests/gitbook.test.ts @@ -311,4 +311,29 @@ describe('componentSchemas', () => { 'expected "build" to be defined', ); }); + + test('should return a best-effort error for a oneOf', () => { + const validate = componentSchemas['DocumentBlocksEssentials']; + expect(validate).toBeInstanceOf(Function); + + const error = validate([], { + object: 'block', + type: 'paragraph', + nodes: [ + // Invalid + { + object: 'block', + type: 'paragraph', + nodes: [ + { + object: 'text', + text: 'Hello, world!', + }, + ], + }, + ], + }); + + expect(error instanceof ValidationError ? error.path : null).toEqual(['nodes', 0]); + }); });