From bc2e06c8a0a1750a901e6ea6375dd2706f384f43 Mon Sep 17 00:00:00 2001 From: Kirill Fuks Date: Tue, 19 Mar 2024 00:27:34 +0100 Subject: [PATCH] fix(nodes, accessors): fix validations with $ref nodes Allow usage of readOnly and WriteOnly tags on $ref nodes fix #34 --- .../__tests__/getValidations.spec.ts | 46 +++++++++++++++++++ src/accessors/getValidations.ts | 27 ++++++++++- src/nodes/RegularNode.ts | 2 +- src/nodes/mirrored/MirroredRegularNode.ts | 3 +- 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/accessors/__tests__/getValidations.spec.ts b/src/accessors/__tests__/getValidations.spec.ts index 306cb50..626e0eb 100644 --- a/src/accessors/__tests__/getValidations.spec.ts +++ b/src/accessors/__tests__/getValidations.spec.ts @@ -12,6 +12,7 @@ describe('getValidations util', () => { multipleOf: 2, }, [SchemaNodeKind.Integer], + {}, ), ).toStrictEqual({ exclusiveMaximum: true, @@ -20,4 +21,49 @@ describe('getValidations util', () => { multipleOf: 2, }); }); + it('should support $ref visibility', () => { + expect( + getValidations( + { + readOnly: true, + }, + [SchemaNodeKind.Object], + { + writeOnly: true, + }, + ), + ).toStrictEqual({ + writeOnly: true, + }); + + expect( + getValidations( + { + writeOnly: true, + }, + [SchemaNodeKind.Object], + { + readOnly: true, + }, + ), + ).toStrictEqual({ + readOnly: true, + }); + + expect( + getValidations({}, [SchemaNodeKind.Object], { + readOnly: true, + }), + ).toStrictEqual({ + readOnly: true, + }); + + expect( + getValidations({}, [SchemaNodeKind.Object], { + writeOnly: true, + }), + ).toStrictEqual({ + writeOnly: true, + }); + }); }); diff --git a/src/accessors/getValidations.ts b/src/accessors/getValidations.ts index 6425248..477bdd3 100644 --- a/src/accessors/getValidations.ts +++ b/src/accessors/getValidations.ts @@ -30,11 +30,34 @@ function getTypeValidations(types: SchemaNodeKind[]): (keyof SchemaFragment)[] | return extraValidations; } -export function getValidations(fragment: SchemaFragment, types: SchemaNodeKind[] | null): Dictionary { +export function getValidations( + fragment: SchemaFragment, + types: SchemaNodeKind[] | null, + originalFragment: SchemaFragment | null = null, +): Dictionary { const extraValidations = types === null ? null : getTypeValidations(types); + const fragmentValidations: Dictionary = pick(fragment, COMMON_VALIDATION_TYPES); + + if (originalFragment) { + const originalValidations: Dictionary = pick(originalFragment, COMMON_VALIDATION_TYPES); + + if (originalValidations.readOnly as boolean) { + fragmentValidations.readOnly = true; + if (fragmentValidations.writeOnly as boolean) { + delete fragmentValidations.writeOnly; + } + } + if (originalValidations.writeOnly as boolean) { + fragmentValidations.writeOnly = true; + if (fragmentValidations.readOnly as boolean) { + delete fragmentValidations.readOnly; + } + } + } + return { - ...pick(fragment, COMMON_VALIDATION_TYPES), + ...fragmentValidations, ...(extraValidations !== null ? pick(fragment, extraValidations) : null), }; } diff --git a/src/nodes/RegularNode.ts b/src/nodes/RegularNode.ts index ed8aa7a..f291a7e 100644 --- a/src/nodes/RegularNode.ts +++ b/src/nodes/RegularNode.ts @@ -47,8 +47,8 @@ export class RegularNode extends BaseNode { this.title = unwrapStringOrNull(fragment.title); this.annotations = getAnnotations(fragment); - this.validations = getValidations(fragment, this.types); this.originalFragment = context?.originalFragment ?? fragment; + this.validations = getValidations(fragment, this.types, this.originalFragment); this.children = void 0; } diff --git a/src/nodes/mirrored/MirroredRegularNode.ts b/src/nodes/mirrored/MirroredRegularNode.ts index 873c15f..f8dc7ba 100644 --- a/src/nodes/mirrored/MirroredRegularNode.ts +++ b/src/nodes/mirrored/MirroredRegularNode.ts @@ -1,5 +1,6 @@ import type { Dictionary } from '@stoplight/types'; +import { getValidations } from '../../accessors/getValidations'; import { isReferenceNode, isRegularNode } from '../../guards'; import type { SchemaFragment } from '../../types'; import { isNonNullable } from '../../utils'; @@ -39,7 +40,7 @@ export class MirroredRegularNode extends BaseNode implements RegularNode { super(); this.fragment = mirroredNode.fragment; this.originalFragment = context?.originalFragment ?? mirroredNode.originalFragment; - + this.validations = getValidations(this.fragment, null, this.originalFragment); this.cache = new WeakMap(); this._this = new Proxy(this, {