Repository: BRIKEV/express-jsdoc-swagger (js)

Issue:
[Enhancement] Event emitter feature
We want to expose our package as an event emitter so the user can receive events of what is happening in our parse functions.

This will be very useful for the user as we allow them to know what has been processed or not, the final SwaggerObject, or if there was an error.

We started this development in this [Branch](https://github.com/BRIKEV/express-jsdoc-swagger/tree/event-emitter) but it needs to be updated.

The API of this emitter can be something like this.

```js
const swaggerGenerator = require('express-jsdoc-swagger');
const express = require('express');

const options = {
  info: {
    version: '1.0.0',
    title: 'Albums store',
    license: {
      name: 'MIT',
    },
  },
  file: './simple.js',
  baseDir: __dirname,
};

const app = express();
const generator = swaggerGenerator(app)(options);

// Event emitter API
generator.on('error', error => {
  // user can handle errors
});

generator.on('process', ({ entity, swaggerObject }) => {
  // user can handle swaggerObject status and which entity was evaluated
});

generator.on('finish', swaggerObject => {
  // user can handle see swaggerObject final result
});
```



Pull request description:
Event emitter
### What kind of change does this PR introduce? (check at least one)
 - [ ] Bugfix
 - [X] Feature
 - [X] Test
 - [X] Docs
 - [ ] Refactor
 - [ ] Build-related changes
 - [ ] Other, please describe:

### Description:
This PR includes event emitter feature and closes #33 


Patch:
diff --git a/examples/README.md b/examples/README.md
index 34600b4..23a6492 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -9,6 +9,7 @@ This page will includes some examples on how to use this package.
 - [parameters](https://github.com/BRIKEV/express-jsdoc-swagger/tree/master/examples/parameters)
 - [security](https://github.com/BRIKEV/express-jsdoc-swagger/tree/master/examples/security)
 - [tags](https://github.com/BRIKEV/express-jsdoc-swagger/tree/master/examples/tags)
+- [Event Emitter example](https://github.com/BRIKEV/express-jsdoc-swagger/tree/master/examples/eventEmitter)
 - albumAPI - WIP
 
 
diff --git a/examples/eventEmitter/index.js b/examples/eventEmitter/index.js
new file mode 100644
index 0000000..a86d22b
--- /dev/null
+++ b/examples/eventEmitter/index.js
@@ -0,0 +1,39 @@
+const express = require('express');
+
+const logger = require('../utils/logger');
+const expressJSDocSwagger = require('../..');
+
+const options = {
+  info: {
+    version: '1.0.0',
+    title: 'Albums store',
+    license: {
+      name: 'MIT',
+    },
+  },
+  file: './simple.js',
+  baseDir: __dirname,
+};
+
+const app = express();
+const port = 3000;
+
+const listener = expressJSDocSwagger(app)(options);
+
+// Event emitter API
+listener.on('error', error => {
+  logger.error(`Error: ${error}`);
+});
+
+listener.on('process', ({ entity, swaggerObject }) => {
+  logger.info(`entity: ${entity}`);
+  logger.info('swaggerObject');
+  logger.info(swaggerObject);
+});
+
+listener.on('finish', swaggerObject => {
+  logger.info('Finish');
+  logger.info(swaggerObject);
+});
+
+app.listen(port, () => logger.info(`Example app listening at http://localhost:${port}`));
diff --git a/examples/package.json b/examples/package.json
index 756577e..7493620 100644
--- a/examples/package.json
+++ b/examples/package.json
@@ -17,7 +17,8 @@
     "requestBody:components": "node requestBody/components.js",
     "security:basic-auth": "node security/basic-auth.js",
     "components:simple": "node components/simple.js",
-    "tags:simple": "node tags/simple.js"
+    "tags:simple": "node tags/simple.js",
+    "eventEmitter": "node eventEmitter/index.js"
   },
   "keywords": [],
   "author": "",
diff --git a/index.js b/index.js
index 56da09b..18046ed 100644
--- a/index.js
+++ b/index.js
@@ -1,22 +1,28 @@
 const swaggerUi = require('swagger-ui-express');
-const debug = require('debug')('express-jsdoc-swagger');
+const swaggerEvents = require('./swaggerEvents');
+
 const processSwagger = require('./processSwagger');
 
-const expressJSDocSwagger = app => (
-  options => {
-    debug(`Retrieving ${JSON.stringify(options)}`);
+let instance = null;
+
+const expressJSDocSwagger = app => {
+  if (instance) return () => instance;
+  return options => {
+    const events = swaggerEvents();
+    instance = events.instance;
     let swaggerObject = {};
 
-    processSwagger(options)
+    processSwagger(options, events.processFile)
       .then(result => {
         swaggerObject = {
           ...swaggerObject,
           ...result,
         };
-      });
+        events.finish(swaggerObject);
+      })
+      .catch(events.error);
 
     app.use('/api-docs', (req, res, next) => {
-      debug(`Render express endpoint with SwaggerObject: ${JSON.stringify(swaggerObject)}`);
       swaggerObject = {
         ...swaggerObject,
         host: req.get('host'),
@@ -25,8 +31,8 @@ const expressJSDocSwagger = app => (
       next();
     }, swaggerUi.serve, swaggerUi.setup());
 
-    return swaggerObject;
-  }
-);
+    return instance;
+  };
+};
 
 module.exports = expressJSDocSwagger;
diff --git a/processSwagger.js b/processSwagger.js
index 2427058..773ec28 100644
--- a/processSwagger.js
+++ b/processSwagger.js
@@ -1,4 +1,3 @@
-const debug = require('debug')('express-jsdoc-swagger:processSwagger');
 const readFiles = require('./consumers/readFiles');
 const globFilesMatches = require('./consumers/globFilesMatches');
 const getOnlyComments = require('./consumers/getOnlyComments');
@@ -11,7 +10,9 @@ const {
   getTags,
 } = require('./transforms');
 
-const processSwagger = options => {
+const defaultLogger = () => null;
+
+const processSwagger = (options, logger = defaultLogger) => {
   let swaggerObject = {
     openapi: '3.0.0',
     info: options.info,
@@ -19,10 +20,10 @@ const processSwagger = options => {
     security: options.security,
   };
 
-  debug('Getting basic swagger info');
   swaggerObject = getBasicInfo(swaggerObject);
+  logger({ entity: 'basicInfo', swaggerObject });
   swaggerObject = getSecuritySchemes(swaggerObject);
-  debug(`SwaggerObject with basic info ${JSON.stringify(swaggerObject)}`);
+  logger({ entity: 'securitySchemas', swaggerObject });
 
   return globFilesMatches(options.baseDir, options.file)
     .then(readFiles)
@@ -30,8 +31,11 @@ const processSwagger = options => {
     .then(jsdocInfo())
     .then(data => {
       swaggerObject = getPaths(swaggerObject, data);
+      logger({ entity: 'paths', swaggerObject });
       swaggerObject = getComponents(swaggerObject, data);
+      logger({ entity: 'components', swaggerObject });
       swaggerObject = getTags(swaggerObject, data);
+      logger({ entity: 'tags', swaggerObject });
       return swaggerObject;
     });
 };
diff --git a/swaggerEvents.js b/swaggerEvents.js
new file mode 100644
index 0000000..ba1e347
--- /dev/null
+++ b/swaggerEvents.js
@@ -0,0 +1,35 @@
+const { EventEmitter } = require('events');
+
+const ERROR_EVENT_NAME = 'error';
+const PROCESS_EVENT_NAME = 'process';
+const FINISH_EVENT_NAME = 'finish';
+
+let api = null;
+
+const error = eventEmitter => errorInfo => (
+  eventEmitter.emit(ERROR_EVENT_NAME, errorInfo)
+);
+
+const processFile = eventEmitter => info => (
+  eventEmitter.emit(PROCESS_EVENT_NAME, info)
+);
+
+const finish = eventEmitter => info => (
+  eventEmitter.emit(FINISH_EVENT_NAME, info)
+);
+
+const swaggerEvents = () => {
+  if (api) return api;
+  const instance = new EventEmitter();
+
+  api = {
+    instance,
+    error: error(instance),
+    processFile: processFile(instance),
+    finish: finish(instance),
+  };
+
+  return api;
+};
+
+module.exports = swaggerEvents;


Test patch:
diff --git a/test/e2e/logger/logger.test.js b/test/e2e/logger/logger.test.js
new file mode 100644
index 0000000..5afa0ee
--- /dev/null
+++ b/test/e2e/logger/logger.test.js
@@ -0,0 +1,87 @@
+const processSwagger = require('../../../processSwagger');
+
+const options = {
+  info: {
+    version: '1.0.0',
+    title: 'Albums store',
+    license: {
+      name: 'MIT',
+      url: 'http://example.com',
+    },
+    description: 'API desctiption',
+    contact: {
+      name: 'contact name',
+      url: 'http://example.com',
+      email: 'test@test.com',
+    },
+    termsOfService: 'http://example.com',
+  },
+  file: './main.js',
+  baseDir: __dirname,
+};
+
+test('should called logger method', async () => {
+  const spy = jest.fn();
+  const expected = {
+    openapi: '3.0.0',
+    info: {
+      version: '1.0.0',
+      title: 'Albums store',
+      license: {
+        name: 'MIT',
+        url: 'http://example.com',
+      },
+      description: 'API desctiption',
+      contact: {
+        name: 'contact name',
+        url: 'http://example.com',
+        email: 'test@test.com',
+      },
+      termsOfService: 'http://example.com',
+    },
+    servers: [],
+    security: undefined,
+  };
+  await processSwagger(options, spy);
+  expect(spy).toHaveBeenCalledTimes(5);
+  expect(spy).toHaveBeenNthCalledWith(1, {
+    entity: 'basicInfo',
+    swaggerObject: expected,
+  });
+  expect(spy).toHaveBeenNthCalledWith(2, {
+    entity: 'securitySchemas',
+    swaggerObject: {
+      ...expected,
+      components: {},
+    },
+  });
+  expect(spy).toHaveBeenNthCalledWith(3, {
+    entity: 'paths',
+    swaggerObject: {
+      ...expected,
+      components: {},
+      paths: {},
+    },
+  });
+  expect(spy).toHaveBeenNthCalledWith(4, {
+    entity: 'components',
+    swaggerObject: {
+      ...expected,
+      components: {
+        schemas: {},
+      },
+      paths: {},
+    },
+  });
+  expect(spy).toHaveBeenNthCalledWith(5, {
+    entity: 'tags',
+    swaggerObject: {
+      ...expected,
+      components: {
+        schemas: {},
+      },
+      paths: {},
+      tags: [],
+    },
+  });
+});
diff --git a/test/events/events.test.js b/test/events/events.test.js
new file mode 100644
index 0000000..ac7063e
--- /dev/null
+++ b/test/events/events.test.js
@@ -0,0 +1,48 @@
+const swaggerEvents = require('../../swaggerEvents');
+
+describe('swaggerEvents event emitter', () => {
+  it('should return one instance', () => {
+    const instance = swaggerEvents();
+    const newInstance = swaggerEvents();
+    expect(instance).toEqual(newInstance);
+  });
+
+  it('should handle error events', done => {
+    const events = swaggerEvents();
+
+    const error = 'Example error';
+
+    events.instance.on('error', errorInfo => {
+      expect(errorInfo).toEqual(error);
+      done();
+    });
+
+    events.error(error);
+  });
+
+  it('should handle process info events', done => {
+    const events = swaggerEvents();
+
+    const message = 'Example message';
+
+    events.instance.on('process', errorInfo => {
+      expect(errorInfo).toEqual(message);
+      done();
+    });
+
+    events.processFile(message);
+  });
+
+  it('should handle finish info events', done => {
+    const events = swaggerEvents();
+
+    const message = 'Example message';
+
+    events.instance.on('finish', errorInfo => {
+      expect(errorInfo).toEqual(message);
+      done();
+    });
+
+    events.finish(message);
+  });
+});