All files / src/plugins/validation/swagger2/semantic-validators form-data.js

78.95% Statements 45/57
73.91% Branches 34/46
76.92% Functions 10/13
78.57% Lines 44/56

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 169                                  29x 29x 29x   29x 40x   40x                 9057x 9057x 5337x         3720x         117x 117x     117x   117x             3603x   1x         3603x 3603x 9017x               4x     2x           118x 119x     118x 2x 2x     2x           2x                                                               117x 118x     117x 113x   4x   4x 4x   4x 2x         2x       4x 2x         2x     4x     40x 40x    
// Assertions are in the following order ( bailing as soon as we hit the first assertion )
 
// Assertation typo
// If a paramter with `in: formdata` exists, warn about typo ( it should be formData )
 
// Assertation 1
// If a paramter with `in: formData` exists, a param with `in: body` cannot
 
// Assertation 2:
// If a parameter with `type: file` exists
// - It must have `in: formData`
// - The consumes property must have `multipart/form-data`
 
// Assertation 3:
// If a parameter with `in: formData` exists a consumes property ( inherited or inline ) my contain `application/x-www-form-urlencoded` or `multipart/form-data`
// Assertation 3 checked with Spectral rule oas2-operation-formData-consume-check
 
const isPlainObject = require('lodash/isPlainObject');
const getIn = require('lodash/get');
const MessageCarrier = require('../../../utils/messageCarrier');
 
module.exports.validate = function({ resolvedSpec }) {
  const messages = new MessageCarrier();
 
  Iif (!isPlainObject(resolvedSpec)) {
    return;
  }
 
  // Looking for...
  // Parameters ( /paths/{method}/parameters or /parameters)
  // - in: formData
  // - type: file
  function walk(obj, path) {
    path = path || [];
    if (typeof obj !== 'object' || obj === null) {
      return;
    }
 
    // Inside a parameter array ( under an operation or pathitem )
    // NOTE: What if we want to add a body, with multipart/form-data? Not possible right?
    if (
      (path[0] === 'paths' || path[0] === 'pathitems') &&
      path[path.length - 1] === 'parameters' &&
      Array.isArray(obj)
    ) {
      const opPath = path.slice(0, path.length - 1);
      const opItem = getIn(resolvedSpec, opPath);
 
      // Check for formdata ( typos )
      assertationTypo(obj, path);
 
      return (
        // assertationOne(obj, path)
        assertationTwo(obj, path, opItem)
      );
    }
 
    // Parameters under the root `/parameters` property
    if (path[0] === 'parameters' && path.length === 2 && Array.isArray(obj)) {
      // Check for formdata ( typos )
      assertationTypo(obj, path);
      // return assertationOne(obj, path)
    }
 
    // Continue to walk the object tree
    const keys = Object.keys(obj);
    Eif (keys) {
      return keys.map(k => walk(obj[k], [...path, k]));
    } else {
      return null;
    }
  }
 
  // Checks the operation for the presences of a consumes
  function hasConsumes(operation, consumes) {
    return (
      isPlainObject(operation) &&
      Array.isArray(operation.consumes) &&
      operation.consumes.some(c => c === consumes)
    );
  }
 
  // Warn about a typo, formdata => formData
  function assertationTypo(params, path) {
    const formDataWithTypos = params.filter(
      p => isPlainObject(p) && p['in'] === 'formdata'
    );
 
    if (formDataWithTypos.length) {
      params.forEach((param, i) => {
        Iif (param['in'] !== 'formdata') {
          return;
        }
        messages.addMessage(
          `${path.join('.')}.${i}`,
          'The form data value for `in` must be camelCase (formData)',
          'error'
        );
      });
      return;
    }
  }
 
  // If a paramter with `in: formData` exists, a param with `in: body` cannot
  // eslint-disable-next-line no-unused-vars
  function assertationOne(params, path) {
    // Assertion 1
    const inBodyIndex = params.findIndex(
      p => isPlainObject(p) && p['in'] === 'body'
    );
    const formData = params.filter(
      p => isPlainObject(p) && p['in'] === 'formData'
    );
    const hasFormData = !!formData.length;
 
    if (~inBodyIndex && hasFormData) {
      // We"ll blame the `in: body` parameter
      const pathStr = `${path.join('.')}.${inBodyIndex}`;
      messages.addMessage(
        pathStr,
        'Parameters cannot have `in` values of both "body" and "formData", as "formData" _will_ be the body',
        'error'
      );
      return;
    }
  }
 
  // If a parameter with `type: file` exists
  // - a. It must have `in: formData`
  // - b. The consumes property must have `multipart/form-data`
  function assertationTwo(params, path, operation) {
    const typeFileIndex = params.findIndex(
      p => isPlainObject(p) && p.type === 'file'
    );
    // No type: file?
    if (!~typeFileIndex) {
      return;
    }
    let hasErrors = false;
 
    const param = params[typeFileIndex];
    const pathStr = [...path, typeFileIndex].join('.');
    // a - must have formData
    if (param['in'] !== 'formData') {
      messages.addMessage(
        pathStr,
        'Parameters with `type` "file" must have `in` be "formData"',
        'error'
      );
      hasErrors = true;
    }
 
    // - b. The consumes property must have `multipart/form-data`
    if (!hasConsumes(operation, 'multipart/form-data')) {
      messages.addMessage(
        pathStr,
        'Operations with Parameters of `type` "file" must include "multipart/form-data" in their "consumes" property',
        'error'
      );
      hasErrors = true;
    }
 
    return hasErrors;
  }
 
  walk(resolvedSpec, []);
  return messages;
};