Skip to content

Commit

Permalink
fix(parser-adapter-yaml-1-2): fix bug related to explicit tag resolut…
Browse files Browse the repository at this point in the history
…ion (#3090)

Structured errors are now thrown with additional metadata.

Refs #3039
  • Loading branch information
char0n authored Aug 29, 2023
1 parent 031aa76 commit 8d8bdc4
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 4 deletions.
3 changes: 3 additions & 0 deletions packages/apidom-ast/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
5 changes: 5 additions & 0 deletions packages/apidom-ast/src/yaml/errors/YamlSchemaError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ApiDOMStructuredError } from '@swagger-api/apidom-error';

class YamlSchemaError extends ApiDOMStructuredError {}

export default YamlSchemaError;
38 changes: 38 additions & 0 deletions packages/apidom-ast/src/yaml/errors/YamlTagError.ts
Original file line number Diff line number Diff line change
@@ -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;
18 changes: 15 additions & 3 deletions packages/apidom-ast/src/yaml/schemas/failsafe/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
45 changes: 45 additions & 0 deletions packages/apidom-parser-adapter-yaml-1-2/test/adapter-node/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 });
}
});
});
});

0 comments on commit 8d8bdc4

Please sign in to comment.