Skip to content
Snippets Groups Projects
Commit 7cffa147 authored by David Schwarzmann's avatar David Schwarzmann
Browse files

build(api#87): Add custom spectral rule for checking for pagination

parent 3db47f59
No related branches found
No related tags found
No related merge requests found
...@@ -3,6 +3,11 @@ extends: ...@@ -3,6 +3,11 @@ extends:
# - https://italia.github.io/api-oas-checker/spectral.yml # - https://italia.github.io/api-oas-checker/spectral.yml
- https://italia.github.io/api-oas-checker/spectral-generic.yml - https://italia.github.io/api-oas-checker/spectral-generic.yml
- https://italia.github.io/api-oas-checker/spectral-security.yml - https://italia.github.io/api-oas-checker/spectral-security.yml
functionsDir: './functions'
functions:
- pagination-response
rules: rules:
no-default-additionalProperties: hint no-default-additionalProperties: hint
string-maxlength: hint string-maxlength: hint
...@@ -17,3 +22,11 @@ rules: ...@@ -17,3 +22,11 @@ rules:
use-semver: off use-semver: off
patch-media-type: off patch-media-type: off
check-for-pagination:
description: An operation that returns a list and less than 4 properties could perhaps support pagination.
message: '{{error}}'
severity: warn
given:
- $.paths.*[get]
then:
function: pagination-response
const RESPONSE_ATTRIBUTES = ['offset', 'totalCount', 'count']
const QUERY_PARAMS = ['limit', 'offset']
const MINIMUM_ATTR_COUNT = 4
module.exports = (operation, _opts, paths) => {
// operation should be a get or post operation
if (operation === null || typeof operation !== 'object') {
return []
}
const path = paths.given || []
// responses is required property of an operation in OpenAPI 2.0, so if
// isn't present this will be flagged elsewhere -- just return
if (!operation.responses || typeof operation.responses !== 'object') {
return []
}
// Find success response code
const resp = Object.keys(operation.responses)
.find((code) => code.startsWith('2'))
// No success response will be flagged elsewhere, just return
if (!resp) {
return []
}
// available content types
const content = operation.responses[resp].content
// Get the schema of the success response
const responseSchema = content[Object.keys(content)[0]].schema || {}
const errors = []
const responseHasArray = Object.values(responseSchema.properties || {})
.some((prop) => prop.type === 'array')
const operationId = operation.operationId ? `'${operation.operationId }'` : ''
if (responseHasArray && Object.keys(responseSchema.properties).length <= MINIMUM_ATTR_COUNT) {
RESPONSE_ATTRIBUTES.forEach((entry) => {
if (!Object.keys(responseSchema.properties).includes(entry)) {
errors.push({
message: `Operation ${operationId} might be pageable. Property '${entry}' is missing.`,
path,
})
}
})
if (operation.parameters) {
const queryParams = operation.parameters.filter((param) => param.in === 'query')
if (queryParams) {
const names = queryParams.map((param) => param.name)
QUERY_PARAMS.forEach((e) => {
if (!names.includes(e)) {
errors.push({
message: `Operation ${operationId} might be pageable. Query parameter '${e}' is missing.`,
path,
})
}
})
}
} else {
errors.push({
message: `Operation ${operationId} might be pageable. Query parameters are missing.`,
path,
})
}
}
return errors
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment