@@ -28,6 +28,13 @@ import { embedError } from "./spec-model.js";
2828 * @property {Object[] } [refs]
2929 */
3030
31+ const infoSchema = z . object ( {
32+ "x-typespec-generated" : z . array ( z . object ( { emitter : z . string ( ) . optional ( ) } ) ) . optional ( ) ,
33+ } ) ;
34+ /**
35+ * @typedef {import("zod").infer<typeof infoSchema> } InfoObject
36+ */
37+
3138// https://swagger.io/specification/v2/#operation-object
3239const operationSchema = z . object ( { operationId : z . string ( ) . optional ( ) } ) ;
3340/**
@@ -53,6 +60,7 @@ const pathsSchema = z.record(z.string(), pathSchema);
5360
5461// https://swagger.io/specification/v2/#swagger-object
5562const swaggerSchema = z . object ( {
63+ info : infoSchema . optional ( ) ,
5664 paths : pathsSchema . optional ( ) ,
5765 "x-ms-paths" : pathsSchema . optional ( ) ,
5866} ) ;
@@ -100,22 +108,42 @@ export class Swagger {
100108 /**
101109 * Content of swagger file, either loaded from `#path` or passed in via `options`.
102110 *
103- * Reset to `undefined` after `#data` is loaded to save memory.
104- *
105111 * @type {string | undefined }
106112 */
107113 #content;
108114
109- // operations: Map of the operations in this swagger, using `operationId` as key
110- /** @type {{operations: Map<string, Operation>, refs: Map<string, Swagger>} | undefined } */
111- #data;
115+ /**
116+ * Content of swagger file, represented as an untyped JSON object
117+ *
118+ * @type {unknown | undefined }
119+ */
120+ #contentJSON;
121+
122+ /**
123+ * Content of swagger file, represented as a typed object
124+ *
125+ * @type {SwaggerObject | undefined }
126+ * */
127+ #contentObject;
112128
113129 /** @type {import('./logger.js').ILogger | undefined } */
114130 #logger;
115131
132+ /**
133+ * Map of the operations in this swagger, using `operationId` as key
134+ *
135+ * @type {Map<string, Operation> | undefined }
136+ */
137+ #operations;
138+
116139 /** @type {string } absolute path */
117140 #path;
118141
142+ /**
143+ * @type {Map<string, Swagger> | undefined }
144+ */
145+ #refs;
146+
119147 /** @type {Tag | undefined } Tag that contains this Swagger */
120148 #tag;
121149
@@ -137,48 +165,77 @@ export class Swagger {
137165 this . #tag = tag ;
138166 }
139167
140- async #getData( ) {
141- if ( ! this . #data) {
168+ /**
169+ * @returns {Promise<string> } Content of swagger file, represented as a string, either loaded from `#path` or passed in via `options`
170+ * @throws {SpecModelError }
171+ */
172+ async #getContent( ) {
173+ if ( this . #content === undefined ) {
142174 const path = this . #path;
143175
144- const content =
145- this . #content ??
146- ( await this . #wrapError(
147- async ( ) => await readFile ( path , { encoding : "utf8" } ) ,
148- "Failed to read file for swagger" ,
149- ) ) ;
176+ this . #content = await this . #wrapError(
177+ async ( ) => await readFile ( path , { encoding : "utf8" } ) ,
178+ "Failed to read file for swagger" ,
179+ ) ;
180+ }
181+
182+ return this . #content;
183+ }
150184
151- /** @type {Map<string, Operation> } */
152- const operations = new Map ( ) ;
185+ /**
186+ * @returns {Promise<unknown> } Content of swagger file, represented as an untyped JSON object
187+ * @throws {SpecModelError }
188+ */
189+ async #getContentJSON( ) {
190+ if ( this . #contentJSON === undefined ) {
191+ const content = await this . #getContent( ) ;
153192
154- const swaggerJson = await this . #wrapError(
193+ this . #contentJSON = await this . #wrapError(
155194 ( ) => /** @type {unknown } */ ( JSON . parse ( content ) ) ,
156195 "Failed to parse JSON for swagger" ,
157196 ) ;
197+ }
198+
199+ return this . #contentJSON;
200+ }
201+
202+ /**
203+ * @returns {Promise<SwaggerObject> } Content of swagger file, represented as a typed object
204+ * @throws {SpecModelError }
205+ */
206+ async #getContentObject( ) {
207+ if ( this . #contentObject === undefined ) {
208+ const contentJSON = await this . #getContentJSON( ) ;
158209
159- /** @type {SwaggerObject } */
160- const swagger = await this . #wrapError(
161- ( ) => swaggerSchema . parse ( swaggerJson ) ,
210+ this . #contentObject = await this . #wrapError(
211+ ( ) => swaggerSchema . parse ( contentJSON ) ,
162212 "Failed to parse schema for swagger" ,
163213 ) ;
214+ }
164215
165- // Process regular paths
166- if ( swagger . paths ) {
167- for ( const [ path , pathObject ] of Object . entries ( swagger . paths ) ) {
168- this . #addOperations( operations , path , pathObject ) ;
169- }
170- }
216+ return this . #contentObject;
217+ }
171218
172- // Process x-ms-paths (Azure extension)
173- if ( swagger [ "x-ms-paths" ] ) {
174- for ( const [ path , pathObject ] of Object . entries ( swagger [ "x-ms-paths" ] ) ) {
175- this . #addOperations( operations , path , pathObject ) ;
176- }
177- }
219+ /**
220+ * @returns {Promise<Map<string, Swagger>> } Map of swaggers referenced from this swagger, using `path` as key
221+ */
222+ async getRefs ( ) {
223+ const allRefs = await this . #getRefs( ) ;
224+
225+ // filter out any paths that are examples
226+ const filtered = new Map ( [ ...allRefs ] . filter ( ( [ path ] ) => ! example ( path ) ) ) ;
227+
228+ return filtered ;
229+ }
230+
231+ async #getRefs( ) {
232+ if ( this . #refs === undefined ) {
233+ const path = this . #path;
234+ const contentJSON = await this . #getContentJSON( ) ;
178235
179236 const schema = await this . #wrapError(
180237 async ( ) =>
181- await $RefParser . resolve ( this . # path, swaggerJson , {
238+ await $RefParser . resolve ( path , contentJSON , {
182239 resolve : { file : excludeExamples , http : false } ,
183240 } ) ,
184241 "Failed to resolve file for swagger" ,
@@ -189,7 +246,7 @@ export class Swagger {
189246 // Exclude ourself
190247 . filter ( ( p ) => resolve ( p ) !== resolve ( this . #path) ) ;
191248
192- const refs = new Map (
249+ this . # refs = new Map (
193250 refPaths . map ( ( p ) => {
194251 const swagger = new Swagger ( p , {
195252 logger : this . #logger,
@@ -198,49 +255,56 @@ export class Swagger {
198255 return [ swagger . path , swagger ] ;
199256 } ) ,
200257 ) ;
201-
202- this . #data = { operations, refs } ;
203-
204- // Clear #content to save memory, since it's no longer needed after #data is loaded
205- this . #content = undefined ;
206258 }
207259
208- return this . #data ;
260+ return this . #refs ;
209261 }
210262
211263 /**
212- * @returns {Promise<Map<string, Swagger>> } Map of swaggers referenced from this swagger, using `path` as key
264+ * @returns {Promise<Map<string, Swagger>> } Map of examples referenced from this swagger, using `path` as key
213265 */
214- async getRefs ( ) {
266+ async getExamples ( ) {
215267 const allRefs = await this . #getRefs( ) ;
216268
217269 // filter out any paths that are examples
218- const filtered = new Map ( [ ...allRefs ] . filter ( ( [ path ] ) => ! example ( path ) ) ) ;
270+ const filtered = new Map ( [ ...allRefs ] . filter ( ( [ path ] ) => example ( path ) ) ) ;
219271
220272 return filtered ;
221273 }
222274
223- async #getRefs( ) {
224- return ( await this . #getData( ) ) . refs ;
225- }
226-
227275 /**
228- * @returns {Promise<Map<string, Swagger >> } Map of examples referenced from this swagger, using `path ` as key
276+ * @returns {Promise<Map<string, Operation >> } Map of the operations in this swagger, using `operationId ` as key
229277 */
230- async getExamples ( ) {
231- const allRefs = await this . #getRefs( ) ;
278+ async getOperations ( ) {
279+ if ( this . #operations === undefined ) {
280+ const contentObject = await this . #getContentObject( ) ;
232281
233- // filter out any paths that are examples
234- const filtered = new Map ( [ ...allRefs ] . filter ( ( [ path ] ) => example ( path ) ) ) ;
282+ this . #operations = new Map ( ) ;
235283
236- return filtered ;
284+ // Process regular paths
285+ if ( contentObject . paths ) {
286+ for ( const [ path , pathObject ] of Object . entries ( contentObject . paths ) ) {
287+ this . #addOperations( this . #operations, path , pathObject ) ;
288+ }
289+ }
290+
291+ // Process x-ms-paths (Azure extension)
292+ if ( contentObject [ "x-ms-paths" ] ) {
293+ for ( const [ path , pathObject ] of Object . entries ( contentObject [ "x-ms-paths" ] ) ) {
294+ this . #addOperations( this . #operations, path , pathObject ) ;
295+ }
296+ }
297+ }
298+
299+ return this . #operations;
237300 }
238301
239302 /**
240- * @returns {Promise<Map<string, Operation>> } Map of the operations in this swagger, using `operationId` as key
303+ * @returns {Promise<boolean> } True if the spec was generated from TypeSpec
241304 */
242- async getOperations ( ) {
243- return ( await this . #getData( ) ) . operations ;
305+ async getTypeSpecGenerated ( ) {
306+ const contentObject = await this . #getContentObject( ) ;
307+ return contentObject . info ?. [ "x-typespec-generated" ] !== undefined ;
244308 }
245309
246310 /**
0 commit comments