@mimik/api-helper 0.0.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,13 +57,24 @@ Gets the API file from swaggerhub and store it in the give PATH location.
57
57
 
58
58
  - <code>Promise</code> An error is thrown if the apiFilename resolution generates an error or the request to the API provider fails or the file connot be saved.
59
59
 
60
+ `apiInfo` options has the following format:
61
+ ``` javascript
62
+ {
63
+ "provider": "provider of the api file, can be `swaggerhub` or `bitbucket`",
64
+ "apiBasicAuth": {
65
+ "username": "username for bitbucket",
66
+ "password": "password for bitbucket"
67
+ },
68
+ "apiApiKey": "apiKey for access private API on swaggerhub, can be optional if the API is accessible publically"
69
+ }
70
+
60
71
  **Requires**: <code>module:@mimik/request-retry</code>, <code>module:@mimik/response-helper</code>, <code>module:@mimik/sumologic-winston-logger</code>, <code>module:fs</code>, <code>module:js-yaml</code>, <code>module:path</code>
61
72
 
62
73
  | Param | Type | Description |
63
74
  | --- | --- | --- |
64
75
  | apiFilename | <code>PATH.&lt;string&gt;</code> | Name of the file where the API file will be stored. |
65
76
  | correlationId | <code>UUID.&lt;string&gt;</code> | CorrelationId when logging activites. |
66
- | options | <code>object</code> | Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiKey`` to access private API. |
77
+ | options | <code>object</code> | Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiInfo` to access the api file in the api provider. |
67
78
 
68
79
  <a name="setupServerFiles"></a>
69
80
 
package/index.js CHANGED
@@ -3,6 +3,8 @@ const fs = require('fs');
3
3
  const pathLib = require('path');
4
4
  const yaml = require('js-yaml');
5
5
  const _ = require('lodash');
6
+ const SwaggerClient = require('swagger-client');
7
+ const { Base64 } = require('js-base64');
6
8
 
7
9
  const logger = require('@mimik/sumologic-winston-logger');
8
10
  const { getRichError } = require('@mimik/response-helper');
@@ -28,8 +30,16 @@ const {
28
30
  CLIENT_CREDENTIALS,
29
31
  IMPLICIT,
30
32
  POSTFIX,
31
- API_PROVIDER,
33
+ API_PROVIDER_SWAGGERHUB,
34
+ API_PROVIDER_BITBUCKET,
32
35
  RESOLVED,
36
+ API_SOURCE,
37
+ SWAGGER,
38
+ BITBUCKET,
39
+ SWAGGERHUB,
40
+ EXTENSION_YML,
41
+ DEFAULT_BITBUCKET_USERNAME,
42
+ DEFAULT_BITBUCKET_PASSWORD,
33
43
  } = require('./lib/common');
34
44
 
35
45
  /**
@@ -117,31 +127,71 @@ const apiSetup = (setup, registeredOperations, securityHandlers, extraFormats, c
117
127
  * @requires path
118
128
  * @param {PATH.<string>} apiFilename - Name of the file where the API file will be stored.
119
129
  * @param {UUID.<string>} correlationId - CorrelationId when logging activites.
120
- * @param {object} options - Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiKey`` to access private API.
130
+ * @param {object} options - Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiInfo` to access the api file in the api provider.
121
131
  * @return {Promise}.
122
132
  * &fulfil {object} The API file itself.
123
133
  * @throws {Promise} An error is thrown if the apiFilename resolution generates an error or the request to the API provider fails or the file connot be saved.
134
+ *
135
+ * `apiInfo` options has the following format:
136
+ * ``` javascript
137
+ * {
138
+ * "provider": "provider of the api file, can be `swaggerhub` or `bitbucket`",
139
+ * "apiBasicAuth": {
140
+ * "username": "username for bitbucket",
141
+ * "password": "password for bitbucket"
142
+ * },
143
+ * "apiApiKey": "apiKey for access private API on swaggerhub, can be optional if the API is accessible publically"
144
+ * }
124
145
  */
125
146
  const getAPIFile = (apiFilename, correlationId, options) => {
126
- logger.info('Getting API definition', correlationId);
147
+ const swaggerOptions = (spec) => ({
148
+ spec,
149
+ allowMetaPatches: false,
150
+ skipNormalization: true,
151
+ mode: 'strict',
152
+ });
153
+
154
+ logger.info('getting API definition', correlationId);
155
+ let apiDefinition;
156
+
127
157
  try {
128
158
  if (fs.existsSync(apiFilename)) {
129
159
  logger.debug('API file already exists', { apiFilename }, correlationId);
130
- let apiDefinition = fs.readFileSync(apiFilename, 'utf8');
131
-
132
- try { apiDefinition = JSON.parse(apiDefinition); }
133
- catch (errJSON) {
134
- try { apiDefinition = yaml.load(apiDefinition); }
135
- catch (errYaml) {
136
- return Promise.reject(getRichError('System', 'wrong file format', { apiFilename }, errYaml));
137
- }
138
- }
139
- return Promise.resolve(apiDefinition);
160
+ apiDefinition = fs.readFileSync(apiFilename, 'utf8');
140
161
  }
141
162
  }
142
163
  catch (err) {
143
164
  return Promise.reject(getRichError('System', 'file system error', { apiFilename }, err));
144
165
  }
166
+ if (apiDefinition) {
167
+ try { apiDefinition = JSON.parse(apiDefinition); }
168
+ catch (err) {
169
+ return Promise.reject(getRichError('System', 'wrong file format', { apiFilename }, err));
170
+ }
171
+ return SwaggerClient.resolve(swaggerOptions(apiDefinition))
172
+ .catch((err) => {
173
+ throw getRichError('System', 'could not resolve apiDefiniton', { apiFilename }, err);
174
+ })
175
+ .then((apiDefinitionResult) => {
176
+ if (apiDefinitionResult.errors.length !== 0) {
177
+ logger.error('errors while resolving definition', { errors: apiDefinitionResult.errors }, correlationId);
178
+ throw getRichError('Parameter', 'errors while resolving definition', { apiFilename, errors: apiDefinitionResult.errors });
179
+ }
180
+ try { fs.writeFileSync(apiFilename, JSON.stringify(apiDefinitionResult.spec, null, 2)); }
181
+ catch (err) {
182
+ throw getRichError('System', 'file system error', { apiFilename }, err);
183
+ }
184
+ return apiDefinitionResult.spec;
185
+ });
186
+ }
187
+ if (!options) {
188
+ return Promise.reject(getRichError('Paremater', 'no options', { apiFilename }));
189
+ }
190
+ const { apiInfo } = options;
191
+
192
+ if (!apiInfo) {
193
+ return Promise.reject(getRichError('Parameter', 'no information for swaggerFile', { apiFilename }));
194
+ }
145
195
  let fileName;
146
196
  let apiDirectory;
147
197
 
@@ -156,45 +206,85 @@ const getAPIFile = (apiFilename, correlationId, options) => {
156
206
  }
157
207
  try {
158
208
  if (!fs.existsSync(apiDirectory)) {
159
- logger.debug('Creating directory', { apiDirectory }, correlationId);
209
+ logger.debug('creating directory', { apiDirectory }, correlationId);
160
210
  fs.mkdirSync(apiDirectory);
161
211
  }
162
212
  }
163
213
  catch (err) {
164
214
  return Promise.reject(getRichError('System', 'file system error', { apiDirectory }, err));
165
215
  }
166
- const url = `${API_PROVIDER}/${params[0]}/${params[1]}/${params[2]}?${RESOLVED}`;
167
-
168
- logger.debug('API file does not exist, retrieving it', { url }, correlationId);
169
216
  const opts = {
170
217
  method: 'GET',
171
- url,
172
218
  headers: {
173
219
  'x-correlation-id': correlationId,
174
220
  },
175
221
  };
176
- if (options) {
177
- if (options.apiKey) opts.headers = { Authorization: options.apiKey };
178
- if (options.metrics) {
179
- opts.metrics = options.metrics;
180
- opts.metrics.url = url;
222
+ const provider = apiInfo.provider || BITBUCKET;
223
+
224
+ try {
225
+ switch (provider) {
226
+ case SWAGGERHUB: {
227
+ opts.url = `${API_PROVIDER_SWAGGERHUB}/${params[0]}/${params[1]}/${params[2]}?${RESOLVED}`;
228
+ if (apiInfo.apiApiKey) opts.headers.Authorization = apiInfo.apiApiKey;
229
+ break;
230
+ }
231
+ case BITBUCKET: {
232
+ if (!apiInfo.apiBasicAuth || !apiInfo.apiBasicAuth.username || !apiInfo.apiBasicAuth.password) {
233
+ throw getRichError('Parameter', 'missing username/password for accessing Bitbucket', { apiFilename });
234
+ }
235
+ if (apiInfo.apiBasicAuth.username === DEFAULT_BITBUCKET_USERNAME || apiInfo.apiBasicAuth.password === DEFAULT_BITBUCKET_PASSWORD) {
236
+ throw getRichError('Parameter', 'missing username/password for accessing Bitbucket', { apiFilename });
237
+ }
238
+ try { opts.headers.Authorization = `Basic ${Base64.encode(`${apiInfo.apiBasicAuth.username}:${apiInfo.apiBasicAuth.password}`)}`; }
239
+ catch (err) {
240
+ throw getRichError('System', 'could not create basicAuth', { apiFilename }, err);
241
+ }
242
+ opts.url = `${API_PROVIDER_BITBUCKET}/${params[0]}/${params[1]}${API_SOURCE}/${params[2]}/${SWAGGER}${EXTENSION_YML}`;
243
+ break;
244
+ }
245
+ default: {
246
+ throw getRichError('Parameter', 'invalid API provider', { provider, apiFilename });
247
+ }
181
248
  }
182
249
  }
250
+ catch (err) {
251
+ return Promise.reject(err);
252
+ }
253
+ if (options.metrics) {
254
+ opts.metrics = options.metrics;
255
+ opts.metrics.url = opts.url;
256
+ }
257
+ opts.retry = {
258
+ logLevel: {
259
+ response: 'debug',
260
+ responseDetails: 'type',
261
+ request: 'debug',
262
+ },
263
+ };
264
+ logger.debug('API file does not exist, retrieving it', { url: opts.url }, correlationId);
183
265
  return rpRetry(opts)
184
- .catch((err) => {
185
- const error = err;
266
+ .then((result) => {
267
+ let resultJSON = result;
186
268
 
187
- error.message = `${error.message} - { "apiFilename":"${apiFilename}"}`;
188
- throw error;
269
+ if (typeof result !== 'object') resultJSON = yaml.load(result);
270
+ return SwaggerClient.resolve(swaggerOptions(resultJSON));
189
271
  })
190
- .then((result) => {
191
- try {
192
- fs.writeFileSync(apiFilename, JSON.stringify(result, null, 2));
272
+ .catch((err) => {
273
+ if (err.statusCode) {
274
+ throw err;
275
+ }
276
+ throw getRichError('System', 'could not resolve apiDefiniton', { apiFilename }, err);
277
+ })
278
+ .then((apiDefinitionResult) => {
279
+ if (apiDefinitionResult.errors.length !== 0) {
280
+ logger.error('errors while resolving definition', { errors: apiDefinitionResult.errors }, correlationId);
281
+ throw getRichError('Parameter', 'errors while resolving definition', { apiFilename, errors: apiDefinitionResult.errors });
193
282
  }
283
+ try { fs.writeFileSync(apiFilename, JSON.stringify(apiDefinitionResult.spec, null, 2)); }
194
284
  catch (err) {
195
- throw getRichError('System', `file system error: ${err.message}`, { apiFilename }, err);
285
+ throw getRichError('System', 'file system error', { apiFilename }, err);
196
286
  }
197
- return result;
287
+ return apiDefinitionResult.spec;
198
288
  });
199
289
  };
200
290
 
package/lib/common.js CHANGED
@@ -15,9 +15,16 @@ const API_KEY_SECURITY = 'ApiKeySecurity';
15
15
  const CLIENT_CREDENTIALS = 'clientCredentials';
16
16
  const IMPLICIT = 'implicit';
17
17
 
18
- const POSTFIX = 'swagger.json';
19
- const API_PROVIDER = 'https://api.swaggerhub.com/apis';
18
+ const API_PROVIDER_SWAGGERHUB = 'https://api.swaggerhub.com/apis';
19
+ const API_PROVIDER_BITBUCKET = 'https://api.bitbucket.org/2.0/repositories';
20
20
  const RESOLVED = 'resolved=true';
21
+ const API_SOURCE = '/src';
22
+ const SWAGGER = 'swagger';
23
+ const BITBUCKET = 'bitbucket';
24
+ const SWAGGERHUB = 'swaggerhub';
25
+ const EXTENSION_YML = '.yml';
26
+ const EXTENSION_JSON = '.json';
27
+ const POSTFIX = `${SWAGGER}${EXTENSION_JSON}`;
21
28
 
22
29
  const LOCAL = 'local';
23
30
  const SET_ON = 'on';
@@ -26,6 +33,9 @@ const SECURITY_OFF = 'mock';
26
33
 
27
34
  const DEFAULT_FORMATS = ['date', 'time', 'date-time', 'byte', 'uuid', 'uri', 'email', 'ipv4', 'ipv6'];
28
35
 
36
+ const DEFAULT_BITBUCKET_USERNAME = ' ';
37
+ const DEFAULT_BITBUCKET_PASSWORD = ' ';
38
+
29
39
  const AUTHORIZATION = 'authorization';
30
40
  const ADMIN = 'admin';
31
41
  const SUB_ADMIN = 'subAdmin';
@@ -56,13 +66,22 @@ module.exports = {
56
66
  CLIENT_CREDENTIALS,
57
67
  IMPLICIT,
58
68
  POSTFIX,
59
- API_PROVIDER,
69
+ API_PROVIDER_SWAGGERHUB,
70
+ API_PROVIDER_BITBUCKET,
71
+ SWAGGER,
72
+ BITBUCKET,
73
+ SWAGGERHUB,
74
+ EXTENSION_JSON,
75
+ EXTENSION_YML,
60
76
  RESOLVED,
77
+ API_SOURCE,
61
78
  LOCAL,
62
79
  SET_ON,
63
80
  SECURITY_ON,
64
81
  SECURITY_OFF,
65
82
  DEFAULT_FORMATS,
83
+ DEFAULT_BITBUCKET_USERNAME,
84
+ DEFAULT_BITBUCKET_PASSWORD,
66
85
  AUTHORIZATION,
67
86
  ADMIN,
68
87
  SUB_ADMIN,
@@ -33,17 +33,17 @@ const saveProperties = (extractResult, buildDirectory, controllersDirectoryName,
33
33
  controllers.forEach((controller) => {
34
34
  itemToSave = '';
35
35
  extractResult[controller].forEach((operationId) => {
36
- itemToSave = `${itemToSave} ${operationId},\n`;
36
+ itemToSave += ` ${operationId},\n`;
37
37
  operationIds.push(operationId);
38
38
  });
39
- stringToSave = `${stringToSave}const {\n${itemToSave}} = require('../${controllersDirectoryName}/${controller}');\n`;
39
+ stringToSave += `const {\n${itemToSave}} = require('../${controllersDirectoryName}/${controller}');\n`;
40
40
  });
41
- stringToSave = `${stringToSave}\nmodule.exports = {\n`;
41
+ stringToSave += '\nmodule.exports = {\n';
42
42
  itemToSave = '';
43
43
  operationIds.forEach((operationId) => {
44
- itemToSave = `${itemToSave} ${operationId},\n`;
44
+ itemToSave += ` ${operationId},\n`;
45
45
  });
46
- stringToSave = `${stringToSave}${itemToSave}};\n`;
46
+ stringToSave += `${itemToSave}};\n`;
47
47
  logger.info(`creating ${filename}`, { filename }, correlationId);
48
48
  try {
49
49
  fs.writeFileSync(filename, stringToSave);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mimik/api-helper",
3
- "version": "0.0.1",
3
+ "version": "1.0.1",
4
4
  "description": "helper for openAPI backend and mimik service",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -31,20 +31,22 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@mimik/request-helper":"^1.7.8",
34
- "@mimik/request-retry": "^2.1.4",
35
- "@mimik/response-helper": "^2.6.3",
34
+ "@mimik/request-retry": "^3.0.0",
35
+ "@mimik/response-helper": "^3.0.0",
36
36
  "@mimik/sumologic-winston-logger": "^1.6.15",
37
- "@mimik/swagger-helper": "^3.0.5",
37
+ "@mimik/swagger-helper": "^4.0.1",
38
38
  "ajv-formats": "^3.0.0-rc.0",
39
+ "js-base64": "3.7.5",
39
40
  "js-yaml":"^4.1.0",
40
41
  "jsonwebtoken": "^9.0.0",
41
42
  "lodash": "^4.17.21",
42
- "openapi-backend": "^5.9.1"
43
+ "openapi-backend": "^5.9.1",
44
+ "swagger-client": "3.19.6"
43
45
  },
44
46
  "devDependencies": {
45
47
  "@mimik/eslint-plugin-dependencies": "^2.4.5",
46
48
  "@mimik/eslint-plugin-document-env": "^1.0.5",
47
- "eslint": "8.38.0",
49
+ "eslint": "8.39.0",
48
50
  "eslint-config-airbnb": "19.0.4",
49
51
  "eslint-plugin-import": "2.27.5",
50
52
  "eslint-plugin-jsx-a11y": "6.7.1",