@@ -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,79 @@ export class Swagger {
137165 this . #tag = tag ;
138166 }
139167
140- async #getData( ) {
141- if ( ! this . #data) {
168+ /**
169+ * Content of swagger file, either loaded from `#path` or passed in via `options`.
170+ *
171+ * @returns {Promise<string> }
172+ * @throws {SpecModelError }
173+ */
174+ async #getContent( ) {
175+ if ( this . #content === undefined ) {
142176 const path = this . #path;
143177
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- ) ) ;
178+ this . #content = await this . #wrapError(
179+ async ( ) => await readFile ( path , { encoding : "utf8" } ) ,
180+ "Failed to read file for swagger" ,
181+ ) ;
182+ }
150183
151- /** @type { Map<string, Operation> } */
152- const operations = new Map ( ) ;
184+ return this . #content ;
185+ }
153186
154- const swaggerJson = await this . #wrapError(
187+ /**
188+ * @returns {Promise<unknown> } Content of swagger file, represented as an untyped JSON object
189+ * @throws {SpecModelError }
190+ */
191+ async #getContentJSON( ) {
192+ if ( this . #contentJSON === undefined ) {
193+ const content = await this . #getContent( ) ;
194+
195+ this . #contentJSON = await this . #wrapError(
155196 ( ) => /** @type {unknown } */ ( JSON . parse ( content ) ) ,
156197 "Failed to parse JSON for swagger" ,
157198 ) ;
199+ }
200+
201+ return this . #contentJSON;
202+ }
203+
204+ /**
205+ * @returns {Promise<SwaggerObject> } Content of swagger file, represented as a typed object
206+ * @throws {SpecModelError }
207+ */
208+ async #getContentObject( ) {
209+ if ( this . #contentObject === undefined ) {
210+ const contentJSON = await this . #getContentJSON( ) ;
158211
159- /** @type {SwaggerObject } */
160- const swagger = await this . #wrapError(
161- ( ) => swaggerSchema . parse ( swaggerJson ) ,
212+ this . #contentObject = await this . #wrapError(
213+ ( ) => swaggerSchema . parse ( contentJSON ) ,
162214 "Failed to parse schema for swagger" ,
163215 ) ;
216+ }
164217
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- }
218+ return this . #contentObject;
219+ }
171220
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- }
221+ /**
222+ * @returns {Promise<Map<string, Swagger>> } Map of swaggers referenced from this swagger, using `path` as key
223+ */
224+ async getRefs ( ) {
225+ const allRefs = await this . #getRefs( ) ;
226+
227+ // filter out any paths that are examples
228+ const filtered = new Map ( [ ...allRefs ] . filter ( ( [ path ] ) => ! example ( path ) ) ) ;
229+
230+ return filtered ;
231+ }
232+
233+ async #getRefs( ) {
234+ if ( this . #refs === undefined ) {
235+ const path = this . #path;
236+ const contentJSON = await this . #getContentJSON( ) ;
178237
179238 const schema = await this . #wrapError(
180239 async ( ) =>
181- await $RefParser . resolve ( this . # path, swaggerJson , {
240+ await $RefParser . resolve ( path , contentJSON , {
182241 resolve : { file : excludeExamples , http : false } ,
183242 } ) ,
184243 "Failed to resolve file for swagger" ,
@@ -189,7 +248,7 @@ export class Swagger {
189248 // Exclude ourself
190249 . filter ( ( p ) => resolve ( p ) !== resolve ( this . #path) ) ;
191250
192- const refs = new Map (
251+ this . # refs = new Map (
193252 refPaths . map ( ( p ) => {
194253 const swagger = new Swagger ( p , {
195254 logger : this . #logger,
@@ -198,49 +257,56 @@ export class Swagger {
198257 return [ swagger . path , swagger ] ;
199258 } ) ,
200259 ) ;
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 ;
206260 }
207261
208- return this . #data ;
262+ return this . #refs ;
209263 }
210264
211265 /**
212- * @returns {Promise<Map<string, Swagger>> } Map of swaggers referenced from this swagger, using `path` as key
266+ * @returns {Promise<Map<string, Swagger>> } Map of examples referenced from this swagger, using `path` as key
213267 */
214- async getRefs ( ) {
268+ async getExamples ( ) {
215269 const allRefs = await this . #getRefs( ) ;
216270
217271 // filter out any paths that are examples
218- const filtered = new Map ( [ ...allRefs ] . filter ( ( [ path ] ) => ! example ( path ) ) ) ;
272+ const filtered = new Map ( [ ...allRefs ] . filter ( ( [ path ] ) => example ( path ) ) ) ;
219273
220274 return filtered ;
221275 }
222276
223- async #getRefs( ) {
224- return ( await this . #getData( ) ) . refs ;
225- }
226-
227277 /**
228- * @returns {Promise<Map<string, Swagger >> } Map of examples referenced from this swagger, using `path ` as key
278+ * @returns {Promise<Map<string, Operation >> } Map of the operations in this swagger, using `operationId ` as key
229279 */
230- async getExamples ( ) {
231- const allRefs = await this . #getRefs( ) ;
280+ async getOperations ( ) {
281+ if ( this . #operations === undefined ) {
282+ const contentObject = await this . #getContentObject( ) ;
232283
233- // filter out any paths that are examples
234- const filtered = new Map ( [ ...allRefs ] . filter ( ( [ path ] ) => example ( path ) ) ) ;
284+ this . #operations = new Map ( ) ;
235285
236- return filtered ;
286+ // Process regular paths
287+ if ( contentObject . paths ) {
288+ for ( const [ path , pathObject ] of Object . entries ( contentObject . paths ) ) {
289+ this . #addOperations( this . #operations, path , pathObject ) ;
290+ }
291+ }
292+
293+ // Process x-ms-paths (Azure extension)
294+ if ( contentObject [ "x-ms-paths" ] ) {
295+ for ( const [ path , pathObject ] of Object . entries ( contentObject [ "x-ms-paths" ] ) ) {
296+ this . #addOperations( this . #operations, path , pathObject ) ;
297+ }
298+ }
299+ }
300+
301+ return this . #operations;
237302 }
238303
239304 /**
240- * @returns {Promise<Map<string, Operation>> } Map of the operations in this swagger, using `operationId` as key
305+ * @returns {Promise<boolean> } True if the spec was generated from TypeSpec
241306 */
242- async getOperations ( ) {
243- return ( await this . #getData( ) ) . operations ;
307+ async getTypeSpecGenerated ( ) {
308+ const contentObject = await this . #getContentObject( ) ;
309+ return contentObject . info ?. [ "x-typespec-generated" ] !== undefined ;
244310 }
245311
246312 /**
0 commit comments