From 8a9f70a2c6933f283b587f888e4fe1928d27e71f Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 5 May 2025 16:31:26 -0400 Subject: [PATCH] Support draft-04 schemas This helps to support the schema for openapi 3.0.0, among others, that use an older draft of JSON schema that has some type differences that make it incompatible with JSON schema draft 07. See #1006 , I think the root cause for the issues that PR caused is that we were trying to download and cache the metaschema from the "URL" instead of using the copy that's bundled with `ajv`. Fixes #780, Fixes #752 (and many, many duplicates we'll have to find and clean up) Signed-off-by: David Thompson --- package.json | 1 + .../services/yamlSchemaService.ts | 15 ++++- test/schemaValidation.test.ts | 62 +++++++++++++++++++ yarn.lock | 5 ++ 4 files changed, 81 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c4a544c73..4b0e024ae 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ }, "dependencies": { "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", "lodash": "4.17.21", "prettier": "^3.5.0", "request-light": "^0.5.7", diff --git a/src/languageservice/services/yamlSchemaService.ts b/src/languageservice/services/yamlSchemaService.ts index 0cd199b3b..8ae730f47 100644 --- a/src/languageservice/services/yamlSchemaService.ts +++ b/src/languageservice/services/yamlSchemaService.ts @@ -28,9 +28,11 @@ import { JSONSchemaDescriptionExt } from '../../requestTypes'; import { SchemaVersions } from '../yamlTypes'; import Ajv, { DefinedError } from 'ajv'; +import Ajv4 from 'ajv-draft-04'; import { getSchemaTitle } from '../utils/schemaUtils'; const ajv = new Ajv(); +const ajv4 = new Ajv4(); const localize = nls.loadMessageBundle(); @@ -39,6 +41,11 @@ const localize = nls.loadMessageBundle(); const jsonSchema07 = require('ajv/dist/refs/json-schema-draft-07.json'); const schema07Validator = ajv.compile(jsonSchema07); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const jsonSchema04 = require('ajv-draft-04/dist/refs/json-schema-draft-04.json'); +const schema04Validator = ajv4.compile(jsonSchema04); +const SCHEMA_04_URI_WITH_HTTPS = ajv4.defaultMeta().replace('http://', 'https://'); + export declare type CustomSchemaProvider = (uri: string) => Promise; export enum MODIFICATION_ACTIONS { @@ -164,9 +171,13 @@ export class YAMLSchemaService extends JSONSchemaService { let schema: JSONSchema = schemaToResolve.schema; const contextService = this.contextService; - if (!schema07Validator(schema)) { + const validator = + this.normalizeId(schema.$schema) === ajv4.defaultMeta() || this.normalizeId(schema.$schema) === SCHEMA_04_URI_WITH_HTTPS + ? schema04Validator + : schema07Validator; + if (!validator(schema)) { const errs: string[] = []; - for (const err of schema07Validator.errors as DefinedError[]) { + for (const err of validator.errors as DefinedError[]) { errs.push(`${err.instancePath} : ${err.message}`); } resolveErrors.push(`Schema '${getSchemaTitle(schemaToResolve.schema, schemaURL)}' is not valid:\n${errs.join('\n')}`); diff --git a/test/schemaValidation.test.ts b/test/schemaValidation.test.ts index 5cd9231b3..edb73810f 100644 --- a/test/schemaValidation.test.ts +++ b/test/schemaValidation.test.ts @@ -2151,4 +2151,66 @@ obj: result = await parseSetup(content); expect(result.length).to.eq(0); }); + + it('draft-04 schema', async () => { + const schema: JSONSchema = { + $schema: 'http://json-schema.org/draft-04/schema#', + type: 'object', + properties: { + myProperty: { + $ref: '#/definitions/Interface%3Ctype%3E', + }, + }, + definitions: { + 'Interface': { + type: 'object', + properties: { + foo: { + type: 'string', + }, + multipleOf: { + type: 'number', + minimum: 0, + exclusiveMinimum: true, + }, + }, + }, + }, + }; + schemaProvider.addSchema(SCHEMA_ID, schema); + const content = `myProperty:\n foo: bar\n multipleOf: 1`; + const result = await parseSetup(content); + assert.equal(result.length, 0); + }); + + it('draft-04 schema with https in metaschema URI', async () => { + const schema: JSONSchema = { + $schema: 'https://json-schema.org/draft-04/schema#', + type: 'object', + properties: { + myProperty: { + $ref: '#/definitions/Interface%3Ctype%3E', + }, + }, + definitions: { + 'Interface': { + type: 'object', + properties: { + foo: { + type: 'string', + }, + multipleOf: { + type: 'number', + minimum: 0, + exclusiveMinimum: true, + }, + }, + }, + }, + }; + schemaProvider.addSchema(SCHEMA_ID, schema); + const content = `myProperty:\n foo: bar\n multipleOf: 1`; + const result = await parseSetup(content); + assert.equal(result.length, 0); + }); }); diff --git a/yarn.lock b/yarn.lock index ce1c8eaa5..877c50d06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -681,6 +681,11 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ajv-draft-04@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8" + integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== + ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"