From df7bf496f9f0be87834e6f0aaf4ec736927c448c Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Thu, 26 Oct 2023 14:18:52 +0200 Subject: [PATCH] refactor: deep clone elements before assigning to structured errrors Refs #3209 --- packages/apidom-ast/src/traversal/visitor.ts | 10 +++-- .../src/yaml/errors/YamlTagError.ts | 7 +++- .../src/yaml/schemas/failsafe/index.ts | 2 + .../src/clone/errors/CloneError.ts | 17 +++++++- packages/apidom-core/src/deepmerge.ts | 2 +- .../src/errors/EvaluationJsonPathError.ts | 11 ++--- .../errors/MultiEvaluationJsonPathError.ts | 11 ++--- .../apidom-json-path/src/evaluate-multi.ts | 4 +- packages/apidom-json-path/src/evaluate.ts | 4 +- .../EvaluationRelativeJsonPointerError.ts | 40 ++++--------------- .../src/evaluate.ts | 37 ++++++++--------- .../src/errors/EvaluationJsonPointerError.ts | 11 ++--- packages/apidom-json-pointer/src/evaluate.ts | 16 +++++--- 13 files changed, 82 insertions(+), 90 deletions(-) diff --git a/packages/apidom-ast/src/traversal/visitor.ts b/packages/apidom-ast/src/traversal/visitor.ts index 8f54c045b9..8d828a0266 100644 --- a/packages/apidom-ast/src/traversal/visitor.ts +++ b/packages/apidom-ast/src/traversal/visitor.ts @@ -1,4 +1,4 @@ -import { ApiDOMError } from '@swagger-api/apidom-error'; +import { ApiDOMStructuredError } from '@swagger-api/apidom-error'; /** * SPDX-FileCopyrightText: Copyright (c) GraphQL Contributors @@ -281,7 +281,9 @@ export const visit = ( let result; if (!Array.isArray(node)) { if (!nodePredicate(node)) { - throw new ApiDOMError(`Invalid AST Node: ${JSON.stringify(node)}`); + throw new ApiDOMStructuredError(`Invalid AST Node: ${String(node)}`, { + node, + }); } // cycle detected; skipping over a sub-tree to avoid recursion @@ -436,7 +438,9 @@ visit[Symbol.for('nodejs.util.promisify.custom')] = async ( let result; if (!Array.isArray(node)) { if (!nodePredicate(node)) { - throw new ApiDOMError(`Invalid AST Node: ${JSON.stringify(node)}`); + throw new ApiDOMStructuredError(`Invalid AST Node: ${String(node)}`, { + node, + }); } // cycle detected; skipping over a sub-tree to avoid recursion diff --git a/packages/apidom-ast/src/yaml/errors/YamlTagError.ts b/packages/apidom-ast/src/yaml/errors/YamlTagError.ts index d9b3d25d44..897771889d 100644 --- a/packages/apidom-ast/src/yaml/errors/YamlTagError.ts +++ b/packages/apidom-ast/src/yaml/errors/YamlTagError.ts @@ -2,13 +2,15 @@ import { ApiDOMErrorOptions } from '@swagger-api/apidom-error'; import YamlSchemaError from './YamlSchemaError'; import Position from '../../Position'; +import Node from '../../Node'; -export interface YamlTagErrorOptions extends ApiDOMErrorOptions { +export interface YamlTagErrorOptions extends ApiDOMErrorOptions { readonly specificTagName: string; readonly explicitTagName: string; readonly tagKind: string; readonly tagPosition?: Position; readonly nodeCanonicalContent?: string; + readonly node?: T; } class YamlTagError extends YamlSchemaError { @@ -22,6 +24,8 @@ class YamlTagError extends YamlSchemaError { public readonly nodeCanonicalContent?: string; + public readonly node?: unknown; + constructor(message?: string, structuredOptions?: YamlTagErrorOptions) { super(message, structuredOptions); @@ -31,6 +35,7 @@ class YamlTagError extends YamlSchemaError { this.tagKind = structuredOptions.tagKind; this.tagPosition = structuredOptions.tagPosition; this.nodeCanonicalContent = structuredOptions.nodeCanonicalContent; + this.node = structuredOptions.node; } } } diff --git a/packages/apidom-ast/src/yaml/schemas/failsafe/index.ts b/packages/apidom-ast/src/yaml/schemas/failsafe/index.ts index aab344b475..c983758feb 100644 --- a/packages/apidom-ast/src/yaml/schemas/failsafe/index.ts +++ b/packages/apidom-ast/src/yaml/schemas/failsafe/index.ts @@ -94,6 +94,7 @@ const FailsafeSchema = stampit({ explicitTagName: node.tag.explicitName, tagKind: node.tag.kind, tagPosition: clone(node.tag.position), + node: node.clone(), }); } @@ -105,6 +106,7 @@ const FailsafeSchema = stampit({ tagKind: node.tag.kind, tagPosition: clone(node.tag.position), nodeCanonicalContent: canonicalNode.content, + node: node.clone(), }); } diff --git a/packages/apidom-core/src/clone/errors/CloneError.ts b/packages/apidom-core/src/clone/errors/CloneError.ts index 5302137d79..5db9bcdbc6 100644 --- a/packages/apidom-core/src/clone/errors/CloneError.ts +++ b/packages/apidom-core/src/clone/errors/CloneError.ts @@ -1,5 +1,20 @@ import { ApiDOMStructuredError } from '@swagger-api/apidom-error'; +import type { ApiDOMErrorOptions } from '@swagger-api/apidom-error'; -class CloneError extends ApiDOMStructuredError {} +interface CloneErrorOptions extends ApiDOMErrorOptions { + readonly value: unknown; +} + +class CloneError extends ApiDOMStructuredError { + public readonly value: unknown; + + constructor(message?: string, structuredOptions?: CloneErrorOptions) { + super(message, structuredOptions); + + if (typeof structuredOptions !== 'undefined') { + this.value = structuredOptions.source; + } + } +} export default CloneError; diff --git a/packages/apidom-core/src/deepmerge.ts b/packages/apidom-core/src/deepmerge.ts index c0c948b88e..b6c2aaeb8c 100644 --- a/packages/apidom-core/src/deepmerge.ts +++ b/packages/apidom-core/src/deepmerge.ts @@ -158,7 +158,7 @@ export default function deepmerge( deepmerge.all = (list: ObjectOrArrayElement[], options?: DeepMergeOptions) => { if (!Array.isArray(list)) { - throw new TypeError('First argument should be an array.'); + throw new TypeError('First argument of deepmerge should be an array.'); } if (list.length === 0) { return new ObjectElement(); diff --git a/packages/apidom-json-path/src/errors/EvaluationJsonPathError.ts b/packages/apidom-json-path/src/errors/EvaluationJsonPathError.ts index e398611c5a..490af457ce 100644 --- a/packages/apidom-json-path/src/errors/EvaluationJsonPathError.ts +++ b/packages/apidom-json-path/src/errors/EvaluationJsonPathError.ts @@ -1,4 +1,4 @@ -import { Element, hasElementSourceMap, toValue } from '@swagger-api/apidom-core'; +import { Element } from '@swagger-api/apidom-core'; import { ApiDOMErrorOptions } from '@swagger-api/apidom-error'; import JsonPathError from './JsonPathError'; @@ -11,19 +11,14 @@ export interface EvaluationJsonPathErrorOptions extends ApiDO class EvaluationJsonPathError extends JsonPathError { public readonly path!: string | string[]; - public readonly element!: string; - - public readonly elementSourceMap?: [[number, number, number], [number, number, number]]; + public readonly element!: T; constructor(message?: string, structuredOptions?: EvaluationJsonPathErrorOptions) { super(message, structuredOptions); if (typeof structuredOptions !== 'undefined') { this.path = structuredOptions.path; - this.element = structuredOptions.element.element; - if (hasElementSourceMap(structuredOptions.element)) { - this.elementSourceMap = toValue(structuredOptions.element.getMetaProperty('sourceMap')); - } + this.element = structuredOptions.element; } } } diff --git a/packages/apidom-json-path/src/errors/MultiEvaluationJsonPathError.ts b/packages/apidom-json-path/src/errors/MultiEvaluationJsonPathError.ts index 849f2c5f4d..14734df392 100644 --- a/packages/apidom-json-path/src/errors/MultiEvaluationJsonPathError.ts +++ b/packages/apidom-json-path/src/errors/MultiEvaluationJsonPathError.ts @@ -1,4 +1,4 @@ -import { Element, hasElementSourceMap, toValue } from '@swagger-api/apidom-core'; +import { Element } from '@swagger-api/apidom-core'; import { ApiDOMErrorOptions } from '@swagger-api/apidom-error'; import JsonPathError from './JsonPathError'; @@ -11,19 +11,14 @@ export interface MultiEvaluationJsonPathErrorOptions extends class MultiEvaluationJsonPathError extends JsonPathError { public readonly paths!: string[] | string[][]; - public readonly element!: string; - - public readonly elementSourceMap?: [[number, number, number], [number, number, number]]; + public readonly element!: T; constructor(message?: string, structuredOptions?: MultiEvaluationJsonPathErrorOptions) { super(message, structuredOptions); if (typeof structuredOptions !== 'undefined') { this.paths = structuredOptions.paths; - this.element = structuredOptions.element.element; - if (hasElementSourceMap(structuredOptions.element)) { - this.elementSourceMap = toValue(structuredOptions.element.getMetaProperty('sourceMap')); - } + this.element = structuredOptions.element; } } } diff --git a/packages/apidom-json-path/src/evaluate-multi.ts b/packages/apidom-json-path/src/evaluate-multi.ts index b8e2733f4f..b05cbc1bde 100644 --- a/packages/apidom-json-path/src/evaluate-multi.ts +++ b/packages/apidom-json-path/src/evaluate-multi.ts @@ -1,5 +1,5 @@ import { JSONPath } from 'jsonpath-plus'; -import { Element, toValue } from '@swagger-api/apidom-core'; +import { Element, toValue, cloneDeep } from '@swagger-api/apidom-core'; import { evaluate as jsonPointerEvaluate } from '@swagger-api/apidom-json-pointer'; import MultiEvaluationJsonPathError from './errors/MultiEvaluationJsonPathError'; @@ -45,7 +45,7 @@ const evaluateMulti: EvaluateMulti = (paths, element) => { `JSON Path evaluation failed while multi-evaluating "${String(paths)}".`, { paths, - element, + element: cloneDeep(element), cause: error, }, ); diff --git a/packages/apidom-json-path/src/evaluate.ts b/packages/apidom-json-path/src/evaluate.ts index bfd3276443..cf94fbf28b 100644 --- a/packages/apidom-json-path/src/evaluate.ts +++ b/packages/apidom-json-path/src/evaluate.ts @@ -1,5 +1,5 @@ import { JSONPath } from 'jsonpath-plus'; -import { Element, toValue } from '@swagger-api/apidom-core'; +import { Element, toValue, cloneDeep } from '@swagger-api/apidom-core'; import { evaluate as jsonPointerEvaluate } from '@swagger-api/apidom-json-pointer'; import EvaluationJsonPathError from './errors/EvaluationJsonPathError'; @@ -25,7 +25,7 @@ const evaluate: Evaluate = (path, element) => { } catch (error: unknown) { throw new EvaluationJsonPathError( `JSON Path evaluation failed while evaluating "${String(path)}".`, - { path, element, cause: error }, + { path, element: cloneDeep(element), cause: error }, ); } }; diff --git a/packages/apidom-json-pointer-relative/src/errors/EvaluationRelativeJsonPointerError.ts b/packages/apidom-json-pointer-relative/src/errors/EvaluationRelativeJsonPointerError.ts index 4ad9858ad6..ffacc7b447 100644 --- a/packages/apidom-json-pointer-relative/src/errors/EvaluationRelativeJsonPointerError.ts +++ b/packages/apidom-json-pointer-relative/src/errors/EvaluationRelativeJsonPointerError.ts @@ -1,4 +1,4 @@ -import { Element, hasElementSourceMap, toValue } from '@swagger-api/apidom-core'; +import { Element } from '@swagger-api/apidom-core'; import { ApiDOMErrorOptions } from '@swagger-api/apidom-error'; import RelativeJsonPointerError from './RelativeJsonPointerError'; @@ -21,17 +21,11 @@ class EvaluationRelativeJsonPointerError< > extends RelativeJsonPointerError { public readonly relativePointer!: string; - public readonly currentElement!: string; + public readonly currentElement!: T; - public readonly currentElementSourceMap?: [[number, number, number], [number, number, number]]; + public readonly rootElement!: U; - public readonly rootElement!: string; - - public readonly rootElementSourceMap?: [[number, number, number], [number, number, number]]; - - public readonly cursorElement?: string; - - public readonly cursorElementSourceMap?: [[number, number, number], [number, number, number]]; + public readonly cursorElement?: V; constructor( message?: string, @@ -41,29 +35,9 @@ class EvaluationRelativeJsonPointerError< if (typeof structuredOptions !== 'undefined') { this.relativePointer = structuredOptions.relativePointer; - - this.currentElement = structuredOptions.currentElement.element; - if (hasElementSourceMap(structuredOptions.currentElement)) { - this.currentElementSourceMap = toValue( - structuredOptions.currentElement.getMetaProperty('sourceMap'), - ); - } - - this.rootElement = structuredOptions.rootElement.element; - if (hasElementSourceMap(structuredOptions.rootElement)) { - this.rootElementSourceMap = toValue( - structuredOptions.rootElement.getMetaProperty('sourceMap'), - ); - } - - if (typeof structuredOptions.cursorElement !== 'undefined') { - this.cursorElement = structuredOptions.cursorElement.element; - if (hasElementSourceMap(structuredOptions.cursorElement)) { - this.cursorElementSourceMap = toValue( - structuredOptions.cursorElement.getMetaProperty('sourceMap'), - ); - } - } + this.currentElement = structuredOptions.currentElement; + this.rootElement = structuredOptions.rootElement; + this.cursorElement = structuredOptions.cursorElement; } } } diff --git a/packages/apidom-json-pointer-relative/src/evaluate.ts b/packages/apidom-json-pointer-relative/src/evaluate.ts index 18162d6413..906325a9ce 100644 --- a/packages/apidom-json-pointer-relative/src/evaluate.ts +++ b/packages/apidom-json-pointer-relative/src/evaluate.ts @@ -1,6 +1,7 @@ import { Element, visit, + cloneDeep, BREAK, isElement, isMemberElement, @@ -43,9 +44,9 @@ const evaluate = ( 'Relative JSON Pointer evaluation failed. Current element not found inside the root element', { relativePointer, - currentElement, - rootElement, - cursorElement: cursor, + currentElement: cloneDeep(currentElement), + rootElement: cloneDeep(rootElement), + cursorElement: cloneDeep.safe(cursor), }, ); } @@ -70,9 +71,9 @@ const evaluate = ( 'Relative JSON Pointer evaluation failed while parsing the pointer.', { relativePointer, - currentElement, - rootElement, - cursorElement: cursor, + currentElement: cloneDeep(currentElement), + rootElement: cloneDeep(currentElement), + cursorElement: cloneDeep.safe(cursor), cause: error, }, ); @@ -98,9 +99,9 @@ const evaluate = ( `Relative JSON Pointer evaluation failed on non-negative-integer prefix of "${relativeJsonPointer.nonNegativeIntegerPrefix}"`, { relativePointer, - currentElement, - rootElement, - cursorElement: cursor, + currentElement: cloneDeep(currentElement), + rootElement: cloneDeep(rootElement), + cursorElement: cloneDeep.safe(cursor), }, ); } @@ -117,9 +118,9 @@ const evaluate = ( `Relative JSON Pointer evaluation failed failed on index-manipulation "${relativeJsonPointer.indexManipulation}"`, { relativePointer, - currentElement, - rootElement, - cursorElement: cursor, + currentElement: cloneDeep(currentElement), + rootElement: cloneDeep(rootElement), + cursorElement: cloneDeep.safe(cursor), }, ); } @@ -133,9 +134,9 @@ const evaluate = ( `Relative JSON Pointer evaluation failed on index-manipulation "${relativeJsonPointer.indexManipulation}"`, { relativePointer, - currentElement, - rootElement, - cursorElement: cursor, + currentElement: cloneDeep(currentElement), + rootElement: cloneDeep(rootElement), + cursorElement: cloneDeep.safe(cursor), }, ); } @@ -152,9 +153,9 @@ const evaluate = ( 'Relative JSON Pointer evaluation failed. Current element cannot be the root element to apply "#"', { relativePointer, - currentElement, - rootElement, - cursorElement: cursor, + currentElement: cloneDeep(currentElement), + rootElement: cloneDeep(rootElement), + cursorElement: cloneDeep.safe(cursor), }, ); } diff --git a/packages/apidom-json-pointer/src/errors/EvaluationJsonPointerError.ts b/packages/apidom-json-pointer/src/errors/EvaluationJsonPointerError.ts index b5d60a8bdb..0c24b6671a 100644 --- a/packages/apidom-json-pointer/src/errors/EvaluationJsonPointerError.ts +++ b/packages/apidom-json-pointer/src/errors/EvaluationJsonPointerError.ts @@ -1,5 +1,5 @@ import { ApiDOMErrorOptions } from '@swagger-api/apidom-error'; -import { Element, hasElementSourceMap, toValue } from '@swagger-api/apidom-core'; +import { Element } from '@swagger-api/apidom-core'; import JsonPointerError from './JsonPointerError'; @@ -20,9 +20,7 @@ class EvaluationJsonPointerError extends JsonPointerError { public readonly failedTokenPosition?: number; - public readonly element!: string; - - public readonly elementSourceMap?: [[number, number, number], [number, number, number]]; + public readonly element!: T; constructor(message?: string, structuredOptions?: EvaluationJsonPointerErrorOptions) { super(message, structuredOptions); @@ -34,10 +32,7 @@ class EvaluationJsonPointerError extends JsonPointerError { } this.failedToken = structuredOptions.failedToken; this.failedTokenPosition = structuredOptions.failedTokenPosition; - this.element = structuredOptions.element.element; - if (hasElementSourceMap(structuredOptions.element)) { - this.elementSourceMap = toValue(structuredOptions.element.getMetaProperty('sourceMap')); - } + this.element = structuredOptions.element; } } } diff --git a/packages/apidom-json-pointer/src/evaluate.ts b/packages/apidom-json-pointer/src/evaluate.ts index 964a48a8d1..22dfb0dfd2 100644 --- a/packages/apidom-json-pointer/src/evaluate.ts +++ b/packages/apidom-json-pointer/src/evaluate.ts @@ -1,5 +1,5 @@ import { isInteger } from 'ramda-adjunct'; -import { Element, isObjectElement, isArrayElement } from '@swagger-api/apidom-core'; +import { Element, isObjectElement, isArrayElement, cloneDeep } from '@swagger-api/apidom-core'; import parse from './parse'; import EvaluationJsonPointerError from './errors/EvaluationJsonPointerError'; @@ -15,7 +15,7 @@ const evaluate = (pointer: string, element: T): Element => { `JSON Pointer evaluation failed while parsing the pointer "${pointer}".`, { pointer, - element, + element: cloneDeep(element), cause: error, }, ); @@ -32,7 +32,7 @@ const evaluate = (pointer: string, element: T): Element => { tokens, failedToken: token, failedTokenPosition: tokenPosition, - element: acc, + element: cloneDeep(acc), }, ); } @@ -44,7 +44,13 @@ const evaluate = (pointer: string, element: T): Element => { if (!(token in acc.content) || !isInteger(Number(token))) { throw new EvaluationJsonPointerError( `JSON Pointer evaluation failed while evaluating token "${token}" against an ArrayElement`, - { pointer, tokens, failedToken: token, failedTokenPosition: tokenPosition, element: acc }, + { + pointer, + tokens, + failedToken: token, + failedTokenPosition: tokenPosition, + element: cloneDeep(acc), + }, ); } // @ts-ignore @@ -58,7 +64,7 @@ const evaluate = (pointer: string, element: T): Element => { tokens, failedToken: token, failedTokenPosition: tokenPosition, - element: acc, + element: cloneDeep(acc), }, ); }, element);