From 8d8bdc4168dfbb43614de2c4e89d11538647598c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Gorej?= Date: Tue, 29 Aug 2023 09:46:47 +0200 Subject: [PATCH] fix(parser-adapter-yaml-1-2): fix bug related to explicit tag resolution (#3090) Structured errors are now thrown with additional metadata. Refs #3039 --- packages/apidom-ast/src/index.ts | 3 ++ .../src/yaml/errors/YamlSchemaError.ts | 5 +++ .../src/yaml/errors/YamlTagError.ts | 38 ++++++++++++++++ .../src/yaml/schemas/failsafe/index.ts | 18 ++++++-- .../indirect/visitors/CstVisitor.ts | 2 +- .../test/adapter-node/index.ts | 45 +++++++++++++++++++ 6 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 packages/apidom-ast/src/yaml/errors/YamlSchemaError.ts create mode 100644 packages/apidom-ast/src/yaml/errors/YamlTagError.ts diff --git a/packages/apidom-ast/src/index.ts b/packages/apidom-ast/src/index.ts index ad765599b..c1c6249fa 100644 --- a/packages/apidom-ast/src/index.ts +++ b/packages/apidom-ast/src/index.ts @@ -55,6 +55,9 @@ export { isStream as isYamlStream, isTag as isYamlTag, } from './yaml/nodes/predicates'; +export { default as YamlSchemaError } from './yaml/errors/YamlSchemaError'; +export { default as YamlTagError } from './yaml/errors/YamlTagError'; +export type { YamlTagErrorOptions } from './yaml/errors/YamlTagError'; // generic AST related exports export { default as Literal } from './Literal'; export { Point, default as Position } from './Position'; diff --git a/packages/apidom-ast/src/yaml/errors/YamlSchemaError.ts b/packages/apidom-ast/src/yaml/errors/YamlSchemaError.ts new file mode 100644 index 000000000..e1530b8c1 --- /dev/null +++ b/packages/apidom-ast/src/yaml/errors/YamlSchemaError.ts @@ -0,0 +1,5 @@ +import { ApiDOMStructuredError } from '@swagger-api/apidom-error'; + +class YamlSchemaError extends ApiDOMStructuredError {} + +export default YamlSchemaError; diff --git a/packages/apidom-ast/src/yaml/errors/YamlTagError.ts b/packages/apidom-ast/src/yaml/errors/YamlTagError.ts new file mode 100644 index 000000000..d9b3d25d4 --- /dev/null +++ b/packages/apidom-ast/src/yaml/errors/YamlTagError.ts @@ -0,0 +1,38 @@ +import { ApiDOMErrorOptions } from '@swagger-api/apidom-error'; + +import YamlSchemaError from './YamlSchemaError'; +import Position from '../../Position'; + +export interface YamlTagErrorOptions extends ApiDOMErrorOptions { + readonly specificTagName: string; + readonly explicitTagName: string; + readonly tagKind: string; + readonly tagPosition?: Position; + readonly nodeCanonicalContent?: string; +} + +class YamlTagError extends YamlSchemaError { + public readonly specificTagName!: string; + + public readonly explicitTagName!: string; + + public readonly tagKind!: string; + + public readonly tagPosition?: Position; + + public readonly nodeCanonicalContent?: string; + + constructor(message?: string, structuredOptions?: YamlTagErrorOptions) { + super(message, structuredOptions); + + if (typeof structuredOptions !== 'undefined') { + this.specificTagName = structuredOptions.specificTagName; + this.explicitTagName = structuredOptions.explicitTagName; + this.tagKind = structuredOptions.tagKind; + this.tagPosition = structuredOptions.tagPosition; + this.nodeCanonicalContent = structuredOptions.nodeCanonicalContent; + } + } +} + +export default YamlTagError; diff --git a/packages/apidom-ast/src/yaml/schemas/failsafe/index.ts b/packages/apidom-ast/src/yaml/schemas/failsafe/index.ts index d176e2aca..aab344b47 100644 --- a/packages/apidom-ast/src/yaml/schemas/failsafe/index.ts +++ b/packages/apidom-ast/src/yaml/schemas/failsafe/index.ts @@ -1,6 +1,7 @@ +import { clone } from 'ramda'; import stampit from 'stampit'; -import { ApiDOMError } from '@swagger-api/apidom-error'; +import YamlTagError from '../../errors/YamlTagError'; import YamlDirective from '../../nodes/YamlDirective'; import { YamlNodeKind } from '../../nodes/YamlTag'; import GenericMapping from './GenericMapping'; @@ -88,12 +89,23 @@ const FailsafeSchema = stampit({ // mechanism for resolving node (tag implementation) not found if (typeof tag === 'undefined') { - throw new ApiDOMError(`Tag "${specificTagName}" couldn't be resolved`); + throw new YamlTagError(`Tag "${specificTagName}" was not recognized.`, { + specificTagName, + explicitTagName: node.tag.explicitName, + tagKind: node.tag.kind, + tagPosition: clone(node.tag.position), + }); } // node content is not compatible with resolving mechanism (tag implementation) if (!tag.test(canonicalNode)) { - throw new ApiDOMError(`Node couldn't be resolved against tag "${specificTagName}"`); + throw new YamlTagError(`Node couldn't be resolved against the tag "${specificTagName}"`, { + specificTagName, + explicitTagName: node.tag.explicitName, + tagKind: node.tag.kind, + tagPosition: clone(node.tag.position), + nodeCanonicalContent: canonicalNode.content, + }); } return tag.resolve(canonicalNode); diff --git a/packages/apidom-parser-adapter-yaml-1-2/src/syntactic-analysis/indirect/visitors/CstVisitor.ts b/packages/apidom-parser-adapter-yaml-1-2/src/syntactic-analysis/indirect/visitors/CstVisitor.ts index cdd32fe90..5a3719997 100644 --- a/packages/apidom-parser-adapter-yaml-1-2/src/syntactic-analysis/indirect/visitors/CstVisitor.ts +++ b/packages/apidom-parser-adapter-yaml-1-2/src/syntactic-analysis/indirect/visitors/CstVisitor.ts @@ -63,7 +63,7 @@ const CstVisitor = stampit({ const kindNodeToYamlTag = (node: TreeCursorSyntaxNode) => { const { tag: tagNode } = node; - const explicitName = tagNode?.text || node.type === 'plain_scalar' ? '?' : '!'; + const explicitName = tagNode?.text || (node.type === 'plain_scalar' ? '?' : '!'); // eslint-disable-next-line no-nested-ternary const kind = node.type.endsWith('mapping') diff --git a/packages/apidom-parser-adapter-yaml-1-2/test/adapter-node/index.ts b/packages/apidom-parser-adapter-yaml-1-2/test/adapter-node/index.ts index 19bb42af9..bac4c4751 100644 --- a/packages/apidom-parser-adapter-yaml-1-2/test/adapter-node/index.ts +++ b/packages/apidom-parser-adapter-yaml-1-2/test/adapter-node/index.ts @@ -1,6 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { assert, expect } from 'chai'; +import { YamlTagError } from '@swagger-api/apidom-ast'; import { toValue, isObjectElement, isParseResultElement, sexprs } from '@swagger-api/apidom-core'; import * as adapter from '../../src/adapter-node'; @@ -110,4 +111,48 @@ describe('adapter-node', function () { assert.strictEqual(toValue(parseResult.errors.get(0)), '(Error YAML syntax error)'); }); }); + + context('given valid YAML 1.2 with unrecognized tag', function () { + specify('should throw error', async function () { + const unknownTagSpec = 'prop: !!unknowntag value'; + + try { + await adapter.parse(unknownTagSpec); + assert.fail('should throw YamlTagError'); + // @ts-ignore + } catch (error: YamlTagError) { + assert.instanceOf(error, YamlTagError); + assert.include(error, { + specificTagName: 'tag:yaml.org,2002:unknowntag', + explicitTagName: '!!unknowntag', + tagKind: 'Scalar', + nodeCanonicalContent: undefined, + }); + assert.include(error.tagPosition.start, { type: 'point', row: 0, column: 6, char: 6 }); + assert.include(error.tagPosition.end, { type: 'point', row: 0, column: 18, char: 18 }); + } + }); + }); + + context('given valid YAML 1.2 with node content failing constraints imposed by tag', function () { + specify('should throw error', async function () { + const unknownTagSpec = 'prop: !!int value'; + + try { + await adapter.parse(unknownTagSpec); + assert.fail('should throw YamlTagError'); + // @ts-ignore + } catch (error: YamlTagError) { + assert.instanceOf(error, YamlTagError); + assert.include(error, { + specificTagName: 'tag:yaml.org,2002:int', + explicitTagName: '!!int', + tagKind: 'Scalar', + nodeCanonicalContent: 'value', + }); + assert.include(error.tagPosition.start, { type: 'point', row: 0, column: 6, char: 6 }); + assert.include(error.tagPosition.end, { type: 'point', row: 0, column: 11, char: 11 }); + } + }); + }); });