Add az-property-default-not-allowed rule with test & docs

This commit is contained in:
Mike Kistler 2023-01-25 07:43:43 -06:00
Родитель e3a2c8151c
Коммит 7a8adfd389
4 изменённых файлов: 231 добавлений и 0 удалений

Просмотреть файл

@ -202,6 +202,9 @@ the characters that can be be used in the parameter value.
Using post for a create operation is discouraged. Use put or patch instead.
### az-property-default-not-allowed
A required property should not specify a default value.
### az-property-description
All schema properties should have a description.

Просмотреть файл

@ -0,0 +1,46 @@
// Check that required properties of a schema do not have a default.
// `input` is the schema of a request or response body
module.exports = function propertyDefaultNotAllowed(schema, options, { path }) {
if (schema === null || typeof schema !== 'object') {
return [];
}
const errors = [];
// eslint-disable-next-line no-restricted-syntax
for (const prop of schema.required || []) {
if (schema.properties[prop]?.default) {
errors.push({
message: `Schema property "${prop}" is required and cannot have a default`,
path: [...path, 'properties', prop, 'default'],
});
}
}
if (schema.properties && typeof schema.properties === 'object') {
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(schema.properties)) {
errors.push(
...propertyDefaultNotAllowed(value, options, { path: [...path, 'properties', key] }),
);
}
}
if (schema.items) {
errors.push(
...propertyDefaultNotAllowed(schema.items, options, { path: [...path, 'items'] }),
);
}
if (schema.allOf && Array.isArray(schema.allOf)) {
// eslint-disable-next-line no-restricted-syntax
for (const [index, value] of schema.allOf.entries()) {
errors.push(
...propertyDefaultNotAllowed(value, options, { path: [...path, 'allOf', index] }),
);
}
}
return errors;
};

Просмотреть файл

@ -15,6 +15,7 @@ functions:
- patch-content-type
- path-param-schema
- path-param-names
- property-default-not-allowed
- readonly-in-response-schema
- security-definitions
- security-requirements
@ -409,6 +410,17 @@ rules:
field: '201'
function: falsy
az-property-default-not-allowed:
description: A required property should not specify a default value.
message: '{{error}}'
severity: warn
formats: ['oas2']
given:
- $.paths[*].[put,post,patch].parameters.[?(@.in == 'body')].schema
- $.paths[*].[get,put,post,patch,delete].responses[*].schema
then:
function: property-default-not-allowed
az-property-description:
description: All schema properties should have a description.
message: Property should have a description.

Просмотреть файл

@ -0,0 +1,170 @@
const { linterForRule } = require('./utils');
let linter;
beforeAll(async () => {
linter = await linterForRule('az-property-default-not-allowed');
return linter;
});
test('az-property-default-not-allowed should find errors', () => {
const myOpenApiDocument = {
swagger: '2.0',
paths: {
'/path1': {
put: {
parameters: [
{
name: 'body',
in: 'body',
schema: {
$ref: '#/definitions/MyBodyModel',
},
required: true,
},
],
responses: {
200: {
description: 'OK',
schema: {
$ref: '#/definitions/MyResponseModel',
},
},
},
},
},
},
definitions: {
MyBodyModel: {
type: 'object',
required: ['prop1'],
properties: {
prop1: {
type: 'string',
default: 'foo',
},
},
},
MyResponseModel: {
type: 'object',
required: ['prop2'],
properties: {
prop2: {
type: 'string',
default: 'bar',
},
prop3: {
$ref: '#/definitions/MyNestedResponseModel',
},
},
allOf: [
{
required: ['prop5'],
properties: {
prop5: {
type: 'string',
default: 'qux',
},
},
},
],
},
MyNestedResponseModel: {
type: 'object',
required: ['prop4'],
properties: {
prop4: {
type: 'string',
default: 'baz',
},
},
},
},
};
return linter.run(myOpenApiDocument).then((results) => {
expect(results.length).toBe(4);
expect(results[0].path.join('.')).toBe('paths./path1.put.parameters.0.schema.properties.prop1.default');
expect(results[0].message).toBe('Schema property "prop1" is required and cannot have a default');
expect(results[1].path.join('.')).toBe('paths./path1.put.responses.200.schema.allOf.0.properties.prop5.default');
expect(results[1].message).toBe('Schema property "prop5" is required and cannot have a default');
expect(results[2].path.join('.')).toBe('paths./path1.put.responses.200.schema.properties.prop2.default');
expect(results[2].message).toBe('Schema property "prop2" is required and cannot have a default');
expect(results[3].path.join('.')).toBe('paths./path1.put.responses.200.schema.properties.prop3.properties.prop4.default');
expect(results[3].message).toBe('Schema property "prop4" is required and cannot have a default');
});
});
test('az-property-default-not-allowed should find no errors', () => {
const myOpenApiDocument = {
swagger: '2.0',
paths: {
'/path1': {
put: {
parameters: [
{
name: 'body',
in: 'body',
schema: {
$ref: '#/definitions/MyBodyModel',
},
required: true,
},
],
responses: {
200: {
description: 'OK',
schema: {
$ref: '#/definitions/MyResponseModel',
},
},
},
},
},
},
definitions: {
MyBodyModel: {
type: 'object',
required: ['prop1'],
properties: {
prop1: {
type: 'string',
},
},
},
MyResponseModel: {
type: 'object',
properties: {
prop2: {
type: 'string',
default: 'bar',
},
prop3: {
$ref: '#/definitions/MyNestedResponseModel',
},
},
allOf: [
{
required: ['prop5'],
properties: {
prop5: {
type: 'string',
},
},
},
],
},
MyNestedResponseModel: {
type: 'object',
required: ['prop4'],
properties: {
prop4: {
type: 'string',
},
},
},
},
};
return linter.run(myOpenApiDocument).then((results) => {
expect(results.length).toBe(0);
});
});