From d2c6d3d5fe65a90fe933f6de49c2a746b8f3aea1 Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Wed, 12 Mar 2025 09:36:30 +0000 Subject: [PATCH 1/4] IPA 106: Create : A Request object must include only input fields --- ...teMethodRequestHasNoReadOnlyFields.test.js | 226 ++++++++++++++++++ tools/spectral/ipa/rulesets/IPA-106.yaml | 9 + .../createMethodRequestHasNoReadonlyFields.js | 40 ++++ .../getMethodResponseHasNoInputFields.js | 12 +- .../rulesets/functions/utils/schemaUtils.js | 49 ++++ 5 files changed, 326 insertions(+), 10 deletions(-) create mode 100644 tools/spectral/ipa/__tests__/createMethodRequestHasNoReadOnlyFields.test.js create mode 100644 tools/spectral/ipa/rulesets/functions/createMethodRequestHasNoReadonlyFields.js diff --git a/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadOnlyFields.test.js b/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadOnlyFields.test.js new file mode 100644 index 0000000000..d90a85f3f3 --- /dev/null +++ b/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadOnlyFields.test.js @@ -0,0 +1,226 @@ +import testRule from './__helpers__/testRule'; +import { DiagnosticSeverity } from '@stoplight/types'; + +const componentSchemas = { + schemas: { + SchemaWithReadOnly: { + type: 'object', + properties: { + id: { + type: 'string', + readOnly: true + }, + name: { + type: 'string' + } + } + }, + SchemaWithoutReadOnly: { + type: 'object', + properties: { + name: { + type: 'string' + }, + description: { + type: 'string' + } + } + }, + NestedSchemaWithReadOnly: { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + userId: { + type: 'string', + readOnly: true + }, + username: { + type: 'string' + } + } + } + } + }, + ArraySchemaWithReadOnly: { + type: 'object', + properties: { + items: { + type: 'array', + items: { + type: 'object', + properties: { + itemId: { + type: 'string', + readOnly: true + }, + itemName: { + type: 'string' + } + } + } + } + } + } + } +}; + +testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ + { + name: 'valid methods - no readOnly fields', + document: { + components: componentSchemas, + paths: { + '/valid-resource': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/SchemaWithoutReadOnly' + } + } + } + } + } + } + } + }, + errors: [] + }, + { + name: 'valid methods - custom method can have readOnly fields', + document: { + components: componentSchemas, + paths: { + '/resource:customAction': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/SchemaWithReadOnly' + } + } + } + } + } + } + } + }, + errors: [] + }, + { + name: 'invalid methods - direct readOnly field', + document: { + components: componentSchemas, + paths: { + '/invalid-resource': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/SchemaWithReadOnly' + } + } + } + } + } + } + } + }, + errors: [ + { + code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', + message: 'The create method request object must not include input fields (readOnly properties). Found readOnly property at: id http://go/ipa/106', + path: ['paths', '/invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], + severity: DiagnosticSeverity.Warning, + } + ] + }, + { + name: 'invalid methods - nested readOnly field', + document: { + components: componentSchemas, + paths: { + '/nested-invalid-resource': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/NestedSchemaWithReadOnly' + } + } + } + } + } + } + } + }, + errors: [ + { + code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', + message: 'The create method request object must not include input fields (readOnly properties). Found readOnly property at: user.userId http://go/ipa/106', + path: ['paths', '/nested-invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], + severity: DiagnosticSeverity.Warning, + } + ] + }, + { + name: 'invalid methods - array with readOnly field', + document: { + components: componentSchemas, + paths: { + '/array-invalid-resource': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/ArraySchemaWithReadOnly' + } + } + } + } + } + } + } + }, + errors: [ + { + code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', + message: 'The create method request object must not include input fields (readOnly properties). Found readOnly property at: items.items.itemId http://go/ipa/106', + path: ['paths', '/array-invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], + severity: DiagnosticSeverity.Warning, + } + ] + }, + { + name: 'methods with exceptions', + document: { + components: componentSchemas, + paths: { + '/excepted-resource': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/SchemaWithReadOnly' + }, + 'x-xgen-IPA-exception': { + 'xgen-IPA-106-create-method-request-has-no-readonly-fields': 'Reason' + } + } + } + } + } + } + } + }, + errors: [] + } +]); \ No newline at end of file diff --git a/tools/spectral/ipa/rulesets/IPA-106.yaml b/tools/spectral/ipa/rulesets/IPA-106.yaml index 238be3dbe9..accc35b408 100644 --- a/tools/spectral/ipa/rulesets/IPA-106.yaml +++ b/tools/spectral/ipa/rulesets/IPA-106.yaml @@ -5,6 +5,7 @@ functions: - createMethodRequestBodyIsRequestSuffixedObject - createMethodShouldNotHaveQueryParameters - createMethodRequestBodyIsGetResponse + - createMethodRequestHasNoReadOnlyFields rules: xgen-IPA-106-create-method-request-body-is-request-suffixed-object: @@ -30,3 +31,11 @@ rules: then: field: '@key' function: 'createMethodRequestBodyIsGetResponse' + xgen-IPA-106-create-method-request-has-no-readonly-fields: + description: 'Create method Request object must not include fields with readOnly:true. http://go/ipa/106' + message: '{{error}} http://go/ipa/106' + severity: warn + given: '$.paths[*].post.requestBody.content' + then: + field: '@key' + function: 'createMethodRequestHasNoReadonlyFields' \ No newline at end of file diff --git a/tools/spectral/ipa/rulesets/functions/createMethodRequestHasNoReadonlyFields.js b/tools/spectral/ipa/rulesets/functions/createMethodRequestHasNoReadonlyFields.js new file mode 100644 index 0000000000..6bcdfd541e --- /dev/null +++ b/tools/spectral/ipa/rulesets/functions/createMethodRequestHasNoReadonlyFields.js @@ -0,0 +1,40 @@ +import { isCustomMethodIdentifier } from './utils/resourceEvaluation.js'; +import { resolveObject } from './utils/componentUtils.js'; +import { hasException } from './utils/exceptions.js'; +import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js'; +import { findPropertiesByAttribute } from './utils/schemaUtils.js'; + +const RULE_NAME = 'xgen-IPA-106-create-method-request-has-no-readonly-fields'; +const ERROR_MESSAGE = 'The Create method request object must not include input fields (readOnly properties).'; + +export default (input, _, { path, documentInventory }) => { + const resourcePath = path[1]; + const oas = documentInventory.resolved; + let mediaType = input; + + if (isCustomMethodIdentifier(resourcePath) || !mediaType.endsWith('json')) { + return; + } + + const requestContentPerMediaType = resolveObject(oas, path); + if (!requestContentPerMediaType || !requestContentPerMediaType.schema) { + return; + } + + if (hasException(requestContentPerMediaType, RULE_NAME)) { + collectException(requestContentPerMediaType, RULE_NAME, path); + return; + } + + const errors = checkViolationsAndReturnErrors(requestContentPerMediaType, path); + + if (errors.length !== 0) { + return collectAndReturnViolation(path, RULE_NAME, errors); + } + + collectAdoption(path, RULE_NAME); +}; + +function checkViolationsAndReturnErrors(contentPerMediaType, path) { + return findPropertiesByAttribute(contentPerMediaType.schema, 'readOnly', path, [], ERROR_MESSAGE); +} diff --git a/tools/spectral/ipa/rulesets/functions/getMethodResponseHasNoInputFields.js b/tools/spectral/ipa/rulesets/functions/getMethodResponseHasNoInputFields.js index c8236053ab..5fe7e5a012 100644 --- a/tools/spectral/ipa/rulesets/functions/getMethodResponseHasNoInputFields.js +++ b/tools/spectral/ipa/rulesets/functions/getMethodResponseHasNoInputFields.js @@ -7,6 +7,7 @@ import { isSingletonResource, } from './utils/resourceEvaluation.js'; import { resolveObject } from './utils/componentUtils.js'; +import { findPropertiesByAttribute } from './utils/schemaUtils.js'; const RULE_NAME = 'xgen-IPA-104-get-method-response-has-no-input-fields'; const ERROR_MESSAGE = 'The get method response object must not include output fields (writeOnly properties).'; @@ -43,14 +44,5 @@ export default (input, _, { path, documentInventory }) => { }; function checkViolationsAndReturnErrors(contentPerMediaType, path) { - const schema = contentPerMediaType.schema; - const properties = schema.properties; - if (properties) { - for (const [value] of Object.entries(properties)) { - if (properties[value].writeOnly) { - return [{ path, message: ERROR_MESSAGE }]; - } - } - } - return []; + return findPropertiesByAttribute(contentPerMediaType.schema, 'writeOnly', path, [], ERROR_MESSAGE); } diff --git a/tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js b/tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js index ae10e80e63..fb9eee9ed9 100644 --- a/tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js +++ b/tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js @@ -15,3 +15,52 @@ export function getSchemaPathFromEnumPath(path) { } return path.slice(0, enumIndex); } + +/** + * Recursively searches a schema to find properties with a specific attribute + * + * @param {Object} schema - The schema to check + * @param {string} attributeName - The attribute to check for (e.g. 'readOnly', 'writeOnly') + * @param {Array} path - The path to the current schema in the document + * @param {Array} errors - Accumulator for errors found + * @param {string} errorMessage - The base error message to use + * @param {Array} propPath - The current property path (for error messages) + * @returns {Array} The accumulated errors + */ +export function findPropertiesByAttribute(schema, attributeName, path, errors = [], errorMessage, propPath = []) { + if (!schema || typeof schema !== 'object') { + return errors; + } + + // Check if this schema has the attribute set to true + if (schema[attributeName] === true) { + errors.push({ + path, + message: `${errorMessage} Found ${attributeName} property at: ${propPath.join('.')}`, + }); + return errors; + } + + // Check properties in object schemas + if (schema.properties) { + for (const [propName, propSchema] of Object.entries(schema.properties)) { + findPropertiesByAttribute(propSchema, attributeName, path, errors, errorMessage, [...propPath, propName]); + } + } + + // Check items in array schemas + if (schema.items) { + findPropertiesByAttribute(schema.items, attributeName, path, errors, errorMessage, [...propPath, 'items']); + } + + // Check allOf, anyOf, oneOf schemas + ['allOf', 'anyOf', 'oneOf'].forEach((combiner) => { + if (Array.isArray(schema[combiner])) { + schema[combiner].forEach((subSchema, index) => { + findPropertiesByAttribute(subSchema, attributeName, path, errors, errorMessage, [...propPath, combiner, index]); + }); + } + }); + + return errors; +} \ No newline at end of file From 0e988e5dd6c9e73fc1c4404b34cdc6d33a7ea36f Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Wed, 12 Mar 2025 09:46:36 +0000 Subject: [PATCH 2/4] IPA 106: Create : A Request object must include only input fields --- ...eMethodRequestHasNoReadonlyFields.test.js} | 6 +- .../getMethodResponseHasNoInputFields.test.js | 4 +- .../ipa/__tests__/utils/schemaUtils.test.js | 247 ++++++++++++++++++ tools/spectral/ipa/rulesets/IPA-106.yaml | 2 +- tools/spectral/ipa/rulesets/README.md | 1 + 5 files changed, 254 insertions(+), 6 deletions(-) rename tools/spectral/ipa/__tests__/{createMethodRequestHasNoReadOnlyFields.test.js => createMethodRequestHasNoReadonlyFields.test.js} (96%) create mode 100644 tools/spectral/ipa/__tests__/utils/schemaUtils.test.js diff --git a/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadOnlyFields.test.js b/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadonlyFields.test.js similarity index 96% rename from tools/spectral/ipa/__tests__/createMethodRequestHasNoReadOnlyFields.test.js rename to tools/spectral/ipa/__tests__/createMethodRequestHasNoReadonlyFields.test.js index d90a85f3f3..0ed61e9c04 100644 --- a/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadOnlyFields.test.js +++ b/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadonlyFields.test.js @@ -134,7 +134,7 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ errors: [ { code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', - message: 'The create method request object must not include input fields (readOnly properties). Found readOnly property at: id http://go/ipa/106', + message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: id http://go/ipa/106', path: ['paths', '/invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], severity: DiagnosticSeverity.Warning, } @@ -163,7 +163,7 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ errors: [ { code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', - message: 'The create method request object must not include input fields (readOnly properties). Found readOnly property at: user.userId http://go/ipa/106', + message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: user.userId http://go/ipa/106', path: ['paths', '/nested-invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], severity: DiagnosticSeverity.Warning, } @@ -192,7 +192,7 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ errors: [ { code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', - message: 'The create method request object must not include input fields (readOnly properties). Found readOnly property at: items.items.itemId http://go/ipa/106', + message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: items.items.itemId http://go/ipa/106', path: ['paths', '/array-invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], severity: DiagnosticSeverity.Warning, } diff --git a/tools/spectral/ipa/__tests__/getMethodResponseHasNoInputFields.test.js b/tools/spectral/ipa/__tests__/getMethodResponseHasNoInputFields.test.js index bd6afda00a..f4e6928c91 100644 --- a/tools/spectral/ipa/__tests__/getMethodResponseHasNoInputFields.test.js +++ b/tools/spectral/ipa/__tests__/getMethodResponseHasNoInputFields.test.js @@ -131,7 +131,7 @@ testRule('xgen-IPA-104-get-method-response-has-no-input-fields', [ { code: 'xgen-IPA-104-get-method-response-has-no-input-fields', message: - 'The get method response object must not include output fields (writeOnly properties). http://go/ipa/104', + 'The get method response object must not include output fields (writeOnly properties). Found writeOnly property at: name http://go/ipa/104', path: [ 'paths', '/resource/{id}', @@ -146,7 +146,7 @@ testRule('xgen-IPA-104-get-method-response-has-no-input-fields', [ { code: 'xgen-IPA-104-get-method-response-has-no-input-fields', message: - 'The get method response object must not include output fields (writeOnly properties). http://go/ipa/104', + 'The get method response object must not include output fields (writeOnly properties). Found writeOnly property at: name http://go/ipa/104', path: [ 'paths', '/resource/{id}/singleton', diff --git a/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js b/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js new file mode 100644 index 0000000000..9f29f95ff5 --- /dev/null +++ b/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js @@ -0,0 +1,247 @@ +// schemaUtils.test.js +import { findPropertiesByAttribute } from '../../rulesets/functions/utils/schemaUtils'; +import { describe, expect, it } from '@jest/globals'; + +describe('findPropertiesByAttribute', () => { + const mockPath = ['paths', '/resources', 'get', 'responses', '200', 'content', 'application/json']; + const errorMessage = 'Test error message'; + + it('handles primitive values', () => { + expect(findPropertiesByAttribute(null, 'readOnly', mockPath, [], errorMessage)).toEqual([]); + expect(findPropertiesByAttribute(undefined, 'readOnly', mockPath, [], errorMessage)).toEqual([]); + expect(findPropertiesByAttribute('string', 'readOnly', mockPath, [], errorMessage)).toEqual([]); + expect(findPropertiesByAttribute(123, 'readOnly', mockPath, [], errorMessage)).toEqual([]); + expect(findPropertiesByAttribute(true, 'readOnly', mockPath, [], errorMessage)).toEqual([]); + }); + + it('detects direct attribute match', () => { + const schema = { + type: 'string', + readOnly: true + }; + + const errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); + + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual({ + path: mockPath, + message: `${errorMessage} Found readOnly property at: ` + }); + }); + + it('detects properties with the specified attribute', () => { + const schema = { + type: 'object', + properties: { + id: { + type: 'string', + readOnly: true + }, + name: { + type: 'string' + }, + password: { + type: 'string', + writeOnly: true + } + } + }; + + // Testing readOnly detection + let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); + expect(errors).toHaveLength(1); + expect(errors[0].message).toContain('Found readOnly property at: id'); + + // Testing writeOnly detection + errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage); + expect(errors).toHaveLength(1); + expect(errors[0].message).toContain('Found writeOnly property at: password'); + }); + + it('detects nested properties with the specified attribute', () => { + const schema = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + id: { + type: 'string', + readOnly: true + }, + credentials: { + type: 'object', + properties: { + password: { + type: 'string', + writeOnly: true + } + } + } + } + } + } + }; + + // Testing deep readOnly detection + let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); + expect(errors).toHaveLength(1); + expect(errors[0].message).toContain('Found readOnly property at: user.id'); + + // Testing deep writeOnly detection + errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage); + expect(errors).toHaveLength(1); + expect(errors[0].message).toContain('Found writeOnly property at: user.credentials.password'); + }); + + it('detects properties in array items', () => { + const schema = { + type: 'object', + properties: { + items: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + readOnly: true + }, + secret: { + type: 'string', + writeOnly: true + } + } + } + } + } + }; + + // Testing readOnly in array items + let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); + expect(errors).toHaveLength(1); + expect(errors[0].message).toContain('Found readOnly property at: items.items.id'); + + // Testing writeOnly in array items + errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage); + expect(errors).toHaveLength(1); + expect(errors[0].message).toContain('Found writeOnly property at: items.items.secret'); + }); + + it('detects properties in schema combiners', () => { + const schema = { + allOf: [ + { + type: 'object', + properties: { + id: { + type: 'string', + readOnly: true + } + } + } + ], + anyOf: [ + { + type: 'object', + properties: { + key: { + type: 'string', + writeOnly: true + } + } + } + ], + oneOf: [ + { + type: 'object' + }, + { + type: 'object', + properties: { + token: { + type: 'string', + readOnly: true + } + } + } + ] + }; + + // Testing readOnly in combiners + let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); + expect(errors).toHaveLength(2); + expect(errors[0].message).toContain('Found readOnly property at: allOf.0.id'); + expect(errors[1].message).toContain('Found readOnly property at: oneOf.1.token'); + + // Testing writeOnly in combiners + errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage); + expect(errors).toHaveLength(1); + expect(errors[0].message).toContain('Found writeOnly property at: anyOf.0.key'); + }); + + it('correctly accumulates multiple errors', () => { + const schema = { + type: 'object', + properties: { + id: { + type: 'string', + readOnly: true + }, + nested: { + type: 'object', + properties: { + innerId: { + type: 'string', + readOnly: true + } + } + }, + items: { + type: 'array', + items: { + readOnly: true + } + } + } + }; + + const errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); + + expect(errors).toHaveLength(3); + expect(errors[0].message).toContain('Found readOnly property at: id'); + expect(errors[1].message).toContain('Found readOnly property at: nested.innerId'); + expect(errors[2].message).toContain('Found readOnly property at: items.items'); + }); + + it('handles empty objects', () => { + const schema = {}; + const errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); + expect(errors).toHaveLength(0); + }); + + it('handles schemas with no matching attributes', () => { + const schema = { + type: 'object', + properties: { + id: { type: 'string' }, + name: { type: 'string' }, + nested: { + type: 'object', + properties: { + value: { type: 'number' } + } + }, + items: { + type: 'array', + items: { + type: 'string' + } + } + } + }; + + const errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); + expect(errors).toHaveLength(0); + }); +}); \ No newline at end of file diff --git a/tools/spectral/ipa/rulesets/IPA-106.yaml b/tools/spectral/ipa/rulesets/IPA-106.yaml index 7fce483354..0d25a4a3a1 100644 --- a/tools/spectral/ipa/rulesets/IPA-106.yaml +++ b/tools/spectral/ipa/rulesets/IPA-106.yaml @@ -5,7 +5,7 @@ functions: - createMethodRequestBodyIsRequestSuffixedObject - createMethodShouldNotHaveQueryParameters - createMethodRequestBodyIsGetResponse - - createMethodRequestHasNoReadOnlyFields + - createMethodRequestHasNoReadonlyFields rules: xgen-IPA-106-create-method-request-body-is-request-suffixed-object: diff --git a/tools/spectral/ipa/rulesets/README.md b/tools/spectral/ipa/rulesets/README.md index 8cb53308eb..cdb830ceed 100644 --- a/tools/spectral/ipa/rulesets/README.md +++ b/tools/spectral/ipa/rulesets/README.md @@ -60,6 +60,7 @@ For rule definitions, see [IPA-106.yaml](https://github.com/mongodb/openapi/blob | xgen-IPA-106-create-method-request-body-is-get-method-response | Request body content of the Create method and response content of the Get method should refer to the same resource. readOnly/writeOnly properties will be ignored. http://go/ipa/106 | warn | +| xgen-IPA-106-create-method-request-has-no-readonly-fields | Create method Request object must not include fields with readOnly:true. http://go/ipa/106 | warn | ### IPA-108 From 163614ede8b47ac244a03104f6972696475e3e68 Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Wed, 12 Mar 2025 09:56:03 +0000 Subject: [PATCH 3/4] IPA 106: Create : A Request object must include only input fields --- ...teMethodRequestHasNoReadonlyFields.test.js | 23 +++++++++++++--- .../getMethodResponseHasNoInputFields.test.js | 4 +-- .../ipa/__tests__/utils/schemaUtils.test.js | 26 +++++++++---------- .../rulesets/functions/utils/schemaUtils.js | 3 ++- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadonlyFields.test.js b/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadonlyFields.test.js index 0ed61e9c04..13c3cc4d79 100644 --- a/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadonlyFields.test.js +++ b/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadonlyFields.test.js @@ -80,6 +80,11 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ schema: { $ref: '#/components/schemas/SchemaWithoutReadOnly' } + }, + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: "string" + } } } } @@ -124,6 +129,12 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ schema: { $ref: '#/components/schemas/SchemaWithReadOnly' } + }, + 'application/vnd.atlas.2024-01-01+json': { + schema: { + type: "string", + readOnly: true + } } } } @@ -134,9 +145,15 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ errors: [ { code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', - message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: id http://go/ipa/106', + message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: id. http://go/ipa/106', path: ['paths', '/invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', + message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at one of the inline schemas. http://go/ipa/106', + path: ['paths', '/invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2024-01-01+json'], + severity: DiagnosticSeverity.Warning, } ] }, @@ -163,7 +180,7 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ errors: [ { code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', - message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: user.userId http://go/ipa/106', + message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: user.userId. http://go/ipa/106', path: ['paths', '/nested-invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], severity: DiagnosticSeverity.Warning, } @@ -192,7 +209,7 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ errors: [ { code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', - message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: items.items.itemId http://go/ipa/106', + message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: items.items.itemId. http://go/ipa/106', path: ['paths', '/array-invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], severity: DiagnosticSeverity.Warning, } diff --git a/tools/spectral/ipa/__tests__/getMethodResponseHasNoInputFields.test.js b/tools/spectral/ipa/__tests__/getMethodResponseHasNoInputFields.test.js index f4e6928c91..9cd8dc171f 100644 --- a/tools/spectral/ipa/__tests__/getMethodResponseHasNoInputFields.test.js +++ b/tools/spectral/ipa/__tests__/getMethodResponseHasNoInputFields.test.js @@ -131,7 +131,7 @@ testRule('xgen-IPA-104-get-method-response-has-no-input-fields', [ { code: 'xgen-IPA-104-get-method-response-has-no-input-fields', message: - 'The get method response object must not include output fields (writeOnly properties). Found writeOnly property at: name http://go/ipa/104', + 'The get method response object must not include output fields (writeOnly properties). Found writeOnly property at: name. http://go/ipa/104', path: [ 'paths', '/resource/{id}', @@ -146,7 +146,7 @@ testRule('xgen-IPA-104-get-method-response-has-no-input-fields', [ { code: 'xgen-IPA-104-get-method-response-has-no-input-fields', message: - 'The get method response object must not include output fields (writeOnly properties). Found writeOnly property at: name http://go/ipa/104', + 'The get method response object must not include output fields (writeOnly properties). Found writeOnly property at: name. http://go/ipa/104', path: [ 'paths', '/resource/{id}/singleton', diff --git a/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js b/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js index 9f29f95ff5..c9c2cbff49 100644 --- a/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js +++ b/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js @@ -25,7 +25,7 @@ describe('findPropertiesByAttribute', () => { expect(errors).toHaveLength(1); expect(errors[0]).toEqual({ path: mockPath, - message: `${errorMessage} Found readOnly property at: ` + message: `${errorMessage} Found readOnly property at one of the inline schemas.` }); }); @@ -50,12 +50,12 @@ describe('findPropertiesByAttribute', () => { // Testing readOnly detection let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); - expect(errors[0].message).toContain('Found readOnly property at: id'); + expect(errors[0].message).toContain('Found readOnly property at: id.'); // Testing writeOnly detection errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); - expect(errors[0].message).toContain('Found writeOnly property at: password'); + expect(errors[0].message).toContain('Found writeOnly property at: password.'); }); it('detects nested properties with the specified attribute', () => { @@ -86,12 +86,12 @@ describe('findPropertiesByAttribute', () => { // Testing deep readOnly detection let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); - expect(errors[0].message).toContain('Found readOnly property at: user.id'); + expect(errors[0].message).toContain('Found readOnly property at: user.id.'); // Testing deep writeOnly detection errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); - expect(errors[0].message).toContain('Found writeOnly property at: user.credentials.password'); + expect(errors[0].message).toContain('Found writeOnly property at: user.credentials.password.'); }); it('detects properties in array items', () => { @@ -120,12 +120,12 @@ describe('findPropertiesByAttribute', () => { // Testing readOnly in array items let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); - expect(errors[0].message).toContain('Found readOnly property at: items.items.id'); + expect(errors[0].message).toContain('Found readOnly property at: items.items.id.'); // Testing writeOnly in array items errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); - expect(errors[0].message).toContain('Found writeOnly property at: items.items.secret'); + expect(errors[0].message).toContain('Found writeOnly property at: items.items.secret.'); }); it('detects properties in schema combiners', () => { @@ -171,13 +171,13 @@ describe('findPropertiesByAttribute', () => { // Testing readOnly in combiners let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(2); - expect(errors[0].message).toContain('Found readOnly property at: allOf.0.id'); - expect(errors[1].message).toContain('Found readOnly property at: oneOf.1.token'); + expect(errors[0].message).toContain('Found readOnly property at: allOf.0.id.'); + expect(errors[1].message).toContain('Found readOnly property at: oneOf.1.token.'); // Testing writeOnly in combiners errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); - expect(errors[0].message).toContain('Found writeOnly property at: anyOf.0.key'); + expect(errors[0].message).toContain('Found writeOnly property at: anyOf.0.key.'); }); it('correctly accumulates multiple errors', () => { @@ -209,9 +209,9 @@ describe('findPropertiesByAttribute', () => { const errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(3); - expect(errors[0].message).toContain('Found readOnly property at: id'); - expect(errors[1].message).toContain('Found readOnly property at: nested.innerId'); - expect(errors[2].message).toContain('Found readOnly property at: items.items'); + expect(errors[0].message).toContain('Found readOnly property at: id.'); + expect(errors[1].message).toContain('Found readOnly property at: nested.innerId.'); + expect(errors[2].message).toContain('Found readOnly property at: items.items.'); }); it('handles empty objects', () => { diff --git a/tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js b/tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js index 66f4ddd284..e5abc6c792 100644 --- a/tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js +++ b/tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js @@ -47,7 +47,8 @@ export function findPropertiesByAttribute(schema, attributeName, path, errors = if (schema[attributeName] === true) { errors.push({ path, - message: `${errorMessage} Found ${attributeName} property at: ${propPath.join('.')}`, + message: propPath.length > 0 ? `${errorMessage} Found ${attributeName} property at: ${propPath.join('.')}.`: + `${errorMessage} Found ${attributeName} property at one of the inline schemas.`, }); return errors; } From 00c1d5c70ead955ebf3df97bd08810e1679cda42 Mon Sep 17 00:00:00 2001 From: Yeliz Henden Date: Wed, 12 Mar 2025 09:58:39 +0000 Subject: [PATCH 4/4] prettier fix --- ...teMethodRequestHasNoReadonlyFields.test.js | 206 ++++++++++-------- .../ipa/__tests__/utils/schemaUtils.test.js | 130 +++++------ tools/spectral/ipa/rulesets/IPA-106.yaml | 2 +- .../rulesets/functions/utils/schemaUtils.js | 8 +- 4 files changed, 183 insertions(+), 163 deletions(-) diff --git a/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadonlyFields.test.js b/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadonlyFields.test.js index 13c3cc4d79..657e1937d1 100644 --- a/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadonlyFields.test.js +++ b/tools/spectral/ipa/__tests__/createMethodRequestHasNoReadonlyFields.test.js @@ -8,23 +8,23 @@ const componentSchemas = { properties: { id: { type: 'string', - readOnly: true + readOnly: true, }, name: { - type: 'string' - } - } + type: 'string', + }, + }, }, SchemaWithoutReadOnly: { type: 'object', properties: { name: { - type: 'string' + type: 'string', }, description: { - type: 'string' - } - } + type: 'string', + }, + }, }, NestedSchemaWithReadOnly: { type: 'object', @@ -34,14 +34,14 @@ const componentSchemas = { properties: { userId: { type: 'string', - readOnly: true + readOnly: true, }, username: { - type: 'string' - } - } - } - } + type: 'string', + }, + }, + }, + }, }, ArraySchemaWithReadOnly: { type: 'object', @@ -53,17 +53,17 @@ const componentSchemas = { properties: { itemId: { type: 'string', - readOnly: true + readOnly: true, }, itemName: { - type: 'string' - } - } - } - } - } - } - } + type: 'string', + }, + }, + }, + }, + }, + }, + }, }; testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ @@ -78,21 +78,21 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ content: { 'application/vnd.atlas.2023-01-01+json': { schema: { - $ref: '#/components/schemas/SchemaWithoutReadOnly' - } + $ref: '#/components/schemas/SchemaWithoutReadOnly', + }, }, 'application/vnd.atlas.2024-01-01+json': { schema: { - type: "string" - } - } - } - } - } - } - } + type: 'string', + }, + }, + }, + }, + }, + }, + }, }, - errors: [] + errors: [], }, { name: 'valid methods - custom method can have readOnly fields', @@ -105,16 +105,16 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ content: { 'application/vnd.atlas.2023-01-01+json': { schema: { - $ref: '#/components/schemas/SchemaWithReadOnly' - } - } - } - } - } - } - } + $ref: '#/components/schemas/SchemaWithReadOnly', + }, + }, + }, + }, + }, + }, + }, }, - errors: [] + errors: [], }, { name: 'invalid methods - direct readOnly field', @@ -127,35 +127,37 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ content: { 'application/vnd.atlas.2023-01-01+json': { schema: { - $ref: '#/components/schemas/SchemaWithReadOnly' - } + $ref: '#/components/schemas/SchemaWithReadOnly', + }, }, 'application/vnd.atlas.2024-01-01+json': { schema: { - type: "string", - readOnly: true - } - } - } - } - } - } - } + type: 'string', + readOnly: true, + }, + }, + }, + }, + }, + }, + }, }, errors: [ { code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', - message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: id. http://go/ipa/106', + message: + 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: id. http://go/ipa/106', path: ['paths', '/invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], severity: DiagnosticSeverity.Warning, }, { code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', - message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at one of the inline schemas. http://go/ipa/106', + message: + 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at one of the inline schemas. http://go/ipa/106', path: ['paths', '/invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2024-01-01+json'], severity: DiagnosticSeverity.Warning, - } - ] + }, + ], }, { name: 'invalid methods - nested readOnly field', @@ -168,23 +170,31 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ content: { 'application/vnd.atlas.2023-01-01+json': { schema: { - $ref: '#/components/schemas/NestedSchemaWithReadOnly' - } - } - } - } - } - } - } + $ref: '#/components/schemas/NestedSchemaWithReadOnly', + }, + }, + }, + }, + }, + }, + }, }, errors: [ { code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', - message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: user.userId. http://go/ipa/106', - path: ['paths', '/nested-invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], + message: + 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: user.userId. http://go/ipa/106', + path: [ + 'paths', + '/nested-invalid-resource', + 'post', + 'requestBody', + 'content', + 'application/vnd.atlas.2023-01-01+json', + ], severity: DiagnosticSeverity.Warning, - } - ] + }, + ], }, { name: 'invalid methods - array with readOnly field', @@ -197,23 +207,31 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ content: { 'application/vnd.atlas.2023-01-01+json': { schema: { - $ref: '#/components/schemas/ArraySchemaWithReadOnly' - } - } - } - } - } - } - } + $ref: '#/components/schemas/ArraySchemaWithReadOnly', + }, + }, + }, + }, + }, + }, + }, }, errors: [ { code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields', - message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: items.items.itemId. http://go/ipa/106', - path: ['paths', '/array-invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], + message: + 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: items.items.itemId. http://go/ipa/106', + path: [ + 'paths', + '/array-invalid-resource', + 'post', + 'requestBody', + 'content', + 'application/vnd.atlas.2023-01-01+json', + ], severity: DiagnosticSeverity.Warning, - } - ] + }, + ], }, { name: 'methods with exceptions', @@ -226,18 +244,18 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [ content: { 'application/vnd.atlas.2023-01-01+json': { schema: { - $ref: '#/components/schemas/SchemaWithReadOnly' + $ref: '#/components/schemas/SchemaWithReadOnly', }, 'x-xgen-IPA-exception': { - 'xgen-IPA-106-create-method-request-has-no-readonly-fields': 'Reason' - } - } - } - } - } - } - } + 'xgen-IPA-106-create-method-request-has-no-readonly-fields': 'Reason', + }, + }, + }, + }, + }, + }, + }, }, - errors: [] - } -]); \ No newline at end of file + errors: [], + }, +]); diff --git a/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js b/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js index c9c2cbff49..d46714e27b 100644 --- a/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js +++ b/tools/spectral/ipa/__tests__/utils/schemaUtils.test.js @@ -17,15 +17,15 @@ describe('findPropertiesByAttribute', () => { it('detects direct attribute match', () => { const schema = { type: 'string', - readOnly: true + readOnly: true, }; - + const errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); - + expect(errors).toHaveLength(1); expect(errors[0]).toEqual({ path: mockPath, - message: `${errorMessage} Found readOnly property at one of the inline schemas.` + message: `${errorMessage} Found readOnly property at one of the inline schemas.`, }); }); @@ -35,23 +35,23 @@ describe('findPropertiesByAttribute', () => { properties: { id: { type: 'string', - readOnly: true + readOnly: true, }, name: { - type: 'string' + type: 'string', }, password: { type: 'string', - writeOnly: true - } - } + writeOnly: true, + }, + }, }; - + // Testing readOnly detection let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('Found readOnly property at: id.'); - + // Testing writeOnly detection errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); @@ -66,28 +66,28 @@ describe('findPropertiesByAttribute', () => { type: 'object', properties: { id: { - type: 'string', - readOnly: true + type: 'string', + readOnly: true, }, credentials: { type: 'object', properties: { password: { type: 'string', - writeOnly: true - } - } - } - } - } - } + writeOnly: true, + }, + }, + }, + }, + }, + }, }; - + // Testing deep readOnly detection let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('Found readOnly property at: user.id.'); - + // Testing deep writeOnly detection errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); @@ -105,23 +105,23 @@ describe('findPropertiesByAttribute', () => { properties: { id: { type: 'string', - readOnly: true + readOnly: true, }, secret: { type: 'string', - writeOnly: true - } - } - } - } - } + writeOnly: true, + }, + }, + }, + }, + }, }; - + // Testing readOnly in array items let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('Found readOnly property at: items.items.id.'); - + // Testing writeOnly in array items errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); @@ -136,10 +136,10 @@ describe('findPropertiesByAttribute', () => { properties: { id: { type: 'string', - readOnly: true - } - } - } + readOnly: true, + }, + }, + }, ], anyOf: [ { @@ -147,33 +147,33 @@ describe('findPropertiesByAttribute', () => { properties: { key: { type: 'string', - writeOnly: true - } - } - } + writeOnly: true, + }, + }, + }, ], oneOf: [ { - type: 'object' + type: 'object', }, { type: 'object', properties: { token: { type: 'string', - readOnly: true - } - } - } - ] + readOnly: true, + }, + }, + }, + ], }; - + // Testing readOnly in combiners let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(2); expect(errors[0].message).toContain('Found readOnly property at: allOf.0.id.'); expect(errors[1].message).toContain('Found readOnly property at: oneOf.1.token.'); - + // Testing writeOnly in combiners errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(1); @@ -186,28 +186,28 @@ describe('findPropertiesByAttribute', () => { properties: { id: { type: 'string', - readOnly: true + readOnly: true, }, nested: { type: 'object', properties: { innerId: { type: 'string', - readOnly: true - } - } + readOnly: true, + }, + }, }, items: { type: 'array', items: { - readOnly: true - } - } - } + readOnly: true, + }, + }, + }, }; - + const errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); - + expect(errors).toHaveLength(3); expect(errors[0].message).toContain('Found readOnly property at: id.'); expect(errors[1].message).toContain('Found readOnly property at: nested.innerId.'); @@ -229,19 +229,19 @@ describe('findPropertiesByAttribute', () => { nested: { type: 'object', properties: { - value: { type: 'number' } - } + value: { type: 'number' }, + }, }, items: { type: 'array', items: { - type: 'string' - } - } - } + type: 'string', + }, + }, + }, }; - + const errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage); expect(errors).toHaveLength(0); }); -}); \ No newline at end of file +}); diff --git a/tools/spectral/ipa/rulesets/IPA-106.yaml b/tools/spectral/ipa/rulesets/IPA-106.yaml index 0d25a4a3a1..d6fcf29c03 100644 --- a/tools/spectral/ipa/rulesets/IPA-106.yaml +++ b/tools/spectral/ipa/rulesets/IPA-106.yaml @@ -42,4 +42,4 @@ rules: given: '$.paths[*].post.requestBody.content' then: field: '@key' - function: 'createMethodRequestHasNoReadonlyFields' \ No newline at end of file + function: 'createMethodRequestHasNoReadonlyFields' diff --git a/tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js b/tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js index e5abc6c792..11648b38f1 100644 --- a/tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js +++ b/tools/spectral/ipa/rulesets/functions/utils/schemaUtils.js @@ -47,8 +47,10 @@ export function findPropertiesByAttribute(schema, attributeName, path, errors = if (schema[attributeName] === true) { errors.push({ path, - message: propPath.length > 0 ? `${errorMessage} Found ${attributeName} property at: ${propPath.join('.')}.`: - `${errorMessage} Found ${attributeName} property at one of the inline schemas.`, + message: + propPath.length > 0 + ? `${errorMessage} Found ${attributeName} property at: ${propPath.join('.')}.` + : `${errorMessage} Found ${attributeName} property at one of the inline schemas.`, }); return errors; } @@ -75,4 +77,4 @@ export function findPropertiesByAttribute(schema, attributeName, path, errors = }); return errors; -} \ No newline at end of file +}