All files / src/plugins/validation/oas3/semantic-validators operations.js

97.5% Statements 39/40
93.55% Branches 29/31
100% Functions 4/4
97.5% Lines 39/40

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                    29x 29x 29x 29x 29x     29x 71x   71x 71x   71x       71x   71x 119x 119x 98x     98x 80x   80x 80x   78x 78x   78x       78x       78x       78x     78x               78x           8x                 78x 78x 78x 83x 74x 74x       74x 11x                             71x       78x         78x    
// Assertation 1. Request body objects must have a `content` property
// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#requestBodyObject
// covered by Spectral's oas3-schema rule
 
// Assertation 2. Operations with non-form request bodies should set the `x-codegen-request-body-name`
// annotation (for code generation purposes)
 
// Assertation 3. Request bodies with application/json content should not use schema
// type: string, format: binary.
 
const pick = require('lodash/pick');
const each = require('lodash/each');
const { hasRefProperty } = require('../../../utils');
const MessageCarrier = require('../../../utils/messageCarrier');
const findOctetSequencePaths = require('../../../utils/findOctetSequencePaths')
  .findOctetSequencePaths;
 
module.exports.validate = function({ resolvedSpec, jsSpec }, config) {
  const messages = new MessageCarrier();
 
  const configSchemas = config.schemas;
  config = config.operations;
 
  const REQUEST_BODY_NAME = 'x-codegen-request-body-name';
 
  // get, head, and delete are not in this list because they are not allowed
  // to have request bodies
  const allowedOps = ['post', 'put', 'patch', 'options', 'trace'];
 
  each(resolvedSpec.paths, (path, pathName) => {
    const operations = pick(path, allowedOps);
    each(operations, (op, opName) => {
      Iif (!op || op['x-sdk-exclude'] === true) {
        return;
      }
      if (op.requestBody) {
        const requestBodyContent = op.requestBody.content;
        const requestBodyMimeTypes =
          op.requestBody.content && Object.keys(requestBodyContent);
        if (requestBodyContent && requestBodyMimeTypes.length) {
          // request body has content
          const firstMimeType = requestBodyMimeTypes[0]; // code generation uses the first mime type
          const oneContentType = requestBodyMimeTypes.length === 1;
          const isJson =
            firstMimeType === 'application/json' ||
            firstMimeType.endsWith('+json');
 
          const hasArraySchema =
            requestBodyContent[firstMimeType].schema &&
            requestBodyContent[firstMimeType].schema.type === 'array';
 
          const hasRequestBodyName =
            op[REQUEST_BODY_NAME] && op[REQUEST_BODY_NAME].trim().length;
 
          // non-array json responses with only one content type will have
          // the body exploded in sdk generation, no need for name
          const explodingBody = oneContentType && isJson && !hasArraySchema;
 
          // referenced request bodies have names
          const hasReferencedRequestBody = hasRefProperty(jsSpec, [
            'paths',
            pathName,
            opName,
            'requestBody'
          ]);
 
          // form params do not need names
          if (
            !isFormParameter(firstMimeType) &&
            !explodingBody &&
            !hasReferencedRequestBody &&
            !hasRequestBodyName
          ) {
            messages.addMessage(
              `paths.${pathName}.${opName}`,
              'Operations with non-form request bodies should set a name with the x-codegen-request-body-name annotation.',
              config.no_request_body_name,
              'no_request_body_name'
            );
          }
 
          // Assertation 3
          const binaryStringStatus = configSchemas.json_or_param_binary_string;
          Eif (binaryStringStatus !== 'off') {
            for (const mimeType of requestBodyMimeTypes) {
              if (mimeType === 'application/json') {
                const schemaPath = `paths.${pathName}.${opName}.requestBody.content.${mimeType}.schema`;
                const octetSequencePaths = findOctetSequencePaths(
                  requestBodyContent[mimeType].schema,
                  schemaPath
                );
                for (const p of octetSequencePaths) {
                  messages.addMessage(
                    p,
                    'JSON request/response bodies should not contain binary (type: string, format: binary) values.',
                    binaryStringStatus,
                    'json_or_param_binary_string'
                  );
                }
              }
            }
          }
        }
      }
    });
  });
 
  return messages;
};
 
function isFormParameter(mimeType) {
  const formDataMimeTypes = [
    'multipart/form-data',
    'application/x-www-form-urlencoded',
    'application/octet-stream'
  ];
  return formDataMimeTypes.includes(mimeType);
}