Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
14 changes: 0 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@
"apache-arrow": "^19.0.1",
"dotenv": "^16.4.7",
"eslint-plugin-jest": "^28.10.0",
"lodash": "^4.17.21",
Copy link
Member

Choose a reason for hiding this comment

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

❤️

"markdown-table": "^3.0.4",
"omit-deep-lodash": "^1.1.7",
"openapi-to-postmanv2": "4.25.0",
"parquet-wasm": "^0.6.1"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ testRule('xgen-IPA-106-create-method-request-body-is-get-method-response', [
content: {
'application/vnd.atlas.2023-01-01+json': {
schema: {
$ref: '#/components/schemas/SchemaOne',
type: 'string',
},
},
'application/vnd.atlas.2024-01-01+json': {
Expand Down
8 changes: 6 additions & 2 deletions tools/spectral/ipa/rulesets/IPA-106.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@ rules:
field: '@key'
function: 'createMethodRequestBodyIsRequestSuffixedObject'
xgen-IPA-106-create-method-should-not-have-query-parameters:
description: 'Create operations should not use query parameters. http://go/ipa/xxx'
description: 'Create operations should not use query parameters. http://go/ipa/106'
message: '{{error}} http://go/ipa/106'
severity: warn
given: '$.paths[*].post'
then:
function: 'createMethodShouldNotHaveQueryParameters'
xgen-IPA-106-create-method-request-body-is-get-method-response:
description: 'The Create method request should be a Get method response. http://go/ipa/106'
description: |
Copy link
Member

Choose a reason for hiding this comment

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

❤️

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
message: '{{error}} http://go/ipa/106'
severity: warn
given: '$.paths[*].post.requestBody.content'
then:
field: '@key'
function: 'createMethodRequestBodyIsGetResponse'
functionOptions:
ignoredValues: ['readOnly', 'writeOnly']
12 changes: 7 additions & 5 deletions tools/spectral/ipa/rulesets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ For rule definitions, see [IPA-105.yaml](https://github.com/mongodb/openapi/blob

For rule definitions, see [IPA-106.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-106.yaml).

| Rule Name | Description | Severity |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------- | -------- |
| xgen-IPA-106-create-method-request-body-is-request-suffixed-object | The Create method request should be a Request suffixed object. http://go/ipa/106 | warn |
| xgen-IPA-106-create-method-should-not-have-query-parameters | Create operations should not use query parameters. http://go/ipa/xxx | warn |
| xgen-IPA-106-create-method-request-body-is-get-method-response | The Create method request should be a Get method response. http://go/ipa/106 | warn |
| Rule Name | Description | Severity |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| xgen-IPA-106-create-method-request-body-is-request-suffixed-object | The Create method request should be a Request suffixed object. http://go/ipa/106 | warn |
| xgen-IPA-106-create-method-should-not-have-query-parameters | Create operations should not use query parameters. http://go/ipa/106 | warn |
| 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 |

### IPA-108

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { getResponseOfGetMethodByMediaType, isCustomMethodIdentifier } from './utils/resourceEvaluation.js';
import { resolveObject } from './utils/componentUtils.js';
import { isEqual } from 'lodash';
import omitDeep from 'omit-deep-lodash';
import { isEqual, omitDeep } from './utils/compareUtils.js';
import { hasException } from './utils/exceptions.js';
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';

const RULE_NAME = 'xgen-IPA-106-create-method-request-body-is-get-method-response';
const ERROR_MESSAGE =
'The request body schema properties must match the response body schema properties of the Get method.';

export default (input, _, { path, documentInventory }) => {
export default (input, opts, { path, documentInventory }) => {
const oas = documentInventory.resolved;
const resourcePath = path[1];
let mediaType = input;
Expand All @@ -34,7 +33,8 @@ export default (input, _, { path, documentInventory }) => {
const errors = checkViolationsAndReturnErrors(
path,
postMethodRequestContentPerMediaType,
getMethodResponseContentPerMediaType
getMethodResponseContentPerMediaType,
opts
);

if (errors.length !== 0) {
Expand All @@ -47,14 +47,16 @@ export default (input, _, { path, documentInventory }) => {
function checkViolationsAndReturnErrors(
path,
postMethodRequestContentPerMediaType,
getMethodResponseContentPerMediaType
getMethodResponseContentPerMediaType,
opts
) {
const errors = [];

const ignoredValues = opts?.ignoredValues || [];
if (
!isEqual(
omitDeep(postMethodRequestContentPerMediaType.schema, 'readOnly', 'writeOnly'),
omitDeep(getMethodResponseContentPerMediaType.schema, 'readOnly', 'writeOnly')
omitDeep(postMethodRequestContentPerMediaType.schema, ...ignoredValues),
Copy link
Member

Choose a reason for hiding this comment

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

❤️

omitDeep(getMethodResponseContentPerMediaType.schema, ...ignoredValues)
)
) {
errors.push({
Expand Down
63 changes: 63 additions & 0 deletions tools/spectral/ipa/rulesets/functions/utils/compareUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// utils/compareUtils.js

/**
* Deep equality check between two values
* @param {*} value1 First value to compare
* @param {*} value2 Second value to compare
* @returns {boolean} Whether the values are deeply equal
*/
export function isEqual(value1, value2) {
// If the values are strictly equal (including handling null/undefined)
if (value1 === value2) return true;

// If either value is null or not an object, they're not equal (we already checked strict equality)
if (value1 == null || value2 == null || typeof value1 !== 'object' || typeof value2 !== 'object') {
return false;
}

const keys1 = Object.keys(value1);
const keys2 = Object.keys(value2);

// Different number of properties
if (keys1.length !== keys2.length) return false;

// Check that all properties in value1 exist in value2 and are equal
for (const key of keys1) {
if (!keys2.includes(key)) return false;

// Recursive equality check for nested objects
if (!isEqual(value1[key], value2[key])) return false;
}

return true;
}

/**
* Deep clone an object while omitting specific properties
* @param {object} obj Object to clone and omit properties from
* @param {...string} keys Properties to omit
* @returns {object} New object without the specified properties
*/
export function omitDeep(obj, ...keys) {
if (!obj || typeof obj !== 'object') return obj;

// Handle arrays
if (Array.isArray(obj)) {
return obj.map((item) => omitDeep(item, ...keys));
}

// Handle regular objects
return Object.entries(obj).reduce((result, [key, value]) => {
// Skip properties that should be omitted
if (keys.includes(key)) return result;

// Handle nested objects/arrays recursively
if (value && typeof value === 'object') {
result[key] = omitDeep(value, ...keys);
} else {
result[key] = value;
}

return result;
}, {});
}
Loading