Skip to content

Commit 5a98e13

Browse files
CLOUDP-304053: IPA-106:Create - The resource must be the request body (implement deepObjectComparison without third party dependencies)
1 parent 698e772 commit 5a98e13

File tree

6 files changed

+78
-29
lines changed

6 files changed

+78
-29
lines changed

package-lock.json

Lines changed: 0 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@
3030
"apache-arrow": "^19.0.1",
3131
"dotenv": "^16.4.7",
3232
"eslint-plugin-jest": "^28.10.0",
33-
"lodash": "^4.17.21",
3433
"markdown-table": "^3.0.4",
35-
"omit-deep-lodash": "^1.1.7",
3634
"openapi-to-postmanv2": "4.25.0",
3735
"parquet-wasm": "^0.6.1"
3836
},

tools/spectral/ipa/__tests__/createMethodRequestBodyIsGetResponse.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ testRule('xgen-IPA-106-create-method-request-body-is-get-method-response', [
9999
content: {
100100
'application/vnd.atlas.2023-01-01+json': {
101101
schema: {
102-
$ref: '#/components/schemas/SchemaOne',
102+
type: 'string',
103103
},
104104
},
105105
'application/vnd.atlas.2024-01-01+json': {

tools/spectral/ipa/rulesets/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ For rule definitions, see [IPA-105.yaml](https://github.com/mongodb/openapi/blob
5050

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

53-
| Rule Name | Description | Severity |
54-
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------- | -------- |
55-
| 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 |
56-
| xgen-IPA-106-create-method-should-not-have-query-parameters | Create operations should not use query parameters. http://go/ipa/xxx | warn |
57-
| 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 |
53+
| Rule Name | Description | Severity |
54+
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
55+
| 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 |
56+
| xgen-IPA-106-create-method-should-not-have-query-parameters | Create operations should not use query parameters. http://go/ipa/xxx | warn |
57+
| 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 |
5858

5959
### IPA-108
6060

tools/spectral/ipa/rulesets/functions/createMethodRequestBodyIsGetResponse.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import { getResponseOfGetMethodByMediaType, isCustomMethodIdentifier } from './utils/resourceEvaluation.js';
22
import { resolveObject } from './utils/componentUtils.js';
3-
import { isEqual } from 'lodash';
4-
import omitDeep from 'omit-deep-lodash';
3+
import { isEqual, omitDeep } from './utils/compareUtils.js';
54
import { hasException } from './utils/exceptions.js';
65
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
76

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

12-
export default (input, _, { path, documentInventory }) => {
11+
export default (input, opts, { path, documentInventory }) => {
1312
const oas = documentInventory.resolved;
1413
const resourcePath = path[1];
1514
let mediaType = input;
@@ -34,7 +33,8 @@ export default (input, _, { path, documentInventory }) => {
3433
const errors = checkViolationsAndReturnErrors(
3534
path,
3635
postMethodRequestContentPerMediaType,
37-
getMethodResponseContentPerMediaType
36+
getMethodResponseContentPerMediaType,
37+
opts
3838
);
3939

4040
if (errors.length !== 0) {
@@ -47,14 +47,16 @@ export default (input, _, { path, documentInventory }) => {
4747
function checkViolationsAndReturnErrors(
4848
path,
4949
postMethodRequestContentPerMediaType,
50-
getMethodResponseContentPerMediaType
50+
getMethodResponseContentPerMediaType,
51+
opts
5152
) {
5253
const errors = [];
5354

55+
const ignoredValues = opts?.ignoredValues || [];
5456
if (
5557
!isEqual(
56-
omitDeep(postMethodRequestContentPerMediaType.schema, 'readOnly', 'writeOnly'),
57-
omitDeep(getMethodResponseContentPerMediaType.schema, 'readOnly', 'writeOnly')
58+
omitDeep(postMethodRequestContentPerMediaType.schema, ...ignoredValues),
59+
omitDeep(getMethodResponseContentPerMediaType.schema, ...ignoredValues)
5860
)
5961
) {
6062
errors.push({
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// utils/compareUtils.js
2+
3+
/**
4+
* Deep equality check between two values
5+
* @param {*} value1 First value to compare
6+
* @param {*} value2 Second value to compare
7+
* @returns {boolean} Whether the values are deeply equal
8+
*/
9+
export function isEqual(value1, value2) {
10+
// If the values are strictly equal (including handling null/undefined)
11+
if (value1 === value2) return true;
12+
13+
// If either value is null or not an object, they're not equal (we already checked strict equality)
14+
if (value1 == null || value2 == null || typeof value1 !== 'object' || typeof value2 !== 'object') {
15+
return false;
16+
}
17+
18+
const keys1 = Object.keys(value1);
19+
const keys2 = Object.keys(value2);
20+
21+
// Different number of properties
22+
if (keys1.length !== keys2.length) return false;
23+
24+
// Check that all properties in value1 exist in value2 and are equal
25+
for (const key of keys1) {
26+
if (!keys2.includes(key)) return false;
27+
28+
// Recursive equality check for nested objects
29+
if (!isEqual(value1[key], value2[key])) return false;
30+
}
31+
32+
return true;
33+
}
34+
35+
/**
36+
* Deep clone an object while omitting specific properties
37+
* @param {object} obj Object to clone and omit properties from
38+
* @param {...string} keys Properties to omit
39+
* @returns {object} New object without the specified properties
40+
*/
41+
export function omitDeep(obj, ...keys) {
42+
if (!obj || typeof obj !== 'object') return obj;
43+
44+
// Handle arrays
45+
if (Array.isArray(obj)) {
46+
return obj.map((item) => omitDeep(item, ...keys));
47+
}
48+
49+
// Handle regular objects
50+
return Object.entries(obj).reduce((result, [key, value]) => {
51+
// Skip properties that should be omitted
52+
if (keys.includes(key)) return result;
53+
54+
// Handle nested objects/arrays recursively
55+
if (value && typeof value === 'object') {
56+
result[key] = omitDeep(value, ...keys);
57+
} else {
58+
result[key] = value;
59+
}
60+
61+
return result;
62+
}, {});
63+
}

0 commit comments

Comments
 (0)