Improvements to az-path-parameter-schema
This commit is contained in:
Родитель
fd2e2ce038
Коммит
0bc674b808
|
@ -1,12 +1,12 @@
|
|||
const URL_MAX_LENGTH = 2083;
|
||||
|
||||
// `given` is a (resolved) parameter entry at the path or operation level
|
||||
module.exports = (param, _opts, paths) => {
|
||||
module.exports = (param, _opts, context) => {
|
||||
if (param === null || typeof param !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const path = paths.path || paths.target || [];
|
||||
const path = context.path || context.target || [];
|
||||
|
||||
// These errors will be caught elsewhere, so silently ignore here
|
||||
if (!param.in || !param.name) {
|
||||
|
@ -30,6 +30,21 @@ module.exports = (param, _opts, paths) => {
|
|||
});
|
||||
}
|
||||
|
||||
// Only check constraints for the final path parameter on a put or patch that returns a 201
|
||||
const apiPath = path[1] ?? '';
|
||||
if (!apiPath.endsWith(`{${param.name}}`)) {
|
||||
return errors;
|
||||
}
|
||||
if (!['put', 'patch'].includes(path[2] ?? '')) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
const oasDoc = context.document.data;
|
||||
const { responses } = oasDoc.paths[apiPath][path[2]];
|
||||
if (!responses || !responses['201']) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (!schema.maxLength && !schema.pattern) {
|
||||
errors.push({
|
||||
message: 'Path parameter should specify a maximum length (maxLength) and characters allowed (pattern).',
|
||||
|
@ -43,7 +58,7 @@ module.exports = (param, _opts, paths) => {
|
|||
} else if (schema.maxLength && schema.maxLength >= URL_MAX_LENGTH) {
|
||||
errors.push({
|
||||
message: `Path parameter maximum length should be less than ${URL_MAX_LENGTH}`,
|
||||
path,
|
||||
path: [...path, 'maxLength'],
|
||||
});
|
||||
} else if (!schema.pattern) {
|
||||
errors.push({
|
||||
|
|
|
@ -347,7 +347,7 @@ rules:
|
|||
formats: ['oas2', 'oas3']
|
||||
given:
|
||||
- $.paths[*].parameters[?(@.in == 'path')]
|
||||
- $.paths.*[get,put,post,patch,delete,options,head].parameters[?(@.in == 'path')]
|
||||
- $.paths[*][get,put,post,patch,delete,options,head].parameters[?(@.in == 'path')]
|
||||
then:
|
||||
function: path-param-schema
|
||||
|
||||
|
@ -524,7 +524,8 @@ rules:
|
|||
description: All success responses except 202 & 204 should define a response body.
|
||||
severity: warn
|
||||
formats: ['oas2']
|
||||
given: $.paths[*][*].responses[?(@property >= 200 && @property < 300 && @property != '202' && @property != '204')]
|
||||
# list http methods explicitly to exclude head
|
||||
given: $.paths[*][get,put,post,patch,delete].responses[?(@property >= 200 && @property < 300 && @property != '202' && @property != '204')]
|
||||
then:
|
||||
field: schema
|
||||
function: truthy
|
||||
|
|
|
@ -15,6 +15,7 @@ test('az-path-parameter-schema should find errors', () => {
|
|||
const oasDoc = {
|
||||
swagger: '2.0',
|
||||
paths: {
|
||||
// 0: should be defined as type: string
|
||||
'/foo/{p1}': {
|
||||
parameters: [
|
||||
{
|
||||
|
@ -24,49 +25,167 @@ test('az-path-parameter-schema should find errors', () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
'/bar/{p2}/baz/{p3}/foo/{p4}': {
|
||||
get: {
|
||||
// 1: should specify a maximum length (maxLength) and characters allowed (pattern) -- p2
|
||||
'/bar/{p2}': {
|
||||
put: {
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/parameters/Param2',
|
||||
name: 'p2',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// 2: should specify characters allowed (pattern) -- p4
|
||||
'/baz/{p3}/qux/{p4}': {
|
||||
put: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p3',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
},
|
||||
{
|
||||
name: 'p4',
|
||||
$ref: '#/parameters/Param4',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// 3: should be less than
|
||||
'/foobar/{p5}': {
|
||||
put: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p5',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
maxLength: 2083,
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
Param2: {
|
||||
name: 'p2',
|
||||
Param4: {
|
||||
name: 'p4',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
maxLength: 64,
|
||||
},
|
||||
},
|
||||
};
|
||||
return linter.run(oasDoc).then((results) => {
|
||||
expect(results.length).toBe(5);
|
||||
expect(results[0].path.join('.')).toBe('paths./foo/{p1}.parameters.0');
|
||||
expect(results.length).toBe(4);
|
||||
expect(results[0].path.join('.')).toBe('paths./foo/{p1}.parameters.0.type');
|
||||
expect(results[0].message).toContain('should be defined as type: string');
|
||||
expect(results[1].path.join('.')).toBe('paths./bar/{p2}.put.parameters.0');
|
||||
expect(results[1].message).toContain('should specify a maximum length');
|
||||
expect(results[1].message).toContain('and characters allowed');
|
||||
expect(results[2].path.join('.')).toBe('paths./baz/{p3}/qux/{p4}.put.parameters.1');
|
||||
expect(results[2].message).toContain('should specify characters allowed');
|
||||
expect(results[3].path.join('.')).toBe('paths./foobar/{p5}.put.parameters.0.maxLength');
|
||||
expect(results[3].message).toContain('should be less than');
|
||||
});
|
||||
});
|
||||
|
||||
test('az-path-parameter-schema should find errors in patch operations', () => {
|
||||
// Test path parameter in 3 different places:
|
||||
// 1. parameter at path level
|
||||
// 2. inline parameter at operation level
|
||||
// 3. referenced parameter at operation level
|
||||
const oasDoc = {
|
||||
swagger: '2.0',
|
||||
paths: {
|
||||
// 0: should specify a maximum length (maxLength) and characters allowed (pattern) -- p2
|
||||
'/bar/{p2}': {
|
||||
patch: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p2',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// 1: should specify characters allowed (pattern) -- p4
|
||||
'/baz/{p3}/qux/{p4}': {
|
||||
patch: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p3',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
$ref: '#/parameters/Param4',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// 2: should be less than
|
||||
'/foobar/{p5}': {
|
||||
patch: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p5',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
maxLength: 2083,
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
Param4: {
|
||||
name: 'p4',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
maxLength: 64,
|
||||
},
|
||||
},
|
||||
};
|
||||
return linter.run(oasDoc).then((results) => {
|
||||
expect(results.length).toBe(3);
|
||||
expect(results[0].path.join('.')).toBe('paths./bar/{p2}.patch.parameters.0');
|
||||
expect(results[0].message).toContain('should specify a maximum length');
|
||||
expect(results[0].message).toContain('and characters allowed');
|
||||
expect(results[1].path.join('.')).toBe('paths./foo/{p1}.parameters.0.type');
|
||||
expect(results[1].message).toContain('should be defined as type: string');
|
||||
expect(results[2].path.join('.')).toBe('paths./bar/{p2}/baz/{p3}/foo/{p4}.get.parameters.0');
|
||||
expect(results[2].message).toContain('should specify a maximum length');
|
||||
expect(results[3].path.join('.')).toBe('paths./bar/{p2}/baz/{p3}/foo/{p4}.get.parameters.1');
|
||||
expect(results[3].message).toContain('should specify characters allowed');
|
||||
expect(results[4].path.join('.')).toBe('paths./bar/{p2}/baz/{p3}/foo/{p4}.get.parameters.2');
|
||||
expect(results[4].message).toContain('should be less than');
|
||||
expect(results[1].path.join('.')).toBe('paths./baz/{p3}/qux/{p4}.patch.parameters.1');
|
||||
expect(results[1].message).toContain('should specify characters allowed');
|
||||
expect(results[2].path.join('.')).toBe('paths./foobar/{p5}.patch.parameters.0.maxLength');
|
||||
expect(results[2].message).toContain('should be less than');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -74,6 +193,7 @@ test('az-path-parameter-schema should find no errors', () => {
|
|||
const oasDoc = {
|
||||
swagger: '2.0',
|
||||
paths: {
|
||||
// 0: should be defined as type: string
|
||||
'/foo/{p1}': {
|
||||
parameters: [
|
||||
{
|
||||
|
@ -85,29 +205,117 @@ test('az-path-parameter-schema should find no errors', () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
'/bar/{p2}/baz/{p3}': {
|
||||
get: {
|
||||
'/bar/{p2}': {
|
||||
put: {
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/parameters/Param2',
|
||||
},
|
||||
{
|
||||
name: 'p3',
|
||||
name: 'p2',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
pattern: '/[a-z]+/',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
patch: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p2',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
pattern: '/[a-z]+/',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/baz/{p3}/qux/{p4}': {
|
||||
put: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p3',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
$ref: '#/parameters/Param4',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
patch: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p3',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
$ref: '#/parameters/Param4',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/foobar/{p5}': {
|
||||
put: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p5',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
pattern: '/[a-z]+/',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
patch: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p5',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
pattern: '/[a-z]+/',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
Param2: {
|
||||
name: 'p2',
|
||||
Param4: {
|
||||
name: 'p4',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
maxLength: 64,
|
||||
pattern: '/[a-z]+/',
|
||||
},
|
||||
},
|
||||
|
@ -121,6 +329,7 @@ test('az-path-parameter-schema should find oas3 errors', () => {
|
|||
const oasDoc = {
|
||||
openapi: '3.0',
|
||||
paths: {
|
||||
// 0: should be defined as type: string
|
||||
'/foo/{p1}': {
|
||||
parameters: [
|
||||
{
|
||||
|
@ -132,31 +341,74 @@ test('az-path-parameter-schema should find oas3 errors', () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
'/bar/{p2}/baz/{p3}': {
|
||||
get: {
|
||||
// 1: should specify a maximum length (maxLength) and characters allowed (pattern) -- p2
|
||||
'/bar/{p2}': {
|
||||
put: {
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/Param2',
|
||||
name: 'p2',
|
||||
in: 'path',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// 2: should specify characters allowed (pattern) -- p4
|
||||
'/baz/{p3}/qux/{p4}': {
|
||||
put: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p3',
|
||||
in: 'path',
|
||||
schema: {
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
},
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/Param4',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// 3: should be less than
|
||||
'/foobar/{p5}': {
|
||||
put: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p5',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
maxLength: 2083,
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
parameters: {
|
||||
Param2: {
|
||||
name: 'p2',
|
||||
Param4: {
|
||||
name: 'p4',
|
||||
in: 'path',
|
||||
schema: {
|
||||
type: 'string',
|
||||
maxLength: 64,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -164,15 +416,15 @@ test('az-path-parameter-schema should find oas3 errors', () => {
|
|||
};
|
||||
return linter.run(oasDoc).then((results) => {
|
||||
expect(results.length).toBe(4);
|
||||
expect(results[0].path.join('.')).toBe('paths./foo/{p1}.parameters.0.schema');
|
||||
expect(results[0].message).toContain('should specify a maximum length');
|
||||
expect(results[0].message).toContain('and characters allowed');
|
||||
expect(results[1].path.join('.')).toBe('paths./foo/{p1}.parameters.0.schema.type');
|
||||
expect(results[1].message).toContain('should be defined as type: string');
|
||||
expect(results[2].path.join('.')).toBe('paths./bar/{p2}/baz/{p3}.get.parameters.0.schema');
|
||||
expect(results[2].message).toContain('should specify a maximum length');
|
||||
expect(results[3].path.join('.')).toBe('paths./bar/{p2}/baz/{p3}.get.parameters.1.schema');
|
||||
expect(results[3].message).toContain('should specify characters allowed');
|
||||
expect(results[0].path.join('.')).toBe('paths./foo/{p1}.parameters.0.schema.type');
|
||||
expect(results[0].message).toContain('should be defined as type: string');
|
||||
expect(results[1].path.join('.')).toBe('paths./bar/{p2}.put.parameters.0.schema');
|
||||
expect(results[1].message).toContain('should specify a maximum length');
|
||||
expect(results[1].message).toContain('and characters allowed');
|
||||
expect(results[2].path.join('.')).toBe('paths./baz/{p3}/qux/{p4}.put.parameters.1.schema');
|
||||
expect(results[2].message).toContain('should specify characters allowed');
|
||||
expect(results[3].path.join('.')).toBe('paths./foobar/{p5}.put.parameters.0.maxLength');
|
||||
expect(results[3].message).toContain('should be less than');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -180,6 +432,7 @@ test('az-path-parameter-schema should find no oas3 errors', () => {
|
|||
const oasDoc = {
|
||||
openapi: '3.0',
|
||||
paths: {
|
||||
// 0: should be defined as type: string
|
||||
'/foo/{p1}': {
|
||||
parameters: [
|
||||
{
|
||||
|
@ -192,15 +445,18 @@ test('az-path-parameter-schema should find no oas3 errors', () => {
|
|||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
'/bar/{p2}/baz/{p3}': {
|
||||
get: {
|
||||
// 1: should specify a maximum length (maxLength) and characters allowed (pattern) -- p2
|
||||
'/bar/{p2}': {
|
||||
put: {
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/Param2',
|
||||
},
|
||||
{
|
||||
name: 'p3',
|
||||
name: 'p2',
|
||||
in: 'path',
|
||||
schema: {
|
||||
type: 'string',
|
||||
|
@ -209,17 +465,63 @@ test('az-path-parameter-schema should find no oas3 errors', () => {
|
|||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// 2: should specify characters allowed (pattern) -- p4
|
||||
'/baz/{p3}/qux/{p4}': {
|
||||
put: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p3',
|
||||
in: 'path',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/Param4',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// 3: should be less than
|
||||
'/foobar/{p5}': {
|
||||
put: {
|
||||
parameters: [
|
||||
{
|
||||
name: 'p5',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
pattern: '/[a-z]+/',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
201: {
|
||||
description: 'Created',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
parameters: {
|
||||
Param2: {
|
||||
name: 'p2',
|
||||
Param4: {
|
||||
name: 'p4',
|
||||
in: 'path',
|
||||
schema: {
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
maxLength: 64,
|
||||
pattern: '/[a-z]+/',
|
||||
},
|
||||
},
|
||||
|
|
Загрузка…
Ссылка в новой задаче