Repository: BRIKEV/express-jsdoc-swagger
Pull request #1 (2020-05-24 17:44:16)

Components transform
Added components transform


Patch:
diff --git a/transforms/components/index.js b/transforms/components/index.js
new file mode 100644
index 0000000..27a3d32
--- /dev/null
+++ b/transforms/components/index.js
@@ -0,0 +1,57 @@
+const { getTagInfo, getTagsInfo } = require('../utils/tags');
+
+const REQUIRED = 'required';
+
+const getPropertyName = ({ name: propertyName }) => {
+  const [name] = propertyName.split('.');
+  return name;
+};
+
+const formatProperties = properties => {
+  if (!properties || !Array.isArray(properties)) return {};
+  return properties.reduce((acum, property) => {
+    const name = getPropertyName(property);
+    const [description, format] = property.description.split(' - ');
+    return {
+      ...acum,
+      [name]: {
+        type: property.type.name,
+        description,
+        ...(format ? { format } : {}),
+      },
+    };
+  }, {});
+};
+
+const isPropertyRequired = properties => {
+  if (!properties || !Array.isArray(properties)) return [];
+  return properties.filter(({ name }) => name.includes(REQUIRED)).map(getPropertyName);
+};
+
+const parseSchema = schema => {
+  const typedef = getTagInfo(schema.tags, 'typedef');
+  const propertyValues = getTagsInfo(schema.tags, 'property');
+  return {
+    [typedef.name]: {
+      description: schema.description,
+      required: isPropertyRequired(propertyValues),
+      type: 'object',
+      properties: formatProperties(propertyValues),
+    },
+  };
+};
+
+const parseComponents = (swaggerObject = {}, components = []) => {
+  if (!components || !Array.isArray(components)) return { components: { schemas: {} } };
+  const componentSchema = components.reduce((acum, item) => ({
+    ...acum, ...parseSchema(item),
+  }), {});
+  return {
+    ...swaggerObject,
+    components: {
+      schemas: componentSchema,
+    },
+  };
+};
+
+module.exports = parseComponents;
diff --git a/transforms/paths/index.js b/transforms/paths/index.js
index 509b4a1..fb48d34 100644
--- a/transforms/paths/index.js
+++ b/transforms/paths/index.js
@@ -1,10 +1,7 @@
 const debug = require('debug')('express-jsdoc-swagger:transforms:paths');
 const responsesGenerator = require('./responses');
 const parametersGenerator = require('./parameters');
-
-const getTagInfo = (tags, key) => tags.find(({ title }) => title === key);
-
-const getTagsInfo = (tags, key) => tags.filter(({ title }) => title === key);
+const { getTagInfo, getTagsInfo } = require('../utils/tags');
 
 const parsePath = path => {
   debug(`Transforms path: ${JSON.stringify(path)}`);
diff --git a/transforms/utils/tags.js b/transforms/utils/tags.js
new file mode 100644
index 0000000..4586aa6
--- /dev/null
+++ b/transforms/utils/tags.js
@@ -0,0 +1,8 @@
+const getTagInfo = (tags, key) => tags.find(({ title }) => title === key);
+
+const getTagsInfo = (tags, key) => tags.filter(({ title }) => title === key);
+
+module.exports = {
+  getTagInfo,
+  getTagsInfo,
+};


Test patch:
diff --git a/test/transforms/components/index.test.js b/test/transforms/components/index.test.js
new file mode 100644
index 0000000..c3b4c8e
--- /dev/null
+++ b/test/transforms/components/index.test.js
@@ -0,0 +1,179 @@
+const jsdocInfo = require('../../../consumers/jsdocInfo');
+const parseComponents = require('../../../transforms/components');
+
+describe('parseComponents method', () => {
+  it('should return empty array with not params', () => {
+    const initialState = {};
+    const components = undefined;
+    const expected = {
+      components: {
+        schemas: {},
+      },
+    };
+    const result = parseComponents(initialState, components);
+    expect(result).toEqual(expected);
+  });
+
+  it('should return empty array with not an array as parameter', () => {
+    const initialState = {};
+    const components = 2;
+    const expected = {
+      components: {
+        schemas: {},
+      },
+    };
+    const result = parseComponents(initialState, components);
+    expect(result).toEqual(expected);
+  });
+
+  it('Should parse jsdoc component spec with basic properties', () => {
+    const jsodInput = [`
+      /**
+       * A song
+       * @typedef {object} Song
+       * @property {string} title - The title
+       * @property {string} artist - The artist
+       * @property {number} year - The year
+       */
+    `];
+    const expected = {
+      components: {
+        schemas: {
+          Song: {
+            type: 'object',
+            required: [],
+            description: 'A song',
+            properties: {
+              title: {
+                type: 'string',
+                description: 'The title',
+              },
+              artist: {
+                type: 'string',
+                description: 'The artist',
+              },
+              year: {
+                type: 'number',
+                description: 'The year',
+              },
+            },
+          },
+        },
+      },
+    };
+    const parsedJSDocs = jsdocInfo()(jsodInput);
+    const result = parseComponents({}, parsedJSDocs);
+    expect(result).toEqual(expected);
+  });
+
+  it('Should parse jsdoc component spec with require and format properties', () => {
+    const jsodInput = [`
+      /**
+       * A song
+       * @typedef {object} Song
+       * @property {string} title.required - The title
+       * @property {string} artist - The artist
+       * @property {number} year - The year - int64
+       */
+    `];
+    const expected = {
+      components: {
+        schemas: {
+          Song: {
+            type: 'object',
+            required: [
+              'title',
+            ],
+            description: 'A song',
+            properties: {
+              title: {
+                type: 'string',
+                description: 'The title',
+              },
+              artist: {
+                type: 'string',
+                description: 'The artist',
+              },
+              year: {
+                type: 'number',
+                description: 'The year',
+                format: 'int64',
+              },
+            },
+          },
+        },
+      },
+    };
+    const parsedJSDocs = jsdocInfo()(jsodInput);
+    const result = parseComponents({}, parsedJSDocs);
+    expect(result).toEqual(expected);
+  });
+
+  it('Should parse two jsdoc components', () => {
+    const jsodInput = [`
+      /**
+       * A song
+       * @typedef {object} Song
+       * @property {string} title.required - The title
+       * @property {string} artist - The artist
+       * @property {number} year - The year - int64
+       */
+    `,
+    `
+      /**
+       * Album
+       * @typedef {object} Album
+       * @property {string} name.required - Album name
+       * @property {number} length
+       */
+    `];
+    const expected = {
+      components: {
+        schemas: {
+          Song: {
+            type: 'object',
+            required: [
+              'title',
+            ],
+            description: 'A song',
+            properties: {
+              title: {
+                type: 'string',
+                description: 'The title',
+              },
+              artist: {
+                type: 'string',
+                description: 'The artist',
+              },
+              year: {
+                type: 'number',
+                description: 'The year',
+                format: 'int64',
+              },
+            },
+          },
+          Album: {
+            type: 'object',
+            required: [
+              'name',
+            ],
+            description: 'Album',
+            properties: {
+              name: {
+                type: 'string',
+                description: 'Album name',
+              },
+              length: {
+                type: 'number',
+                description: '',
+              },
+            },
+          },
+        },
+      },
+    };
+    const parsedJSDocs = jsdocInfo()(jsodInput);
+    const result = parseComponents({}, parsedJSDocs);
+    expect(result).toEqual(expected);
+  });
+});