diff --git a/tools/spectral/ipa/__tests__/IPA121DateTimeFieldsMentionISO8601.test.js b/tools/spectral/ipa/__tests__/IPA121DateTimeFieldsMentionISO8601.test.js new file mode 100644 index 0000000000..ebe3f89e0b --- /dev/null +++ b/tools/spectral/ipa/__tests__/IPA121DateTimeFieldsMentionISO8601.test.js @@ -0,0 +1,261 @@ +import testRule from './__helpers__/testRule.js'; +import { DiagnosticSeverity } from '@stoplight/types'; + +testRule('xgen-IPA-121-date-time-fields-mention-iso-8601', [ + { + name: 'valid when date-time format mentions ISO 8601 in description', + document: { + components: { + schemas: { + TestSchema: { + properties: { + createdAt: { + type: 'string', + format: 'date-time', + description: 'The creation timestamp in ISO 8601 format in UTC.', + }, + updatedOn: { + type: 'string', + format: 'date-time', + description: 'When the resource was last updated. Uses ISO 8601 datetime format in UTC.', + }, + }, + }, + }, + parameters: { + TestParameter: { + name: 'createdAt', + in: 'query', + description: 'The creation timestamp in ISO 8601 format in UTC.', + schema: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'valid with non-date-time format', + document: { + components: { + schemas: { + TestSchema: { + properties: { + username: { + type: 'string', + description: 'The username for login.', + }, + age: { + type: 'integer', + description: 'Age in years.', + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'invalid when date-time format has no description', + document: { + components: { + schemas: { + TestSchema: { + properties: { + createdAt: { + type: 'string', + format: 'date-time', + }, + modifiedAt: { + type: 'string', + format: 'date-time', + description: 'The modification timestamp.', + }, + }, + }, + }, + parameters: { + TestParameter: { + name: 'createdAt', + in: 'query', + description: 'The creation timestamp', + schema: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + paths: { + '/resources': { + post: { + requestBody: { + content: { + 'application/json': { + schema: { + properties: { + $ref: '#/components/schemas/TestSchema', + }, + }, + }, + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-121-date-time-fields-mention-iso-8601', + message: + 'API producers must use ISO 8601 date-time format in UTC for all timestamps. Fields must note ISO 8601 and UTC in their description.', + path: ['components', 'schemas', 'TestSchema', 'properties', 'createdAt'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-121-date-time-fields-mention-iso-8601', + message: + 'API producers must use ISO 8601 date-time format in UTC for all timestamps. Fields must note ISO 8601 and UTC in their description.', + path: ['components', 'schemas', 'TestSchema', 'properties', 'modifiedAt'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-121-date-time-fields-mention-iso-8601', + message: + 'API producers must use ISO 8601 date-time format in UTC for all timestamps. Fields must note ISO 8601 and UTC in their description.', + path: ['components', 'parameters', 'TestParameter'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'exception', + document: { + components: { + schemas: { + TestSchema: { + properties: { + createdAt: { + type: 'string', + format: 'date-time', + description: 'The creation timestamp.', + 'x-xgen-IPA-exception': { + 'xgen-IPA-121-date-time-fields-mention-iso-8601': 'Legacy field format', + }, + }, + }, + }, + }, + parameters: { + TestParameter: { + name: 'createdAt', + in: 'query', + description: 'The creation timestamp', + schema: { + type: 'string', + format: 'date-time', + }, + 'x-xgen-IPA-exception': { + 'xgen-IPA-121-date-time-fields-mention-iso-8601': 'Legacy field format', + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'test with parameters in path operation', + document: { + paths: { + '/resources': { + get: { + parameters: [ + { + name: 'since', + in: 'query', + description: 'Filter resources created since this ISO 8601 timestamp in UTC', + schema: { + type: 'string', + format: 'date-time', + }, + }, + { + name: 'until', + in: 'query', + description: 'Filter resources created until this timestamp', // Missing ISO 8601 and UTC + schema: { + type: 'string', + format: 'date-time', + }, + }, + ], + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-121-date-time-fields-mention-iso-8601', + message: + 'API producers must use ISO 8601 date-time format in UTC for all timestamps. Fields must note ISO 8601 and UTC in their description.', + path: ['paths', '/resources', 'get', 'parameters', '1'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'test with requestBody properties', + document: { + paths: { + '/resources': { + post: { + requestBody: { + content: { + 'application/json': { + schema: { + properties: { + scheduledFor: { + type: 'string', + format: 'date-time', + description: 'When to schedule the job using ISO 8601 format in UTC.', + }, + expiresAt: { + type: 'string', + format: 'date-time', + description: 'When this resource expires.', // Missing ISO 8601 and UTC + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-121-date-time-fields-mention-iso-8601', + message: + 'API producers must use ISO 8601 date-time format in UTC for all timestamps. Fields must note ISO 8601 and UTC in their description.', + path: [ + 'paths', + '/resources', + 'post', + 'requestBody', + 'content', + 'application/json', + 'schema', + 'properties', + 'expiresAt', + ], + severity: DiagnosticSeverity.Warning, + }, + ], + }, +]); diff --git a/tools/spectral/ipa/ipa-spectral.yaml b/tools/spectral/ipa/ipa-spectral.yaml index 10d0ce7384..50fa4ea782 100644 --- a/tools/spectral/ipa/ipa-spectral.yaml +++ b/tools/spectral/ipa/ipa-spectral.yaml @@ -14,6 +14,7 @@ extends: - ./rulesets/IPA-117.yaml - ./rulesets/IPA-118.yaml - ./rulesets/IPA-119.yaml + - ./rulesets/IPA-121.yaml - ./rulesets/IPA-123.yaml - ./rulesets/IPA-125.yaml diff --git a/tools/spectral/ipa/rulesets/IPA-121.yaml b/tools/spectral/ipa/rulesets/IPA-121.yaml new file mode 100644 index 0000000000..60d95f94fd --- /dev/null +++ b/tools/spectral/ipa/rulesets/IPA-121.yaml @@ -0,0 +1,22 @@ +# IPA-121: Datetime +# http://go/ipa/121 + +functions: + - IPA121DateTimeFieldsMentionISO8601 + +rules: + xgen-IPA-121-date-time-fields-mention-iso-8601: + description: | + Fields with format="date-time" should mention ISO 8601 and UTC in their description. + It collects adoption metrics at schema property level and parameter level + message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-121-date-time-fields-mention-iso-8601' + given: + - $.paths..parameters[*] + - $.components.parameters[*] + - $.components.schemas..properties[*] + - $.paths..requestBody..schema..properties[*] + - $.paths..responses..schema..properties[*] + resolved: false + severity: warn + then: + function: IPA121DateTimeFieldsMentionISO8601 diff --git a/tools/spectral/ipa/rulesets/README.md b/tools/spectral/ipa/rulesets/README.md index d1754972d2..ff9890bda9 100644 --- a/tools/spectral/ipa/rulesets/README.md +++ b/tools/spectral/ipa/rulesets/README.md @@ -802,6 +802,18 @@ All cloudProviderEnumValues should be listed in the enum array. +### IPA-121 + +Rules are based on [http://go/ipa/IPA-121](http://go/ipa/IPA-121). + +#### xgen-IPA-121-date-time-fields-mention-iso-8601 + + ![warn](https://img.shields.io/badge/warning-yellow) +Fields with format="date-time" should mention ISO 8601 and UTC in their description. +It collects adoption metrics at schema property level and parameter level + + + ### IPA-123 Rules are based on [http://go/ipa/IPA-123](http://go/ipa/IPA-123). diff --git a/tools/spectral/ipa/rulesets/functions/IPA121DateTimeFieldsMentionISO8601.js b/tools/spectral/ipa/rulesets/functions/IPA121DateTimeFieldsMentionISO8601.js new file mode 100644 index 0000000000..2dac2f5355 --- /dev/null +++ b/tools/spectral/ipa/rulesets/functions/IPA121DateTimeFieldsMentionISO8601.js @@ -0,0 +1,36 @@ +import { hasException } from './utils/exceptions.js'; +import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js'; + +const RULE_NAME = 'xgen-IPA-121-date-time-fields-mention-iso-8601'; +const ERROR_MESSAGE = + 'API producers must use ISO 8601 date-time format in UTC for all timestamps. Fields must note ISO 8601 and UTC in their description.'; + +export default (input, options, { path }) => { + const fieldType = path[path.length - 2]; + + if (hasException(input, RULE_NAME)) { + collectException(input, RULE_NAME, path); + return; + } + + let format; + let description = input.description; + if (fieldType === 'parameters') { + format = input.schema?.format; + } else if (fieldType === 'properties') { + format = input.format; + } + + if (format === 'date-time') { + if (!description?.includes('ISO 8601') && !description?.includes('UTC')) { + return collectAndReturnViolation(path, RULE_NAME, [ + { + path, + message: ERROR_MESSAGE, + }, + ]); + } + + collectAdoption(path, RULE_NAME); + } +};