validateTransaction(transactions, callback) {
    let schema = this.openapi,
      componentsAndPaths,
      analysis,
      options = this.computedOptions,
      schemaCache = {
        schemaFakerCache: this.schemaFakerCache
      },
      matchedEndpoints = [],
      jsonSchemaDialect = schema.jsonSchemaDialect;

    // Only change the stack limit if the optimizeConversion option is true
    if (options.optimizeConversion) {
      // Deciding stack limit based on size of the schema, number of refs and number of paths.
      analysis = schemaUtils.analyzeSpec(schema);

      // Update options on the basis of analysis.
      options = schemaUtils.determineOptions(analysis, options);
    }

    if (!this.validated) {
      return callback(new OpenApiErr('The schema must be validated before attempting conversion'));
    }

    // this cannot be attempted before validation
    componentsAndPaths = { concreteUtils };
    Object.assign(componentsAndPaths, concreteUtils.getRequiredData(this.openapi));


    // create and sanitize basic spec
    schema.servers = _.isEmpty(schema.servers) ? [{ url: '/' }] : schema.servers;
    schema.securityDefs = _.get(schema, 'components.securitySchemes', {});
    schema.baseUrl = _.get(schema, 'servers.0.url', '{{baseURL}}');
    schema.baseUrlVariables = _.get(schema, 'servers.0.variables');

    // Fix {scheme} and {path} vars in the URL to :scheme and :path
    schema.baseUrl = schemaUtils.fixPathVariablesInUrl(schema.baseUrl);

    // check validity of transactions
    try {
      // add Ajv options to support validation of OpenAPI schema.
      // For more details see https://ajv.js.org/#options
      let ajv = new Ajv({
          allErrors: true,
          strict: false
        }),
        validate,
        res;
      addFormats(ajv);
      validate = ajv.compile(transactionSchema);
      res = validate(transactions);

      if (!res) {
        return callback(new OpenApiErr('Invalid syntax provided for requestList', validate.errors));
      }
    }
    catch (e) {
      return callback(new OpenApiErr('Invalid syntax provided for requestList', e));
    }

    return setTimeout(() => {
      async.map(transactions, (transaction, requestCallback) => {
        if (!transaction.id || !transaction.request) {
          return requestCallback(new Error('All transactions must have `id` and `request` properties.'));
        }

        let requestUrl = transaction.request.url,
          matchedPaths;
        if (typeof requestUrl === 'object') {

          // SDK.Url.toString() resolves pathvar to empty string if value is empty
          // so update path variable value to same as key in such cases
          _.forEach(requestUrl.variable, (pathVar) => {
            if (_.isNil(pathVar.value) || (typeof pathVar.value === 'string' && _.trim(pathVar.value).length === 0)) {
              pathVar.value = ':' + pathVar.key;
            }
          });

          // SDK URL object. Get raw string representation.
          requestUrl = (new Url(requestUrl)).toString();
        }

        // 1. Look at transaction.request.URL + method, and find matching request from schema
        matchedPaths = schemaUtils.findMatchingRequestFromSchema(
          transaction.request.method,
          requestUrl,
          schema,
          options
        );

        if (!matchedPaths.length) {
          // No matching paths found
          return requestCallback(null, {
            requestId: transaction.id,
            endpoints: []
          });
        }

        return setTimeout(() => {
          // 2. perform validation for each identified matchedPath (schema endpoint)
          return async.map(matchedPaths, (matchedPath, pathsCallback) => {
            const transactionPathVariables = _.get(transaction, 'request.url.variable', []),
              localServers = matchedPath.path.hasOwnProperty('servers') ?
                matchedPath.path.servers :
                [],
              serversPathVars = [...getServersPathVars(localServers), ...getServersPathVars(schema.servers)],
              isNotAServerPathVar = (pathVarName) => {
                return !serversPathVars.includes(pathVarName);
              };

            matchedPath.unmatchedVariablesFromTransaction = [];
            // override path variable value with actual value present in transaction
            // as matched pathvariable contains key as value, as it is generated from url only
            _.forEach(matchedPath.pathVariables, (pathVar) => {
              const mappedPathVar = _.find(transactionPathVariables, (transactionPathVar) => {
                let matched = transactionPathVar.key === pathVar.key;
                if (
                  !matched &&
                  isNotAServerPathVar(transactionPathVar.key) &&
                  !matchedPath.unmatchedVariablesFromTransaction.includes(transactionPathVar)
                ) {
                  matchedPath.unmatchedVariablesFromTransaction.push(transactionPathVar);
                }
                return matched;
              });
              pathVar.value = _.get(mappedPathVar, 'value', pathVar.value);
              // set _varMatched flag which represents if variable was found in transaction or not
              pathVar._varMatched = !_.isEmpty(mappedPathVar);
            });

            // resolve $ref in all parameter objects if present
            _.forEach(_.get(matchedPath, 'path.parameters'), (param) => {
              if (param.hasOwnProperty('$ref')) {
                _.assign(param, schemaUtils.getRefObject(param.$ref, componentsAndPaths, options));
                _.unset(param, '$ref');
              }
            });

            matchedEndpoints.push(matchedPath.jsonPath);
            // 3. validation involves checking these individual properties
            async.parallel({
              metadata: function(cb) {
                schemaUtils.checkMetadata(transaction, '$', matchedPath.path, matchedPath.name, options, cb);
              },
              path: function(cb) {
                schemaUtils.checkPathVariables(matchedPath, '$.request.url.variable', matchedPath.path,
                  componentsAndPaths, options, schemaCache, jsonSchemaDialect, cb);
              },
              queryparams: function(cb) {
                schemaUtils.checkQueryParams(requestUrl, '$.request.url.query', matchedPath.path,
                  componentsAndPaths, options, schemaCache, jsonSchemaDialect, cb);
              },
              headers: function(cb) {
                schemaUtils.checkRequestHeaders(transaction.request.header, '$.request.header', matchedPath.jsonPath,
                  matchedPath.path, componentsAndPaths, options, schemaCache, jsonSchemaDialect, cb);
              },
              requestBody: function(cb) {
                schemaUtils.checkRequestBody(transaction.request.body, '$.request.body', matchedPath.jsonPath,
                  matchedPath.path, componentsAndPaths, options, schemaCache, jsonSchemaDialect, cb);
              },
              responses: function (cb) {
                schemaUtils.checkResponses(transaction.response, '$.responses', matchedPath.jsonPath,
                  matchedPath.path, componentsAndPaths, options, schemaCache, jsonSchemaDialect, cb);
              }
            }, (err, result) => {
              let allMismatches = _.concat(result.metadata, result.queryparams, result.headers, result.path,
                  result.requestBody),
                responseMismatchesPresent = false,
                retVal;

              // adding mistmatches from responses
              _.each(result.responses, (response) => {
                if (_.get(response, 'mismatches', []).length > 0) {
                  responseMismatchesPresent = true;
                  return false;
                }
              });

              retVal = {
                matched: (allMismatches.length === 0 && !responseMismatchesPresent),
                endpointMatchScore: matchedPath.score,
                endpoint: matchedPath.name,
                mismatches: allMismatches,
                responses: result.responses
              };

              pathsCallback(null, retVal);
            });
          }, (err, result) => {
            // only need to return endpoints that have the joint-highest score
            let highestScore = -Infinity,
              bestResults;
            result.forEach((endpoint) => {
              if (endpoint.endpointMatchScore > highestScore) {
                highestScore = endpoint.endpointMatchScore;
              }
            });
            bestResults = _.filter(result, (ep) => {
              return ep.endpointMatchScore === highestScore;
            });

            requestCallback(err, {
              requestId: transaction.id,
              endpoints: bestResults
            });
          });
        }, 0);
      }, (err, result) => {
        var retVal;

        if (err) {
          return callback(err);
        }

        // determine if any endpoint for any request misatched
        _.each(result, (reqRes) => {
          let thisMismatch = false;
          _.each(reqRes.endpoints, (ep) => {
            if (!ep.matched) {
              return false;
            }
          });
          if (thisMismatch) {
            return false;
          }
        });

        retVal = {
          requests: _.keyBy(result, 'requestId'),
          missingEndpoints: schemaUtils.getMissingSchemaEndpoints(schema, matchedEndpoints,
            componentsAndPaths, options, schemaCache)
        };

        callback(null, retVal);
      });
    }, 0);
  }
@description Takes in a transaction object (meant to represent a Postman history object)

@param {*} transactions RequestList
@param {*} callback return
@returns {boolean} validation