@@ -176,148 +176,6 @@ let _ = require('lodash'),
176176 return tree ;
177177 } ,
178178
179- // _generateTreeFromPaths = function (openapi, { includeDeprecated }) {
180- // /**
181- // * We will create a unidirectional graph
182- // */
183- // let tree = new Graph();
184-
185- // tree.setNode('root:collection', {
186- // type: 'collection',
187- // data: {},
188- // meta: {}
189- // });
190-
191- // _.forEach(openapi.paths, function (methods, path) {
192- // let pathSplit = path === '/' ? [path] : _.compact(path.split('/'));
193-
194- // // if after path split we just have one entry
195- // // that means no folders need to be generated.
196- // // check for all the methods inside it and expand.
197- // if (pathSplit.length === 1) {
198- // /**
199- // * Always first try to find the node if it already exists.
200- // * if yes, bail out nothing is needed to be done.
201- // *
202- // * if the path length is 1, then also generate
203- // * the folder otherwise /pet and /pet/:id will never be in same folder.
204- // */
205- // // if (!tree.hasNode(`path:${pathSplit[0]}`)) {
206- // // tree.setNode(`path:${pathSplit[0]}`, {
207- // // type: 'folder',
208- // // meta: {
209- // // name: pathSplit[0],
210- // // path: pathSplit[0],
211- // // pathIdentifier: pathIdentifier
212- // // },
213- // // data: {}
214- // // });
215-
216- // // tree.setEdge('root:collection', `path:${pathSplit[0]}`);
217- // // }
218-
219-
220- // _.forEach(methods, function (data, method) {
221- // if (!ALLOWED_HTTP_METHODS[method]) {
222- // return;
223- // }
224-
225- // /**
226- // * include deprecated handling.
227- // * If true, add in the postman collection. If false ignore the request.
228- // */
229- // if (!includeDeprecated && data.deprecated) {
230- // return;
231- // }
232-
233- // tree.setNode(`path:${pathSplit[0]}:${method}`, {
234- // type: 'request',
235- // meta: {
236- // path: path,
237- // method: method,
238- // pathIdentifier: pathSplit[0]
239- // },
240- // data: {}
241- // });
242-
243- // tree.setEdge(`path:${pathSplit[0]}`, `path:${pathSplit[0]}:${method}`);
244- // });
245- // }
246-
247- // else {
248- // _.forEach(pathSplit, function (p, index) {
249- // let previousPathIdentified = pathSplit.slice(0, index).join('/'),
250- // pathIdentifier = pathSplit.slice(0, index + 1).join('/');
251-
252- // /**
253- // * Always first try to find the node if it already exists.
254- // * if yes, bail out nothing is needed to be done.
255- // */
256- // if (tree.hasNode(`path:${pathIdentifier}`)) {
257- // return;
258- // }
259-
260- // else {
261- // tree.setNode(`path:${pathIdentifier}`, {
262- // type: 'folder',
263- // meta: {
264- // name: p,
265- // path: p,
266- // pathIdentifier: pathIdentifier
267- // },
268- // data: {}
269- // });
270-
271- // /**
272- // * If index is 0, this means that we are on the first level.
273- // * Hence it is folder/request to be added on the first level
274- // *
275- // * If after the split we have more than one paths, then we need
276- // * to add to the previous node.
277- // */
278- // tree.setEdge(index === 0 ? 'root:collection' : `path:${previousPathIdentified}`, `path:${pathIdentifier}`);
279- // }
280- // });
281-
282- // /**
283- // * Now for all the methods present in the path, add the request nodes.
284- // */
285-
286- // _.forEach(methods, function (data, method) {
287- // if (!ALLOWED_HTTP_METHODS[method]) {
288- // return;
289- // }
290-
291- // /**
292- // * include deprecated handling.
293- // * If true, add in the postman collection. If false ignore the request.
294- // */
295- // if (!includeDeprecated && data.deprecated) {
296- // return;
297- // }
298-
299- // // join till the last path i.e. the folder.
300- // let previousPathIdentified = pathSplit.slice(0, (pathSplit.length)).join('/'),
301- // pathIdentifier = `${pathSplit.join('/')}:${method}`;
302-
303- // tree.setNode(`path:${pathIdentifier}`, {
304- // type: 'request',
305- // data: {},
306- // meta: {
307- // path: path,
308- // method: method,
309- // pathIdentifier: pathIdentifier
310- // }
311- // });
312-
313- // tree.setEdge(`path:${previousPathIdentified}`, `path:${pathIdentifier}`);
314- // });
315- // }
316- // });
317-
318- // return tree;
319- // },
320-
321179 _generateTreeFromTags = function ( openapi , { includeDeprecated } ) {
322180 let tree = new Graph ( ) ,
323181
@@ -424,6 +282,124 @@ let _ = require('lodash'),
424282 return tree ;
425283 } ,
426284
285+ /**
286+ * Generates tree structure with nested folders based on tag order
287+ * @param {Object } openapi - OpenAPI specification
288+ * @param {Object } options - Generation options
289+ * @param {boolean } options.includeDeprecated - Whether to include deprecated operations
290+ * @returns {Object } - Graph tree with nested folder structure
291+ */
292+ _generateTreeFromNestedTags = function ( openapi , { includeDeprecated } ) {
293+ let tree = new Graph ( ) ,
294+
295+ tagDescMap = _ . reduce ( openapi . tags , function ( acc , data ) {
296+ acc [ data . name ] = data . description ;
297+
298+ return acc ;
299+ } , { } ) ;
300+
301+ tree . setNode ( 'root:collection' , {
302+ type : 'collection' ,
303+ data : { } ,
304+ meta : { }
305+ } ) ;
306+
307+ /**
308+ * Helper function to create nested folder structure for tags
309+ * @param {Array } tags - Array of tags to create nested folders for
310+ * @returns {String } - Node ID of the deepest folder created
311+ */
312+ const createNestedFolders = function ( tags ) {
313+ if ( ! tags || tags . length === 0 ) {
314+ return 'root:collection' ;
315+ }
316+
317+ let parentNodeId = 'root:collection' ;
318+
319+ // Create nested folder structure based on tag order
320+ for ( let i = 0 ; i < tags . length ; i ++ ) {
321+ const tag = tags [ i ] ,
322+ folderPath = tags . slice ( 0 , i + 1 ) . join ( ':' ) ,
323+ currentNodeId = `path:${ folderPath } ` ;
324+
325+ // Create folder node if it doesn't exist
326+ if ( ! tree . hasNode ( currentNodeId ) ) {
327+ tree . setNode ( currentNodeId , {
328+ type : 'folder' ,
329+ meta : {
330+ path : '' ,
331+ name : tag ,
332+ description : tagDescMap [ tag ] || ''
333+ } ,
334+ data : { }
335+ } ) ;
336+
337+ // Connect to parent (either root collection or previous folder)
338+ tree . setEdge ( parentNodeId , currentNodeId ) ;
339+ }
340+
341+ parentNodeId = currentNodeId ;
342+ }
343+
344+ return parentNodeId ;
345+ } ;
346+
347+ _ . forEach ( openapi . paths , function ( methods , path ) {
348+ _ . forEach ( methods , function ( data , method ) {
349+ if ( ! ALLOWED_HTTP_METHODS [ method ] ) {
350+ return ;
351+ }
352+
353+ /**
354+ * include deprecated handling.
355+ * If true, add in the postman collection. If false ignore the request.
356+ */
357+ if ( ! includeDeprecated && data . deprecated ) {
358+ return ;
359+ }
360+
361+ /**
362+ * Create nested folder structure based on tags order
363+ * and place the request in the deepest folder
364+ */
365+ if ( data . tags && data . tags . length > 0 ) {
366+ // Create nested folder structure and get the deepest folder node
367+ const deepestFolderNodeId = createNestedFolders ( data . tags ) ,
368+ // Create a unique request node ID (one per operation)
369+ requestNodeId = `request:${ path } :${ method } ` ;
370+
371+ tree . setNode ( requestNodeId , {
372+ type : 'request' ,
373+ data : { } ,
374+ meta : {
375+ tags : data . tags ,
376+ path : path ,
377+ method : method
378+ }
379+ } ) ;
380+
381+ // Connect request to the deepest folder
382+ tree . setEdge ( deepestFolderNodeId , requestNodeId ) ;
383+ }
384+ else {
385+ // No tags - place request directly under root collection
386+ tree . setNode ( `path:${ path } :${ method } ` , {
387+ type : 'request' ,
388+ data : { } ,
389+ meta : {
390+ path : path ,
391+ method : method
392+ }
393+ } ) ;
394+
395+ tree . setEdge ( 'root:collection' , `path:${ path } :${ method } ` ) ;
396+ }
397+ } ) ;
398+ } ) ;
399+
400+ return tree ;
401+ } ,
402+
427403 _generateWebhookEndpoints = function ( openapi , tree , { includeDeprecated } ) {
428404 if ( ! _ . isEmpty ( openapi . webhooks ) ) {
429405 tree . setNode ( `${ PATH_WEBHOOK } :folder` , {
@@ -470,12 +446,18 @@ let _ = require('lodash'),
470446 *
471447 * @returns {Object } - tree format
472448 */
473- module . exports = function ( openapi , { folderStrategy, includeWebhooks, includeDeprecated } ) {
449+ module . exports = function ( openapi , { folderStrategy, includeWebhooks, includeDeprecated, nestedFolderHierarchy } ) {
474450 let skeletonTree ;
475451
476452 switch ( folderStrategy ) {
477453 case 'tags' :
478- skeletonTree = _generateTreeFromTags ( openapi , { includeDeprecated } ) ;
454+ if ( nestedFolderHierarchy ) {
455+ skeletonTree = _generateTreeFromNestedTags ( openapi , { includeDeprecated } ) ;
456+ }
457+ else {
458+ skeletonTree = _generateTreeFromTags ( openapi , { includeDeprecated } ) ;
459+ }
460+
479461 break ;
480462
481463 case 'paths' :
0 commit comments