@@ -43,7 +43,7 @@ class Validator {
4343 const ajvSchema = clone ( schema )
4444 this . convertSchemaToAjvFormat ( ajvSchema )
4545 this . ajv . addSchema ( ajvSchema , schemaKey )
46- this . _ajvSchemas [ schemaKey ] = schema
46+ this . _ajvSchemas [ schemaKey ] = ajvSchema
4747 }
4848 }
4949
@@ -70,13 +70,56 @@ class Validator {
7070 schema . fjs_type = 'string'
7171 schema . type . push ( 'object' )
7272 }
73+
74+ // Ajv percent-decodes the $ref fragment before matching it against the
75+ // definitions keys (see ajv's unescapeFragment), while our internal
76+ // serializer resolves refs literally. Decode the definitions/$defs keys
77+ // so that keys copied verbatim into $ref (e.g. 'Some%3Cloremipsum%3E')
78+ // resolve consistently on both sides.
79+ this . normalizeDefinitionsKeys ( schema )
80+
7381 for ( const property in schema ) {
7482 if ( typeof schema [ property ] === 'object' ) {
7583 this . convertSchemaToAjvFormat ( schema [ property ] )
7684 }
7785 }
7886 }
7987
88+ normalizeDefinitionsKeys ( schema ) {
89+ for ( const containerKey of [ 'definitions' , '$defs' ] ) {
90+ const defs = schema [ containerKey ]
91+ if ( defs === null || typeof defs !== 'object' || Array . isArray ( defs ) ) {
92+ continue
93+ }
94+ for ( const key of Object . keys ( defs ) ) {
95+ let decoded
96+ try {
97+ decoded = decodeURIComponent ( key )
98+ } catch {
99+ // Invalid or incomplete percent sequences: decode only the valid
100+ // percent-encoded segments, leaving the rest untouched.
101+ decoded = key . replace ( / % [ 0 - 9 a - f A - F ] { 2 } / g, ( segment ) => {
102+ try {
103+ return decodeURIComponent ( segment )
104+ } catch {
105+ return segment
106+ }
107+ } )
108+ }
109+ if ( decoded === key ) {
110+ continue
111+ }
112+ // Skip when decoding would collide with an existing key, otherwise
113+ // one of the two definitions would silently shadow the other.
114+ if ( Object . prototype . hasOwnProperty . call ( defs , decoded ) ) {
115+ continue
116+ }
117+ defs [ decoded ] = defs [ key ]
118+ delete defs [ key ]
119+ }
120+ }
121+ }
122+
80123 getState ( ) {
81124 return {
82125 ajvOptions : this . _ajvOptions ,
0 commit comments