Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 46 additions & 7 deletions packages/ruleset/src/functions/integer-attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ module.exports = function (schema, _opts, context) {
// These are the valid format values that can be used with integer schemas.
const intFormats = ['int32', 'int64'];

//These are the minimum and maximum values for integer schemas
const int32Minimum = -2147483648;
const int32Maximum = 2147483647;

const int64Minimum = -9007199254740991;
const int64Maximum = 9007199254740991;
Comment thread
diatrcz marked this conversation as resolved.
Outdated

/**
* This function performs various checks on an integer schema property to make sure it
* contains the "minimum", "maximum" attributes.
Expand All @@ -50,10 +57,13 @@ function integerBoundaryErrors(schema, path) {

const errors = [];

if (isDefined(format) && !intFormats.includes(format)) {
if (
!isDefined(format) ||
(isDefined(format) && !intFormats.includes(format))
) {
errors.push({
message: `Integer schemas should specify format as one of ${intFormats.join(
'.'
', '
)}`,
path,
});
Expand All @@ -70,11 +80,40 @@ function integerBoundaryErrors(schema, path) {
path,
});
}
if (isDefined(minimum) && isDefined(maximum) && minimum > maximum) {
errors.push({
message: `'minimum' cannot be greater than 'maximum'`,
path,
});
if (isDefined(minimum) && isDefined(maximum)) {
if (minimum > maximum) {
errors.push({
message: `'minimum' cannot be greater than 'maximum'`,
path,
});
}
if (format === 'int32') {
if (minimum > int32Maximum || minimum < int32Minimum) {
errors.push({
message: `'minimum' value is out of safe range`,
path,
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might not want to gate this on maximum also being defined (and corresponding feedback for the three below).

if (maximum > int32Maximum || maximum < int32Minimum) {
errors.push({
message: `'maximum' value is out of safe range`,
path,
});
}
} else {
if (minimum > int64Maximum || minimum < int64Minimum) {
errors.push({
message: `'minimum' value is out of safe range`,
path,
});
}
if (maximum > int64Maximum || maximum < int64Minimum) {
errors.push({
message: `'maximum' value is out of safe range`,
path,
});
}
}
}

return errors;
Expand Down
101 changes: 101 additions & 0 deletions packages/ruleset/test/rules/integer-attributes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ describe(`Spectral rule: ${ruleId}`, () => {
testDocument.components.schemas['RuleTester2'] = {
description: 'Tests integer fields within allOf',
type: ['integer'],
format: 'int32',
allOf: [
{
minimum: 1,
Expand All @@ -142,6 +143,7 @@ describe(`Spectral rule: ${ruleId}`, () => {
in: 'query',
schema: {
type: 'integer',
format: 'int32',
minimum: 1,
},
},
Expand All @@ -166,6 +168,7 @@ describe(`Spectral rule: ${ruleId}`, () => {
in: 'query',
schema: {
type: 'integer',
format: 'int64',
maximum: 720,
},
},
Expand Down Expand Up @@ -216,6 +219,7 @@ describe(`Spectral rule: ${ruleId}`, () => {
in: 'query',
schema: {
type: 'integer',
format: 'int32',
allOf: [{ maximum: 10 }, { maximum: 25 }],
},
},
Expand All @@ -240,6 +244,7 @@ describe(`Spectral rule: ${ruleId}`, () => {
in: 'query',
schema: {
type: 'integer',
format: 'int32',
allOf: [
{
oneOf: [{ minimum: 0 }, { minimum: 3 }],
Expand Down Expand Up @@ -300,6 +305,7 @@ describe(`Spectral rule: ${ruleId}`, () => {
in: 'query',
schema: {
type: 'integer',
format: 'int64',
allOf: [{ minimum: 10 }, { maximum: 9 }],
},
},
Expand Down Expand Up @@ -328,6 +334,7 @@ describe(`Spectral rule: ${ruleId}`, () => {
in: 'query',
schema: {
type: ['integer'],
format: 'int32',
minimum: 16,
maximum: 15,
},
Expand Down Expand Up @@ -404,6 +411,7 @@ describe(`Spectral rule: ${ruleId}`, () => {
properties: {
size: {
type: 'integer',
format: 'int32',
description: 'no validation',
},
},
Expand Down Expand Up @@ -437,6 +445,7 @@ describe(`Spectral rule: ${ruleId}`, () => {
] = {
schema: {
type: 'integer',
format: 'int32',
description: 'no validation',
},
};
Expand Down Expand Up @@ -464,5 +473,97 @@ describe(`Spectral rule: ${ruleId}`, () => {
expect(results[i].path.join('.')).toBe(expectedPaths[i]);
}
});
it('Integer schema int32 minimum is out of safe range', async () => {
const testDocument = makeCopy(rootDocument);
testDocument.paths['/v1/movies'].post.parameters = [
{
name: 'max_movie_length',
in: 'query',
schema: {
type: 'integer',
format: 'int32',
minimum: -9007199254740991,
maximum: 2147483649,
},
},
];

const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(2);

const expectedPath = 'paths./v1/movies.post.parameters.0.schema';

const expectedMessages = [
`'minimum' value is out of safe range`,
`'maximum' value is out of safe range`,
];

for (let i = 0; i < results.length; i++) {
expect(results[i].code).toBe(ruleId);
expect(results[i].message).toBe(expectedMessages[i]);
expect(results[i].severity).toBe(expectedSeverity);
expect(results[i].path.join('.')).toBe(expectedPath);
}
});
it('Integer schema int64 minimum is out of safe range', async () => {
const testDocument = makeCopy(rootDocument);
testDocument.paths['/v1/movies'].post.parameters = [
{
name: 'max_movie_length',
in: 'query',
schema: {
type: 'integer',
format: 'int64',
minimum: -9007199254740992,
maximum: 9007199254740992,
},
},
];

const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(2);

const expectedPath = 'paths./v1/movies.post.parameters.0.schema';

const expectedMessages = [
`'minimum' value is out of safe range`,
`'maximum' value is out of safe range`,
];

for (let i = 0; i < results.length; i++) {
expect(results[i].code).toBe(ruleId);
expect(results[i].message).toBe(expectedMessages[i]);
expect(results[i].severity).toBe(expectedSeverity);
expect(results[i].path.join('.')).toBe(expectedPath);
}
});
it('Integer schema doesnt have format defined', async () => {
const testDocument = makeCopy(rootDocument);
testDocument.paths['/v1/movies'].post.parameters = [
{
name: 'max_movie_length',
in: 'query',
schema: {
type: 'integer',
minimum: 2,
maximum: 3,
},
},
];

const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(1);

const expectedPath = 'paths./v1/movies.post.parameters.0.schema';

const expectedMessage = 'Integer schemas should specify format as one of';

for (let i = 0; i < results.length; i++) {
expect(results[i].code).toBe(ruleId);
expect(results[i].message).toContain(expectedMessage);
expect(results[i].severity).toBe(expectedSeverity);
expect(results[i].path.join('.')).toBe(expectedPath);
}
});
});
});
1 change: 1 addition & 0 deletions packages/ruleset/test/test-utils/root-document.js
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,7 @@ module.exports = {
},
status_code: {
type: 'integer',
format: 'int32',
minimum: 0,
maximum: 599,
description: 'The HTTP status code.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,13 @@ components:
type: string
limit:
type: integer
format: int32
minimum: 1
maximum: 100
description: for pagination
offset:
type: integer
format: int32
minimum: 0
maximum: 1024000
description: for pagination
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,13 @@ components:
type: string
limit:
type: integer
format: int32
minimum: 1
maximum: 100
description: for pagination
offset:
type: integer
format: int32
minimum: 0
maximum: 1024000
description: for pagination
Expand Down