11import fs from 'node:fs' ;
2+ import os from 'node:os' ;
23import path from 'node:path' ;
34import { fileURLToPath } from 'node:url' ;
45
@@ -11,6 +12,10 @@ const __dirname = path.dirname(__filename);
1112const getSnapshotsPath = ( ) => path . join ( __dirname , '__snapshots__' ) ;
1213const getTempSnapshotsPath = ( ) => path . join ( __dirname , '.gen' , 'snapshots' ) ;
1314
15+ const writeJsonFile = ( filePath : string , value : unknown ) => {
16+ fs . writeFileSync ( filePath , JSON . stringify ( value , null , 2 ) ) ;
17+ } ;
18+
1419/**
1520 * Helper function to compare a bundled schema with a snapshot file.
1621 * Handles writing the schema to a temp file and comparing with the snapshot.
@@ -46,6 +51,176 @@ describe('bundle', () => {
4651 await expectBundledSchemaToMatchSnapshot ( schema , 'circular-ref-with-description.json' ) ;
4752 } ) ;
4853
54+ it ( 'emits decoded internal refs for generic component names' , async ( ) => {
55+ const tempDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'json-schema-ref-parser-' ) ) ;
56+
57+ try {
58+ const rootPath = path . join ( tempDir , 'root.json' ) ;
59+
60+ writeJsonFile ( rootPath , {
61+ components : {
62+ schemas : {
63+ ClientResponse : {
64+ properties : {
65+ id : {
66+ type : 'string' ,
67+ } ,
68+ } ,
69+ type : 'object' ,
70+ } ,
71+ 'PaginatedListItems<ClientResponse>' : {
72+ properties : {
73+ items : {
74+ items : {
75+ $ref : '#/components/schemas/ClientResponse' ,
76+ } ,
77+ type : 'array' ,
78+ } ,
79+ } ,
80+ type : 'object' ,
81+ } ,
82+ } ,
83+ } ,
84+ info : {
85+ title : 'Test API' ,
86+ version : '1.0.0' ,
87+ } ,
88+ openapi : '3.0.0' ,
89+ paths : {
90+ '/clients' : {
91+ get : {
92+ responses : {
93+ '200' : {
94+ content : {
95+ 'application/json' : {
96+ schema : {
97+ $ref : '#/components/schemas/PaginatedListItems<ClientResponse>' ,
98+ } ,
99+ } ,
100+ } ,
101+ description : 'ok' ,
102+ } ,
103+ } ,
104+ } ,
105+ } ,
106+ } ,
107+ } ) ;
108+
109+ const refParser = new $RefParser ( ) ;
110+ const schema = ( await refParser . bundle ( { pathOrUrlOrSchema : rootPath } ) ) as any ;
111+
112+ expect (
113+ schema . paths [ '/clients' ] . get . responses [ '200' ] . content [ 'application/json' ] . schema . $ref ,
114+ ) . toBe ( '#/components/schemas/PaginatedListItems<ClientResponse>' ) ;
115+
116+ const bundledJson = JSON . stringify ( schema ) ;
117+ expect ( bundledJson ) . not . toContain ( 'PaginatedListItems%3CClientResponse%3E' ) ;
118+ } finally {
119+ fs . rmSync ( tempDir , { force : true , recursive : true } ) ;
120+ }
121+ } ) ;
122+
123+ it ( 'emits decoded refs for external schemas with generic and unicode names' , async ( ) => {
124+ const tempDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'json-schema-ref-parser-' ) ) ;
125+
126+ try {
127+ const externalPath = path . join ( tempDir , 'external.json' ) ;
128+ const rootPath = path . join ( tempDir , 'root.json' ) ;
129+
130+ writeJsonFile ( externalPath , {
131+ components : {
132+ schemas : {
133+ 'PaginatedList<ClientItem>' : {
134+ description : 'generic schema' ,
135+ properties : {
136+ next : {
137+ $ref : '#/components/schemas/PaginatedList<ClientItem>' ,
138+ } ,
139+ } ,
140+ type : 'object' ,
141+ } ,
142+ Überschrift : {
143+ description : 'unicode schema' ,
144+ properties : {
145+ next : {
146+ $ref : '#/components/schemas/Überschrift' ,
147+ } ,
148+ } ,
149+ type : 'object' ,
150+ } ,
151+ } ,
152+ } ,
153+ } ) ;
154+
155+ writeJsonFile ( rootPath , {
156+ info : {
157+ title : 'Test API' ,
158+ version : '1.0.0' ,
159+ } ,
160+ openapi : '3.0.0' ,
161+ paths : {
162+ '/generic' : {
163+ get : {
164+ responses : {
165+ '200' : {
166+ content : {
167+ 'application/json' : {
168+ schema : {
169+ $ref : 'external.json#/components/schemas/PaginatedList<ClientItem>' ,
170+ } ,
171+ } ,
172+ } ,
173+ description : 'ok' ,
174+ } ,
175+ } ,
176+ } ,
177+ } ,
178+ '/unicode' : {
179+ get : {
180+ responses : {
181+ '200' : {
182+ content : {
183+ 'application/json' : {
184+ schema : {
185+ $ref : 'external.json#/components/schemas/Überschrift' ,
186+ } ,
187+ } ,
188+ } ,
189+ description : 'ok' ,
190+ } ,
191+ } ,
192+ } ,
193+ } ,
194+ } ,
195+ } ) ;
196+
197+ const refParser = new $RefParser ( ) ;
198+ const schema = ( await refParser . bundle ( { pathOrUrlOrSchema : rootPath } ) ) as any ;
199+ const schemas = schema . components . schemas as Record < string , any > ;
200+
201+ const findSchemaByDescription = ( description : string ) =>
202+ Object . entries ( schemas ) . find ( ( [ , value ] ) => value . description === description ) ;
203+
204+ const genericSchema = findSchemaByDescription ( 'generic schema' ) ;
205+ const unicodeSchema = findSchemaByDescription ( 'unicode schema' ) ;
206+
207+ expect ( genericSchema ) . toBeDefined ( ) ;
208+ expect ( unicodeSchema ) . toBeDefined ( ) ;
209+
210+ const [ genericName , genericValue ] = genericSchema ! ;
211+ const [ unicodeName , unicodeValue ] = unicodeSchema ! ;
212+
213+ expect ( genericValue . properties . next . $ref ) . toBe ( `#/components/schemas/${ genericName } ` ) ;
214+ expect ( unicodeValue . properties . next . $ref ) . toBe ( `#/components/schemas/${ unicodeName } ` ) ;
215+
216+ const bundledJson = JSON . stringify ( schema ) ;
217+ expect ( bundledJson ) . not . toContain ( 'PaginatedList%3CClientItem%3E' ) ;
218+ expect ( bundledJson ) . not . toContain ( '%C3%9Cberschrift' ) ;
219+ } finally {
220+ fs . rmSync ( tempDir , { force : true , recursive : true } ) ;
221+ }
222+ } ) ;
223+
49224 it ( 'bundles multiple references to the same file correctly' , async ( ) => {
50225 const refParser = new $RefParser ( ) ;
51226 const pathOrUrlOrSchema = path . join (
0 commit comments