@itentialopensource/adapter-utils 5.1.6 → 5.2.0

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/CHANGELOG.md CHANGED
@@ -1,4 +1,24 @@
1
1
 
2
+ ## 5.2.0 [10-04-2023]
3
+
4
+ * Broker Changes
5
+
6
+ Closes ADAPT-2848
7
+
8
+ See merge request itentialopensource/adapter-utils!277
9
+
10
+ ---
11
+
12
+ ## 5.1.7 [09-09-2023]
13
+
14
+ * Added pagination to expanded generic handler
15
+
16
+ Closes ADAPT-2849
17
+
18
+ See merge request itentialopensource/adapter-utils!276
19
+
20
+ ---
21
+
2
22
  ## 5.1.6 [09-06-2023]
3
23
 
4
24
  * add fixes to connector that are missing
@@ -7,7 +7,6 @@
7
7
 
8
8
  const AsyncLockCl = require('async-lock');
9
9
  const path = require('path');
10
- const jsonQuery = require('json-query');
11
10
 
12
11
  const lock = new AsyncLockCl();
13
12
  let id = null;
@@ -32,16 +31,6 @@ function createCacheEntity(entityName, entityList, interval, sortEntities = true
32
31
  });
33
32
  }
34
33
 
35
- /**
36
- * @summary Generates name for data object. Currently non-unique
37
- * @function generateName
38
- * @returns {String} - generated name
39
- */
40
- function generateName() {
41
- log.warn('Name for entity not found, generated name placeholder');
42
- return 'GeneratedName';
43
- }
44
-
45
34
  /**
46
35
  * @summary Deletes cache data and properties. Data deletion
47
36
  * saves considerable memory.
@@ -358,98 +347,6 @@ function removeCacheEntry(cache, entityType) {
358
347
  log.error(`${origin}: Did not find cache type ${entityType} to remove!`);
359
348
  }
360
349
 
361
- /**
362
- * @function getDataFromSources
363
- * @summary INTERNAL FUNCTION: get data from source(s) - nested
364
- * @param {*} loopField - fields
365
- * @param {Array} sources - sources to look into
366
- * @returns {*} - nested field value
367
- */
368
- function getDataFromSources(loopField, sources) {
369
- let fieldValue = loopField;
370
-
371
- // go through the sources to find the field
372
- for (let s = 0; s < sources.length; s += 1) {
373
- // find the field value using jsonquery
374
- const nestedValue = jsonQuery(loopField, { data: sources[s] }).value;
375
-
376
- // if we found in source - set and no need to check other sources
377
- if (nestedValue) {
378
- fieldValue = nestedValue;
379
- break;
380
- }
381
- }
382
-
383
- return fieldValue;
384
- }
385
-
386
- /**
387
- * @summary Manipulates data from call to only represent the specified fields and id and name if they do not have one
388
- *
389
- * @function parseResponseFields
390
- * @param {Boolean} responseFull - whether or not to take in all fields
391
- * @param {Object of output to call result} responseFields - fields to take in
392
- * - ie response field of "name": "id" will give the iap call output a name field
393
- * with the value being the id
394
- * @param {Array of Objects} allData - data returned from IAP Call
395
- * @return {Array of Objects} - modified data
396
- */
397
- function parseResponseFields(responseFields, allData, end, requestFields) {
398
- const origin = `${id}-cacheHandler-createCacheData`;
399
- log.trace(origin);
400
-
401
- const parsedData = [];
402
- const rfKeys = Object.keys(responseFields);
403
- log.debug(rfKeys);
404
-
405
- let ostypePrefix = '';
406
- if (responseFields.ostypePrefix) {
407
- ostypePrefix = responseFields.ostypePrefix;
408
- }
409
-
410
- let statusValue = true;
411
- if (responseFields.statusValue) {
412
- statusValue = responseFields.statusValue;
413
- }
414
-
415
- allData.response.forEach((currData) => {
416
- const newObj = currData;
417
-
418
- for (let rf = 0; rf < rfKeys.length; rf += 1) {
419
- if (rfKeys[rf] !== 'ostypePrefix') {
420
- let fieldValue = getDataFromSources(responseFields[rfKeys[rf]], [currData, { fake: 'fakedata' }, requestFields]);
421
-
422
- // if the field is ostype - need to add prefix
423
- if (rfKeys[rf] === 'ostype' && typeof fieldValue === 'string') {
424
- fieldValue = ostypePrefix + fieldValue;
425
- }
426
- // if there is a status to set, set it
427
- if (rfKeys[rf] === 'status') {
428
- // if really looking for just a good response
429
- if (responseFields[rfKeys[rf]] === 'return2xx' && allData.icode === statusValue.toString()) {
430
- newObj.isAlive = true;
431
- } else if (fieldValue.toString() === statusValue.toString()) {
432
- newObj.isAlive = true;
433
- } else {
434
- newObj.isAlive = false;
435
- }
436
- }
437
- // if we found a good value
438
- newObj[rfKeys[rf]] = fieldValue;
439
- }
440
- }
441
-
442
- if (!Object.prototype.hasOwnProperty.call(currData, 'name')) {
443
- newObj.name = generateName();
444
- }
445
-
446
- // no longer requiring id field
447
-
448
- parsedData.push(newObj);
449
- });
450
- return parsedData;
451
- }
452
-
453
350
  /**
454
351
  * @summary makes IAP Calls through Generic Handler
455
352
  *
@@ -458,13 +355,18 @@ function parseResponseFields(responseFields, allData, end, requestFields) {
458
355
  * @param requestHandler - instance of requestHandler to make call
459
356
  * @param callback - data or error
460
357
  */
461
- function makeIAPCall(calls, requestHandler, callback) { // todo pass in properties from cacheHandler
358
+ function makeIAPCall(calls, requestHandler, callback) {
462
359
  const callPromises = [];
463
- log.debug('Starting an iap call from cache.');
360
+
464
361
  for (let i = 0; i < calls.length; i += 1) {
465
362
  log.debug('Response :', calls[i].responseFields);
466
363
  callPromises.push(new Promise((resolve, reject) => {
467
- requestHandler.genericAdapterRequest(calls[i].path, calls[i].method, calls[i].query, calls[i].body, calls[i].headers, (callRet, callErr) => {
364
+ const metadata = {};
365
+ if (calls[i].pagination) {
366
+ metadata.pagination = calls[i].pagination;
367
+ metadata.pagination.responseDatakey = calls[i].responseDatakey || '';
368
+ }
369
+ requestHandler.iapMakeGenericCall(metadata, calls[i].path, calls[i], [{ fake: 'fakedata' }], [], (callRet, callErr) => {
468
370
  if (callErr) {
469
371
  log.error('Make iap call failed with error');
470
372
  log.error(callErr);
@@ -472,21 +374,13 @@ function makeIAPCall(calls, requestHandler, callback) { // todo pass in properti
472
374
  log.error(callErr.IAPerror.displayString);
473
375
  return reject(callErr);
474
376
  }
475
- if (calls[i].handleFailure === 'ignore') {
476
- log.info(`Call failed for path ${calls[i].path} with error code ${callErr.icode}. Ignoring.`);
477
- return resolve([]);
478
- }
479
377
  log.warn(`Call failed for path ${calls[i].path} with error code ${callErr.icode}. Rejecting.`);
480
378
  return reject(callErr);
481
379
  }
482
380
 
483
381
  // callRet is the object returned from that call
484
382
  log.info(`Sucessful Call. Adding to cache from path ${calls[i].path}`);
485
- const result = callRet;
486
- if (calls[i].responseDatakey) {
487
- result.response = jsonQuery(calls[i].responseDatakey, { data: result.response }).value;
488
- }
489
- return resolve(parseResponseFields(calls[i].responseFields, result, i, calls[i].requestFields));
383
+ return resolve(callRet);
490
384
  });
491
385
  }));
492
386
  }
@@ -5,6 +5,54 @@
5
5
  /* eslint consistent-return: warn */
6
6
 
7
7
  /* NodeJS internal utilities */
8
+ const jsonQuery = require('json-query');
9
+
10
+ // INTERNAL FUNCTIONS
11
+
12
+ /**
13
+ * @summary Update the offset for paginated call.
14
+ * @function incrementOffset
15
+ * @param {String} offsetType - type of offset (page or limit)
16
+ * @param {Number} data - previous offset
17
+ * @param {Number} limit - call limit
18
+ * @returns {Number} - new offset
19
+ */
20
+ function incrementOffset(offsetType, previousOffset, limit) {
21
+ let newOffset;
22
+ if (offsetType === 'limit') {
23
+ newOffset = previousOffset + limit;
24
+ } else if (offsetType === 'page') {
25
+ newOffset = previousOffset + 1;
26
+ } else {
27
+ throw new Error(`Offset Type : ${offsetType} not supported or insufficient data was provided`);
28
+ }
29
+ return newOffset;
30
+ }
31
+
32
+ /**
33
+ * @summary Function to set the value at a specified property path
34
+ * @function setNestedProperty
35
+ * @param {Object} oldValue - Object to update
36
+ * @param {String} responseDataKey - path to property to update in . separated string
37
+ * @param {Object} newValue - Object with data to use in update
38
+ */
39
+ function setNestedProperty(oldValue, responseDatakey, newValue) {
40
+ const newRes = jsonQuery(responseDatakey, { data: newValue.response }).value;
41
+ const oldRes = jsonQuery(responseDatakey, { data: oldValue.response }).value;
42
+ const path = responseDatakey.split('.');
43
+ let currentObj = oldValue.response;
44
+ for (let i = 0; i < path.length - 1; i += 1) {
45
+ const segment = path[i];
46
+ if (Object.hasOwnProperty.call(currentObj, segment)) {
47
+ currentObj = currentObj[segment];
48
+ } else {
49
+ currentObj[segment] = {};
50
+ currentObj = currentObj[segment];
51
+ }
52
+ }
53
+ // Modifies value at path and updates original object
54
+ currentObj[path[path.length - 1]] = oldRes.concat(newRes);
55
+ }
8
56
 
9
57
  class GenericHandler {
10
58
  /**
@@ -156,7 +204,7 @@ class GenericHandler {
156
204
  }
157
205
  reqObj.addlHeaders = { ...reqObj.addlHeaders, ...signature };
158
206
  this.requestHandlerInst.identifyRequest('.generic', action, reqObj, returnF, (irReturnData, irReturnError) => {
159
- // if we received an error or their is no response on the results
207
+ // if we received an error or there is no response on the results
160
208
  // return an error
161
209
  if (irReturnError) {
162
210
  /* HERE IS WHERE YOU CAN ALTER THE ERROR MESSAGE */
@@ -174,6 +222,24 @@ class GenericHandler {
174
222
  });
175
223
  });
176
224
  }
225
+
226
+ // Does not support AWS adapters due to the auth call above
227
+ if (metadata && metadata.pagination) {
228
+ return this.expandedGenericAdapterRequestPaginated(metadata.pagination, action, reqObj, returnF, meth, (returnData, returnError) => {
229
+ if (returnError) {
230
+ /* HERE IS WHERE YOU CAN ALTER THE ERROR MESSAGE */
231
+ return callback(null, returnError);
232
+ }
233
+ if (!Object.hasOwnProperty.call(returnData, 'response')) {
234
+ const errorObj = this.requestHandlerInst.formatErrorObject(this.myid, meth, 'Invalid Response', ['genericAdapterRequest'], null, null, null);
235
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
236
+ return callback(null, errorObj);
237
+ }
238
+ /* HERE IS WHERE YOU CAN ALTER THE RETURN DATA */
239
+ // return the response
240
+ return callback(returnData, null);
241
+ });
242
+ }
177
243
  return this.requestHandlerInst.identifyRequest('.generic', action, reqObj, returnF, (irReturnData, irReturnError) => {
178
244
  // if we received an error or their is no response on the results
179
245
  // return an error
@@ -198,6 +264,85 @@ class GenericHandler {
198
264
  }
199
265
  }
200
266
 
267
+ /**
268
+ * Makes the requested generic call
269
+ *
270
+ * @function expandedGenericAdapterRequestPaginated
271
+ * @param {Object} metadata - metadata for the call (optional).
272
+ * Can be a stringified Object.
273
+ * @param {String} uriPath - the path of the api call - do not include the host, port, base path or version (optional)
274
+ * @param {String} restMethod - the rest method (GET, POST, PUT, PATCH, DELETE) (optional)
275
+ * @param {Object} pathVars - the parameters to be put within the url path (optional).
276
+ * Can be a stringified Object.
277
+ * @param {Object} queryData - the parameters to be put on the url (optional).
278
+ * Can be a stringified Object.
279
+ * @param {Object} requestBody - the body to add to the request (optional).
280
+ * Can be a stringified Object.
281
+ * @param {Object} addlHeaders - additional headers to be put on the call (optional).
282
+ * Can be a stringified Object.
283
+ * @param {Object} paginationObject - object specifying pagination variables and increment method
284
+ * @param {getCallback} callback - a callback function to return the result (Generics)
285
+ * or the error
286
+ */
287
+ expandedGenericAdapterRequestPaginated(paginationObject, action, reqObj, returnF, meth, callback) {
288
+ const origin = `${this.myid}-requestHandler-expandedGenericAdapterRequestPaginated`;
289
+ log.trace(origin);
290
+ let results;
291
+ // Set up variables for calls
292
+ const { offsetVar } = paginationObject;
293
+ const pagType = paginationObject.requestLocation; // Body or query supported
294
+ let pagLocation;
295
+ if (pagType === 'body') {
296
+ pagLocation = 'payload';
297
+ } else if (pagType === 'query') {
298
+ pagLocation = 'uriQuery';
299
+ } else {
300
+ const err = new Error(`Pagination Type : ${pagType} not supported or insufficient data was provided`);
301
+ return callback(null, err);
302
+ }
303
+ const limit = reqObj[pagLocation][paginationObject.limitVar];
304
+ const recursiveCall = (currentOffset) => {
305
+ try {
306
+ const myReqObj = {
307
+ ...reqObj
308
+ };
309
+ myReqObj[pagLocation][offsetVar] = currentOffset;
310
+ this.requestHandlerInst.identifyRequest('.generic', action, myReqObj, returnF, (result, error) => {
311
+ if (error) {
312
+ return callback(null, error);
313
+ }
314
+
315
+ if (!Object.hasOwnProperty.call(result, 'response')) {
316
+ const errorObj = this.requestHandlerInst.formatErrorObject(this.myid, meth, 'Invalid Response', ['genericAdapterRequestPaginated'], null, null, null);
317
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
318
+ return callback(null, errorObj);
319
+ }
320
+ // Results hasn't been populated yet
321
+ if (!results) {
322
+ results = { ...result };
323
+ } else if (paginationObject.responseDatakey) {
324
+ setNestedProperty(results, paginationObject.responseDatakey, result);
325
+ } else {
326
+ results.response = results.response.concat(result.response);
327
+ }
328
+ if (result.response.length === limit) {
329
+ const newOffset = incrementOffset(paginationObject.incrementBy, currentOffset, limit);
330
+ // Rescurse
331
+ recursiveCall(newOffset);
332
+ } else {
333
+ return callback(results, null);
334
+ }
335
+ });
336
+ } catch (e) {
337
+ // handle any exception
338
+ const errorObj = this.transUtil.checkAndReturn(e, origin, 'Expanded Generic Adapter Request with Pagination Failed');
339
+ return callback(null, errorObj);
340
+ }
341
+ };
342
+
343
+ recursiveCall(reqObj[pagLocation][offsetVar]);
344
+ }
345
+
201
346
  /**
202
347
  * Makes the requested generic call
203
348
  *
@@ -329,24 +329,109 @@ function dbCalls(collectionName, entity, action, data, callback) {
329
329
  /*
330
330
  * INTERNAL FUNCTION: get data from source(s) - nested
331
331
  */
332
- function getDataFromSources(loopField, sources) {
332
+ function getDataFromSources(loopField, sources, props) {
333
333
  let fieldValue = loopField;
334
-
334
+ let foundProp = false;
335
335
  // go through the sources to find the field
336
336
  for (let s = 0; s < sources.length; s += 1) {
337
337
  // find the field value using jsonquery
338
338
  const nestedValue = jsonQuery(loopField, { data: sources[s] }).value;
339
339
 
340
340
  // if we found in source - set and no need to check other sources
341
- if (nestedValue) {
341
+ if (nestedValue && Object.keys(nestedValue).length !== 0) {
342
342
  fieldValue = nestedValue;
343
+ foundProp = true;
343
344
  break;
344
345
  }
345
346
  }
346
347
 
348
+ // Check for field in adapter properties
349
+ if (!foundProp && props && props[loopField]) {
350
+ fieldValue = props[loopField];
351
+ }
352
+
347
353
  return fieldValue;
348
354
  }
349
355
 
356
+ /**
357
+ * @summary Extracts the keys to be replaced in request fields or response fields.
358
+ *
359
+ * @function extractKeysFromBraces
360
+ * @param {String} value - String in which to look for {} and extract values
361
+ *
362
+ * @return {Array} Array containing 0 or more keys found in input String
363
+ */
364
+ function extractKeysFromBraces(value) {
365
+ const regex = /\{(.*?)\}/g;
366
+ const matches = value.match(regex);
367
+ if (matches) {
368
+ return matches.map((key) => key.replace(/{|}/g, ''));
369
+ }
370
+ return [];
371
+ }
372
+
373
+ /**
374
+ * @summary Given a string containing a logical OR in braces {||}, return the LHS if value is found
375
+ * in sources, otherwise return the RHS
376
+ *
377
+ * @function setConditionalValue
378
+ * @param {String} valueField - String containing operands
379
+ * @param {Array} source - Array containing sources to search for operand values
380
+ * @param {Object} props - Object containing Adapter props as secondary source
381
+ *
382
+ * @return {String} Final value with data replaced in found keys.
383
+ */
384
+ function setConditionalValue(valueField, sources, props) {
385
+ if (!valueField.includes('{||}')) {
386
+ throw new Error('This method is only to be used with Strings containing a logical OR in braces {||}');
387
+ }
388
+ const operands = valueField.split('{||}'); // Array of left side and right side
389
+ const operandA = extractKeysFromBraces(operands[0]); // [name]
390
+ const operandB = extractKeysFromBraces(operands[1]); // [serial]
391
+ let finalValue = operands[0]; // {name}
392
+ for (let i = 0; i < operandA.length; i += 1) {
393
+ const fieldValue = getDataFromSources(operandA[i], sources, props);
394
+ if (fieldValue === operandA[i]) {
395
+ // did not find value - break here to try second part of conditional
396
+ [, finalValue] = operands;
397
+ break;
398
+ }
399
+ finalValue = finalValue.replace(`{${operandA[i]}}`, fieldValue);
400
+ }
401
+
402
+ // Check if broke out of loop due to missing value
403
+ if (finalValue !== operands[1]) {
404
+ return finalValue;
405
+ }
406
+
407
+ // Look for values for keys to the right of {||}
408
+ for (let j = 0; j < operandB.length; j += 1) {
409
+ const fieldValue = getDataFromSources(operandB[j], sources, props);
410
+ if (fieldValue === operandB[j]) {
411
+ throw new Error(`Could not find value in sources for ${operandA} or ${operandB}`);
412
+ }
413
+ finalValue = finalValue.replace(`{${operandB[j]}}`, fieldValue);
414
+ }
415
+
416
+ return finalValue;
417
+ }
418
+
419
+ function setResponseDataFromSources(loopField, sources, props) {
420
+ if (loopField.includes('{||}')) {
421
+ return setConditionalValue(loopField, sources, props);
422
+ }
423
+ let myField = loopField;
424
+ const keys = extractKeysFromBraces(loopField);
425
+ // Handle if val not found ?
426
+ for (let k = 0; k < keys.length; k += 1) {
427
+ const responseKey = keys[k];
428
+ const fieldValue = getDataFromSources(responseKey, sources, props);
429
+ myField = myField.replace(`{${responseKey}}`, fieldValue);
430
+ }
431
+
432
+ return myField;
433
+ }
434
+
350
435
  class RequestHandler {
351
436
  /**
352
437
  * Request Handler
@@ -553,19 +638,16 @@ class RequestHandler {
553
638
  let handleFail = 'fail';
554
639
  let ostypePrefix = '';
555
640
  let statusValue = 'true';
641
+ const sources = devResp.concat([callProps.requestFields]);
556
642
  if (callProps.path) {
557
643
  uriPath = `${callProps.path}`;
558
-
559
644
  // make any necessary changes to the path
560
- if (devResp !== null && callProps.requestFields && Object.keys(callProps.requestFields).length > 0) {
561
- const rqKeys = Object.keys(callProps.requestFields);
562
-
645
+ if (devResp !== null) {
563
646
  // get the field from the provided device
564
- for (let rq = 0; rq < rqKeys.length; rq += 1) {
565
- const fieldValue = getDataFromSources(callProps.requestFields[rqKeys[rq]], devResp);
566
-
567
- // put the value into the path - if it has been specified in the path
568
- uriPath = uriPath.replace(`{${rqKeys[rq]}}`, fieldValue);
647
+ const pathKeys = extractKeysFromBraces(uriPath);
648
+ for (let pathKey = 0; pathKey < pathKeys.length; pathKey += 1) {
649
+ const fieldValue = getDataFromSources(pathKeys[pathKey], sources, this.props);
650
+ uriPath = uriPath.replace(`{${pathKeys[pathKey]}}`, fieldValue);
569
651
  }
570
652
  }
571
653
  }
@@ -577,69 +659,41 @@ class RequestHandler {
577
659
  // go through the query params to check for variable values
578
660
  const cpKeys = Object.keys(callQuery);
579
661
  for (let cp = 0; cp < cpKeys.length; cp += 1) {
580
- // if (callQuery[cpKeys[cp]].startsWith('{') && callQuery[cpKeys[cp]].endsWith('}')) {
581
- // make any necessary changes to the query params
582
- if (devResp !== null && callProps.requestFields && Object.keys(callProps.requestFields).length > 0) {
583
- const rqKeys = Object.keys(callProps.requestFields);
584
-
585
- // get the field from the provided device
586
- for (let rq = 0; rq < rqKeys.length; rq += 1) {
587
- if (callQuery[cpKeys[cp]] === rqKeys[rq]) {
588
- const fieldValue = getDataFromSources(callProps.requestFields[rqKeys[rq]], devResp);
589
- // put the value into the query - if it has been specified in the query
590
- callQuery[cpKeys[cp]] = fieldValue;
591
- }
592
- }
662
+ // get array of values to replace
663
+ const matches = extractKeysFromBraces(callQuery[cpKeys[cp]]);
664
+ for (let m = 0; m < matches.length; m += 1) {
665
+ const queryKey = matches[m];
666
+ const fieldValue = getDataFromSources(queryKey, sources, this.props);
667
+ callQuery[cpKeys[cp]] = callQuery[cpKeys[cp]].replace(`{${queryKey}}`, fieldValue);
593
668
  }
594
- // }
595
669
  }
596
670
  }
597
671
  if (callProps.body) {
598
672
  callBody = { ...callProps.body };
599
-
600
673
  // go through the body fields to check for variable values
601
674
  const cbKeys = Object.keys(callBody);
602
675
  for (let cb = 0; cb < cbKeys.length; cb += 1) {
603
- // if (callBody[cbKeys[cb]].startsWith('{') && callBody[cbKeys[cb]].endsWith('}')) {
676
+ const matches = extractKeysFromBraces(callBody[cbKeys[cb]]);
677
+ for (let m = 0; m < matches.length; m += 1) {
604
678
  // make any necessary changes to the query params
605
- if (devResp !== null && callProps.requestFields && Object.keys(callProps.requestFields).length > 0) {
606
- const rqKeys = Object.keys(callProps.requestFields);
607
-
608
- // get the field from the provided device
609
- for (let rq = 0; rq < rqKeys.length; rq += 1) {
610
- if (callBody[cbKeys[cb]] === rqKeys[rq]) {
611
- const fieldValue = getDataFromSources(callProps.requestFields[rqKeys[rq]], devResp);
612
-
613
- // put the value into the query - if it has been specified in the query
614
- callBody[cbKeys[cb]] = fieldValue;
615
- }
616
- }
679
+ const bodyKey = matches[m];
680
+ const fieldValue = getDataFromSources(bodyKey, sources, this.props);
681
+ callBody[cbKeys[cb]] = callBody[cbKeys[cb]].replace(`{${bodyKey}}`, fieldValue);
617
682
  }
618
- // }
619
683
  }
620
684
  }
621
685
  if (callProps.headers) {
622
686
  callHeaders = { ...callProps.headers };
623
-
624
- // go through the body fields to check for variable values
687
+ // go through the header fields to check for variable values
625
688
  const chKeys = Object.keys(callHeaders);
626
689
  for (let ch = 0; ch < chKeys.length; ch += 1) {
627
- // if (callHeaders[chKeys[ch]].startsWith('{') && callHeaders[chKeys[ch]].endsWith('}')) {
628
- // make any necessary changes to the query params
629
- if (devResp !== null && callProps.requestFields && Object.keys(callProps.requestFields).length > 0) {
630
- const rqKeys = Object.keys(callProps.requestFields);
631
-
632
- // get the field from the provided device
633
- for (let rq = 0; rq < rqKeys.length; rq += 1) {
634
- if (callHeaders[chKeys[ch]] === rqKeys[rq]) {
635
- const fieldValue = getDataFromSources(callProps.requestFields[rqKeys[rq]], devResp);
636
-
637
- // put the value into the query - if it has been specified in the query
638
- callHeaders[chKeys[ch]] = fieldValue;
639
- }
640
- }
690
+ const matches = extractKeysFromBraces(callHeaders[chKeys[ch]]);
691
+ for (let m = 0; m < matches.length; m += 1) {
692
+ const headerKey = matches[m];
693
+ // make any necessary changes to the query params
694
+ const fieldValue = getDataFromSources(headerKey, sources, this.props);
695
+ callHeaders[chKeys[ch]] = callQuery[chKeys[ch]].replace(`{${headerKey}}`, fieldValue);
641
696
  }
642
- // }
643
697
  }
644
698
  }
645
699
  if (callProps.handleFailure) {
@@ -654,9 +708,16 @@ class RequestHandler {
654
708
 
655
709
  // !! using Generic makes it easier on the Adapter Builder (just need to change the path)
656
710
  // !! you can also replace with a specific call if that is easier
711
+ if (callProps.pagination) {
712
+ metadata.pagination = callProps.pagination;
713
+ metadata.pagination.responseDatakey = callProps.responseDatakey || '';
714
+ }
657
715
  return this.expandedGenericAdapterRequest(metadata, uriPath, uriMethod, null, callQuery, callBody, callHeaders, (result, error) => {
658
716
  // if we received an error or their is no response on the results return an error
659
717
  if (error) {
718
+ if (this.props.stub && error.icode === 'AD.301') {
719
+ return callback(null, error);
720
+ }
660
721
  if (handleFail === 'fail') {
661
722
  return callback(null, error);
662
723
  }
@@ -690,7 +751,8 @@ class RequestHandler {
690
751
  const thisDevice = myResult.response[a];
691
752
  for (let rf = 0; rf < rfKeys.length; rf += 1) {
692
753
  if (rfKeys[rf] !== 'ostypePrefix') {
693
- let fieldValue = getDataFromSources(callProps.responseFields[rfKeys[rf]], [thisDevice, devResp, callProps.requestFields]);
754
+ // devResp removed due to conditional issues
755
+ let fieldValue = setResponseDataFromSources(callProps.responseFields[rfKeys[rf]], [thisDevice, callProps.requestFields]);
694
756
 
695
757
  // if the field is ostype - need to add prefix
696
758
  if (rfKeys[rf] === 'ostype' && typeof fieldValue === 'string') {
@@ -740,8 +802,7 @@ class RequestHandler {
740
802
  for (let rf = 0; rf < rfKeys.length; rf += 1) {
741
803
  // skip ostypePrefix since it is not a field
742
804
  if (rfKeys[rf] !== 'ostypePrefix') {
743
- let fieldValue = getDataFromSources(callProps.responseFields[rfKeys[rf]], [thisDevice, devResp, callProps.requestFields]);
744
-
805
+ let fieldValue = setResponseDataFromSources(callProps.responseFields[rfKeys[rf]], [thisDevice, devResp, callProps.requestFields]);
745
806
  // if the field is ostype - need to add prefix
746
807
  if (rfKeys[rf] === 'ostype' && typeof fieldValue === 'string') {
747
808
  fieldValue = ostypePrefix + fieldValue;
@@ -781,6 +842,7 @@ class RequestHandler {
781
842
  });
782
843
  } catch (e) {
783
844
  const errorObj = this.formatErrorObject(this.myid, meth, 'Caught Exception', null, null, null, e);
845
+ log.debug(`actual error: ${JSON.stringify(e)}`);
784
846
  log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
785
847
  return callback(null, errorObj);
786
848
  }
@@ -1191,7 +1191,9 @@ class RestHandler {
1191
1191
  const bodyString = buildPayload(entity, action, entitySchema, payload);
1192
1192
 
1193
1193
  if ((callMeth !== 'GET' || entitySchema.sendGetBody) && bodyString !== '{}') {
1194
- thisAHdata['Content-length'] = Buffer.byteLength(bodyString);
1194
+ if (!thisAHdata['Content-length'] && !thisAHdata['Content-Length']) {
1195
+ thisAHdata['Content-length'] = Buffer.byteLength(bodyString);
1196
+ }
1195
1197
  }
1196
1198
 
1197
1199
  // set up the request to be sent
@@ -1319,7 +1321,9 @@ class RestHandler {
1319
1321
  const bodyString = buildPayload('.system', 'healthcheck', healthSchema, payload);
1320
1322
 
1321
1323
  if ((callMeth !== 'GET' || healthSchema.sendGetBody) && bodyString !== '{}') {
1322
- thisAHdata['Content-length'] = Buffer.byteLength(bodyString);
1324
+ if (!thisAHdata['Content-length'] && !thisAHdata['Content-Length']) {
1325
+ thisAHdata['Content-length'] = Buffer.byteLength(bodyString);
1326
+ }
1323
1327
  }
1324
1328
 
1325
1329
  // set up the request to be sent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itentialopensource/adapter-utils",
3
- "version": "5.1.6",
3
+ "version": "5.2.0",
4
4
  "description": "Itential Adapter Utility Libraries",
5
5
  "scripts": {
6
6
  "postinstall": "node utils/setup.js",
Binary file
@@ -1046,6 +1046,38 @@
1046
1046
  "description": "The method of the call to getDevicesFiltered",
1047
1047
  "default": "GET"
1048
1048
  },
1049
+ "pagination": {
1050
+ "type": "object",
1051
+ "description": "todo",
1052
+ "properties": {
1053
+ "offsetVar": {
1054
+ "type": "string",
1055
+ "description": "Name of variable that defines how to go to next set of results"
1056
+ },
1057
+ "limitVar": {
1058
+ "type": "string",
1059
+ "description": "Name of variable that defines the max results returned in a request"
1060
+ },
1061
+ "incrementBy": {
1062
+ "type": "string",
1063
+ "enum": [
1064
+ "limit",
1065
+ "page"
1066
+ ],
1067
+ "description": "How to incremenet offset. Default limit",
1068
+ "default": "limit"
1069
+ },
1070
+ "requestLocation": {
1071
+ "type": "string",
1072
+ "enum": [
1073
+ "query",
1074
+ "body"
1075
+ ],
1076
+ "description": "Where in request the pagination data goes",
1077
+ "default": "query"
1078
+ }
1079
+ }
1080
+ },
1049
1081
  "query": {
1050
1082
  "type": "object",
1051
1083
  "description": "The json object with query parameters of the call to getDevicesFiltered",
@@ -1402,31 +1434,63 @@
1402
1434
  "properties": {
1403
1435
  "path": {
1404
1436
  "type": "string",
1405
- "description": "The fully qualified path of the call to getDevice (e.g. /rest/api/device/{deviceid})",
1437
+ "description": "The fully qualified path of the call to populate the cache (e.g. /rest/api/devices)",
1406
1438
  "default": ""
1407
1439
  },
1408
1440
  "method": {
1409
1441
  "type": "string",
1410
- "description": "The method of the call to getDevice",
1442
+ "description": "The method of the call to populate the cache",
1411
1443
  "default": "GET"
1412
1444
  },
1445
+ "pagination": {
1446
+ "type": "object",
1447
+ "description": "todo",
1448
+ "properties": {
1449
+ "offsetVar": {
1450
+ "type": "string",
1451
+ "description": "Name of variable that defines how to go to next set of results"
1452
+ },
1453
+ "limitVar": {
1454
+ "type": "string",
1455
+ "description": "Name of variable that defines the max results returned in a request"
1456
+ },
1457
+ "incrementBy": {
1458
+ "type": "string",
1459
+ "enum": [
1460
+ "limit",
1461
+ "page"
1462
+ ],
1463
+ "description": "How to incremenet offset. Default limit",
1464
+ "default": "limit"
1465
+ },
1466
+ "requestLocation": {
1467
+ "type": "string",
1468
+ "enum": [
1469
+ "query",
1470
+ "body"
1471
+ ],
1472
+ "description": "Where in request the pagination data goes",
1473
+ "default": "query"
1474
+ }
1475
+ }
1476
+ },
1413
1477
  "query": {
1414
1478
  "type": "object",
1415
- "description": "The json object with query parameters of the call to getDevice",
1479
+ "description": "The json object with query parameters of the call to populate the cache",
1416
1480
  "additionalProperties": {
1417
1481
  "type": ["string", "number"]
1418
1482
  }
1419
1483
  },
1420
1484
  "body": {
1421
1485
  "type": "object",
1422
- "description": "The json object with body of the call to getDevice",
1486
+ "description": "The json object with body of the call to populate the cache",
1423
1487
  "additionalProperties": {
1424
1488
  "type": ["string", "number"]
1425
1489
  }
1426
1490
  },
1427
1491
  "headers": {
1428
1492
  "type": "object",
1429
- "description": "The json object with headers of the call to getDevice",
1493
+ "description": "The json object with headers of the call to populate the cache",
1430
1494
  "additionalProperties": {
1431
1495
  "type": ["string", "number"]
1432
1496
  }
@@ -1442,7 +1506,7 @@
1442
1506
  },
1443
1507
  "requestFields": {
1444
1508
  "type": "object",
1445
- "description": "The json object with response fields of the call to getDevice",
1509
+ "description": "The json object with response fields of the call to populate the cache",
1446
1510
  "additionalProperties": {
1447
1511
  "type": ["string", "number"]
1448
1512
  },
@@ -1455,7 +1519,7 @@
1455
1519
  },
1456
1520
  "responseFields": {
1457
1521
  "type": "object",
1458
- "description": "The json object with response fields of the call to getDevice",
1522
+ "description": "The json object with response fields of the call to populate the cache",
1459
1523
  "additionalProperties": {
1460
1524
  "type": ["string", "number"]
1461
1525
  }