All files / src/plugins/validation/2and3/semantic-validators walker.js

100% Statements 44/44
100% Branches 48/48
100% Functions 6/6
100% Lines 44/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168                          29x 29x 29x   29x 116x   116x   116x     5240x                                     5240x 991x 3x                   5240x 3x 1x               5240x 2x 1x               5240x 21x 1x                 5240x 346x 346x 346x   346x     20x                     5240x 5240x 11623x 12x                   116x       29x           29x                       29x     346x     346x 346x 2101x 2101x       439x 439x   1662x     346x         26x    
// Walks an entire spec.
 
// Assertation 1:
// `type` values for properties must be strings
// multi-type properties are not allowed
 
// Assertation 2:
// In specific areas of a spec, allowed $ref values are restricted.
 
// Assertation 3:
// Sibling keys with $refs are not allowed - default set to `off`
// http://watson-developer-cloud.github.io/api-guidelines/swagger-coding-style#sibling-elements-for-refs
 
const match = require('matcher');
const { walk } = require('../../../utils');
const MessageCarrier = require('../../../utils/messageCarrier');
 
module.exports.validate = function({ jsSpec, isOAS3 }, config) {
  const messages = new MessageCarrier();
 
  config = config.walker;
 
  walk(jsSpec, [], function(obj, path) {
    // parent keys that allow non-string "type" properties. for example,
    // having a definition called "type" is allowed
    const allowedParents = isOAS3
      ? [
          'schemas',
          'properties',
          'responses',
          'parameters',
          'requestBodies',
          'headers',
          'securitySchemes'
        ]
      : [
          'definitions',
          'properties',
          'parameters',
          'responses',
          'securityDefinitions'
        ];
 
    ///// "type" should always have a string-type value, everywhere.
    if (obj.type && allowedParents.indexOf(path[path.length - 1]) === -1) {
      if (typeof obj.type !== 'string') {
        messages.addMessage(
          [...path, 'type'],
          '"type" should be a string',
          'error'
        );
      }
    }
 
    ///// Minimums and Maximums
 
    if (obj.maximum && obj.minimum) {
      if (greater(obj.minimum, obj.maximum)) {
        messages.addMessage(
          path.concat(['minimum']),
          'Minimum cannot be more than maximum',
          'error'
        );
      }
    }
 
    if (obj.maxProperties && obj.minProperties) {
      if (greater(obj.minProperties, obj.maxProperties)) {
        messages.addMessage(
          path.concat(['minProperties']),
          'minProperties cannot be more than maxProperties',
          'error'
        );
      }
    }
 
    if (obj.maxLength && obj.minLength) {
      if (greater(obj.minLength, obj.maxLength)) {
        messages.addMessage(
          path.concat(['minLength']),
          'minLength cannot be more than maxLength',
          'error'
        );
      }
    }
 
    ///// Restricted $refs -- only check internal refs
    if (obj.$ref && typeof obj.$ref === 'string' && obj.$ref.startsWith('#')) {
      const blacklistPayload = getRefPatternBlacklist(path, isOAS3);
      const refBlacklist = blacklistPayload.blacklist || [];
      const matches = match([obj.$ref], refBlacklist);
 
      if (refBlacklist && refBlacklist.length && matches.length) {
        // Assertation 2
        // use the slice(1) to remove the `!` negator from the string
        messages.addMessage(
          [...path, '$ref'],
          `${
            blacklistPayload.location
          } $refs must follow this format: ${refBlacklist[0].slice(1)}`,
          config.incorrect_ref_pattern,
          'incorrect_ref_pattern'
        );
      }
    }
 
    const keys = Object.keys(obj);
    keys.forEach(k => {
      if (keys.indexOf('$ref') > -1 && k !== '$ref') {
        messages.addMessage(
          path.concat([k]),
          'Values alongside a $ref will be ignored.',
          config.$ref_siblings,
          '$ref_siblings'
        );
      }
    });
  });
 
  return messages;
};
 
// values are globs!
const unacceptableRefPatternsS2 = {
  responses: ['!*#/responses*'],
  schema: ['!*#/definitions*'],
  parameters: ['!*#/parameters*']
};
 
const unacceptableRefPatternsOAS3 = {
  responses: ['!*#/components/responses*'],
  schema: ['!*#/components/schemas*'],
  parameters: ['!*#/components/parameters*'],
  requestBody: ['!*#/components/requestBodies*'],
  security: ['!*#/components/securitySchemes*'],
  callbacks: ['!*#/components/callbacks*'],
  examples: ['!*#/components/examples*'],
  headers: ['!*#/components/headers*'],
  links: ['!*#/components/links*']
};
 
const exceptionedParents = ['properties'];
 
function getRefPatternBlacklist(path, isOAS3) {
  const unacceptableRefPatterns = isOAS3
    ? unacceptableRefPatternsOAS3
    : unacceptableRefPatternsS2;
  let location = '';
  const blacklist = path.reduce((prev, curr, i) => {
    const parent = path[i - 1];
    if (
      unacceptableRefPatterns[curr] &&
      exceptionedParents.indexOf(parent) === -1
    ) {
      location = curr;
      return unacceptableRefPatterns[curr];
    } else {
      return prev;
    }
  }, null);
  return { blacklist, location };
}
 
function greater(a, b) {
  // is a greater than b?
  return a > b;
}