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 | 29x 29x 29x 29x 29x 29x 100x 100x 100x 199x 199x 199x 199x 100x 199x 199x 199x 15x 15x 15x 199x 249x 146x 165x 165x 165x 199x 199x 2x 199x 15x 15x 199x 199x 35x 199x 180x 199x 199x 35x 3x 100x | // Assertation 1: // Path parameters definition, either at the path-level or the operation-level, need matching paramater declarations // Assertation 2: // Path parameter declarations do not allow empty names (/path/{} is not valid) // Assertation 3: // Path strings must be (equivalently) different (Example: /pet/{petId} and /pet/{petId2} are equivalently the same and would generate an error) // Assertation 4: // Paths must have unique (name + in combination) parameters // Assertation 5: // Paths cannot have literal query strings in them. // Handled by the Spectral rule, path-not-include-query const each = require('lodash/each'); const findIndex = require('lodash/findIndex'); const isPlainObject = require('lodash/isPlainObject'); const MessageCarrier = require('../../../utils/messageCarrier'); const templateRegex = /\{(.*?)\}/g; module.exports.validate = function({ resolvedSpec }) { const messages = new MessageCarrier(); const seenRealPaths = {}; const tallyRealPath = path => { // ~~ is a flag for a removed template string const realPath = path.replace(templateRegex, '~~'); const prev = seenRealPaths[realPath]; seenRealPaths[realPath] = true; // returns if it was previously seen return !!prev; }; each(resolvedSpec.paths, (path, pathName) => { Iif (!path || !pathName) { return; } const parametersFromPath = path.parameters ? path.parameters.slice() : []; const availableParameters = parametersFromPath.map((param, i) => { Iif (!isPlainObject(param)) { return; } param.$$path = `paths.${pathName}.parameters[${i}]`; return param; }); each(path, (operation, operationName) => { if ( operation && operation.parameters && Array.isArray(operation.parameters) ) { availableParameters.push( ...operation.parameters.map((param, i) => { Iif (!isPlainObject(param)) { return; } param.$$path = `paths.${pathName}.${operationName}.parameters[${i}]`; return param; }) ); } }); // Assertation 3 const hasBeenSeen = tallyRealPath(pathName); if (hasBeenSeen) { messages.addMessage( `paths.${pathName}`, 'Equivalent paths are not allowed.', 'error' ); } // Assertation 4 each(parametersFromPath, (parameterDefinition, i) => { const nameAndInComboIndex = findIndex(parametersFromPath, { name: parameterDefinition.name, in: parameterDefinition.in }); // comparing the current index against the first found index is good, because // it cuts down on error quantity when only two parameters are involved, // i.e. if param1 and param2 conflict, this will only complain about param2. // it also will favor complaining about parameters later in the spec, which // makes more sense to the user. Iif (i !== nameAndInComboIndex && parameterDefinition.in) { messages.addMessage( `paths.${pathName}.parameters[${i}]`, "Path parameters must have unique 'name' + 'in' properties", 'error' ); } }); let pathTemplates = pathName.match(templateRegex) || []; pathTemplates = pathTemplates.map(str => str.replace('{', '').replace('}', '') ); // Assertation 1 each(availableParameters, (parameterDefinition, i) => { Iif ( isPlainObject(parameterDefinition) && parameterDefinition.in === 'path' && pathTemplates.indexOf(parameterDefinition.name) === -1 ) { messages.addMessage( parameterDefinition.$$path || `paths.${pathName}.parameters[${i}]`, `Path parameter was defined but never used: ${parameterDefinition.name}`, 'error' ); } }); Eif (pathTemplates) { pathTemplates.forEach(parameter => { // Assertation 2 if (parameter === '') { // it was originally "{}" messages.addMessage( `paths.${pathName}`, 'Empty path parameter declarations are not valid', 'error' ); } }); } else { each(availableParameters, (parameterDefinition, i) => { // Assertation 1, for cases when no templating is present on the path if (parameterDefinition.in === 'path') { messages.addMessage( `paths.${pathName}.parameters[${i}]`, `Path parameter was defined but never used: ${parameterDefinition.name}`, 'error' ); } }); } }); return messages; }; |