@@ -29,6 +29,12 @@ import { parse } from 'yaml';
2929import * as Json from 'jsonc-parser' ;
3030import { getSchemaTitle } from '../utils/schemaUtils' ;
3131
32+ import * as Draft04 from '@hyperjump/json-schema/draft-04' ;
33+ import * as Draft07 from '@hyperjump/json-schema/draft-07' ;
34+ import * as Draft201909 from '@hyperjump/json-schema/draft-2019-09' ;
35+ import * as Draft202012 from '@hyperjump/json-schema/draft-2020-12' ;
36+
37+ type SupportedSchemaVersions = '2020-12' | '2019-09' | 'draft-07' | 'draft-04' ;
3238export declare type CustomSchemaProvider = ( uri : string ) => Promise < string | string [ ] > ;
3339
3440export enum MODIFICATION_ACTIONS {
@@ -154,12 +160,34 @@ export class YAMLSchemaService extends JSONSchemaService {
154160 let schema : JSONSchema = schemaToResolve . schema ;
155161 const contextService = this . contextService ;
156162
157- // Basic schema validation - check if schema is a valid object
158163 if ( typeof schema !== 'object' || schema === null || Array . isArray ( schema ) ) {
159164 const invalidSchemaType = Array . isArray ( schema ) ? 'array' : typeof schema ;
160165 resolveErrors . push (
161166 `Schema '${ getSchemaTitle ( schemaToResolve . schema , schemaURL ) } ' is not valid:\nWrong schema: "${ invalidSchemaType } ", it MUST be an Object or Boolean`
162167 ) ;
168+ } else {
169+ try {
170+ const schemaVersion = this . detectSchemaVersion ( schema ) ;
171+ const validator = this . getValidatorForVersion ( schemaVersion ) ;
172+ const metaSchemaUrl = this . getSchemaMetaSchema ( schemaVersion ) ;
173+
174+ // Validate the schema against its meta-schema using the URL directly
175+ const result = await validator . validate ( metaSchemaUrl , schema , 'BASIC' ) ;
176+ if ( ! result . valid && result . errors ) {
177+ const errs : string [ ] = [ ] ;
178+ for ( const error of result . errors ) {
179+ if ( error . instanceLocation && error . keyword ) {
180+ errs . push ( `${ error . instanceLocation } : ${ this . extractKeywordName ( error . keyword ) } constraint violation` ) ;
181+ }
182+ }
183+ if ( errs . length > 0 ) {
184+ resolveErrors . push ( `Schema '${ getSchemaTitle ( schemaToResolve . schema , schemaURL ) } ' is not valid:\n${ errs . join ( '\n' ) } ` ) ;
185+ }
186+ }
187+ } catch ( error ) {
188+ // If meta-schema validation fails, log but don't block schema loading
189+ console . error ( `Failed to validate schema meta-schema: ${ error . message } ` ) ;
190+ }
163191 }
164192
165193 const findSection = ( schema : JSONSchema , path : string ) : JSONSchema => {
@@ -725,6 +753,79 @@ export class YAMLSchemaService extends JSONSchemaService {
725753 onResourceChange ( uri : string ) : boolean {
726754 return super . onResourceChange ( uri ) ;
727755 }
756+
757+ /**
758+ * Detect the JSON Schema version from the $schema property
759+ */
760+ private detectSchemaVersion ( schema : JSONSchema ) : SupportedSchemaVersions {
761+ const schemaProperty = schema . $schema ;
762+ if ( typeof schemaProperty === 'string' ) {
763+ if ( schemaProperty . includes ( '2020-12' ) ) {
764+ return '2020-12' ;
765+ } else if ( schemaProperty . includes ( '2019-09' ) ) {
766+ return '2019-09' ;
767+ } else if ( schemaProperty . includes ( 'draft-07' ) || schemaProperty . includes ( 'draft/7' ) ) {
768+ return 'draft-07' ;
769+ } else if ( schemaProperty . includes ( 'draft-04' ) || schemaProperty . includes ( 'draft/4' ) ) {
770+ return 'draft-04' ;
771+ }
772+ }
773+ return 'draft-07' ;
774+ }
775+
776+ /**
777+ * Get the appropriate validator module for a schema version
778+ */
779+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
780+ private getValidatorForVersion ( version : SupportedSchemaVersions ) : any {
781+ switch ( version ) {
782+ case '2020-12' :
783+ return Draft202012 ;
784+ case '2019-09' :
785+ return Draft201909 ;
786+ case 'draft-07' :
787+ return Draft07 ;
788+ case 'draft-04' :
789+ default :
790+ return Draft04 ;
791+ }
792+ }
793+
794+ /**
795+ * Get the correct schema meta URI for a given version
796+ */
797+ private getSchemaMetaSchema ( version : SupportedSchemaVersions ) : string {
798+ switch ( version ) {
799+ case '2020-12' :
800+ return 'https://json-schema.org/draft/2020-12/schema' ;
801+ case '2019-09' :
802+ return 'https://json-schema.org/draft/2019-09/schema' ;
803+ case 'draft-07' :
804+ return 'http://json-schema.org/draft-07/schema' ;
805+ case 'draft-04' :
806+ return 'http://json-schema.org/draft-04/schema' ;
807+ default :
808+ return 'http://json-schema.org/draft-07/schema' ;
809+ }
810+ }
811+
812+ /**
813+ * Extract a human-readable keyword name from a keyword URI
814+ */
815+ private extractKeywordName ( keywordUri : string ) : string {
816+ if ( typeof keywordUri !== 'string' ) {
817+ return 'validation' ;
818+ }
819+
820+ const parts = keywordUri . split ( '/' ) ;
821+ const lastPart = parts [ parts . length - 1 ] ;
822+
823+ if ( lastPart === 'validate' ) {
824+ return 'schema validation' ;
825+ }
826+
827+ return lastPart || 'validation' ;
828+ }
728829}
729830
730831function toDisplayString ( url : string ) : string {
0 commit comments