Skip to content

Commit fb874f5

Browse files
CLOUDP-287249: IPA-123: Validate enums must be UPPER_SNAKE_CASE
1 parent d47a778 commit fb874f5

File tree

5 files changed

+89
-46
lines changed

5 files changed

+89
-46
lines changed

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

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [
2727
schemas: {
2828
SchemaName: {
2929
'x-xgen-IPA-exception': {
30-
'xgen-IPA-123-enum-values-must-be-upper-snake-case': {},
30+
'xgen-IPA-123-enum-values-must-be-upper-snake-case': 'reason',
3131
},
3232
properties: {
3333
exampleProperty: {
@@ -42,7 +42,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [
4242
errors: [],
4343
},
4444
{
45-
name: 'invalid schema with exception - components.schemas',
45+
name: 'invalid schema - components.schemas',
4646
document: {
4747
components: {
4848
schemas: {
@@ -93,7 +93,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [
9393
errors: [],
9494
},
9595
{
96-
name: 'invalid schema with exceptions - paths.*',
96+
name: 'invalid schema with exception - paths.*',
9797
document: {
9898
paths: {
9999
'/a/{exampleId}': {
@@ -102,7 +102,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [
102102
{
103103
schema: {
104104
'x-xgen-IPA-exception': {
105-
'xgen-IPA-123-enum-values-must-be-upper-snake-case': {},
105+
'xgen-IPA-123-enum-values-must-be-upper-snake-case': 'reason',
106106
},
107107
type: 'string',
108108
enum: ['exampleA', 'exampleB'],
@@ -113,20 +113,7 @@ testRule('xgen-IPA-123-enum-values-must-be-upper-snake-case', [
113113
},
114114
},
115115
},
116-
errors: [
117-
{
118-
code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case',
119-
message: 'exampleA enum value must be UPPER_SNAKE_CASE. http://go/ipa/123',
120-
path: ['paths', '/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'],
121-
severity: DiagnosticSeverity.Warning,
122-
},
123-
{
124-
code: 'xgen-IPA-123-enum-values-must-be-upper-snake-case',
125-
message: 'exampleB enum value must be UPPER_SNAKE_CASE. http://go/ipa/123',
126-
path: ['paths', '/a/{exampleId}', 'get', 'parameters', '0', 'schema', 'enum'],
127-
severity: DiagnosticSeverity.Warning,
128-
},
129-
],
116+
errors: [],
130117
},
131118
{
132119
name: 'invalid schema - paths.*',

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

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
11
import { hasException } from './utils/exceptions.js';
2+
import {getSchemaPath, resolveObject} from './utils/componentUtils.js'
23
import { casing } from '@stoplight/spectral-functions';
34

45
const RULE_NAME = 'xgen-IPA-123-enum-values-must-be-upper-snake-case';
56
const ERROR_MESSAGE = 'enum value must be UPPER_SNAKE_CASE.';
67

7-
function getSchemaPath(path) {
8-
if (path.includes('components')) {
9-
return path.slice(0, 3);
10-
} else if (path.includes('paths')) {
11-
const index = path.findIndex((item) => item === 'schema');
12-
return path.slice(0, index + 1);
13-
}
14-
}
8+
export default (input, _, { path, documentInventory }) => {
159

16-
export default (input, _, { path }) => {
17-
//There are two path types for enum definition
18-
//First type: components.schemas.schemaName.*.enum
19-
//Second type: paths.*.method.parameters[*].schema.enum
20-
if (hasException(getSchemaPath(path), RULE_NAME)) {
10+
const oas = documentInventory.resolved;
11+
const schemaPath = getSchemaPath(path);
12+
const schemaObject = resolveObject(oas, schemaPath);
13+
if (hasException(schemaObject, RULE_NAME)) {
2114
return;
2215
}
2316

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isPathParam } from './utils/pathUtils.js';
1+
import { isPathParam } from './utils/componentUtils.js';
22
import { hasException } from './utils/exceptions.js';
33

44
const RULE_NAME = 'xgen-IPA-102-path-alternate-resource-name-path-param';
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Checks if a string belongs to a path parameter or a path parameter with a custom method.
3+
*
4+
* A path parameter has the format: `{paramName}`
5+
* A path parameter with a custom method has the format: `{paramName}:customMethod`
6+
*
7+
* @param {string} str - A string extracted from a path split by slashes.
8+
* @returns {boolean} True if the string matches the expected formats, false otherwise.
9+
*/
10+
export function isPathParam(str) {
11+
const pathParamRegEx = new RegExp(`^{[a-z][a-zA-Z0-9]*}$`);
12+
const pathParamWithCustomMethodRegEx = new RegExp(`^{[a-z][a-zA-Z0-9]*}:[a-z][a-zA-Z0-9]*$`);
13+
return pathParamRegEx.test(str) || pathParamWithCustomMethodRegEx.test(str);
14+
}
15+
16+
/**
17+
* Extracts the schema path from the given JSONPath array.
18+
*
19+
* This function is designed to handle two types of paths commonly encountered in OpenAPI definitions:
20+
*
21+
* 1. **Component Schema Paths**:
22+
* - Represented as: `components.schemas.schemaName.*.enum`
23+
* - This path indicates that the enum is defined within a schema under `components.schemas`.
24+
* - The function returns the first three elements (`["components", "schemas", "schemaName"]`).
25+
*
26+
* 2. **Parameter Schema Paths**:
27+
* - Represented as: `paths.*.method.parameters[*].schema.enum`
28+
* - This path indicates that the enum is part of a parameter's schema in an operation.
29+
* - The function identifies the location of `schema` in the path and returns everything up to (and including) it.
30+
*
31+
* @param {string[]} path - An array representing the JSONPath structure of the OpenAPI definition.
32+
* @returns {string[]} The truncated path pointing to the schema object.
33+
*/
34+
export function getSchemaPath(path) {
35+
if (path.includes('components')) {
36+
return path.slice(0, 3);
37+
} else if (path.includes('paths')) {
38+
const index = path.findIndex((item) => item === 'schema');
39+
return path.slice(0, index + 1);
40+
}
41+
}
42+
43+
/**
44+
* Resolves the value of a nested property within an OpenAPI structure using a given path.
45+
*
46+
* This function traverses an OpenAPI object based on a specified path (array of keys)
47+
* and retrieves the value at the end of the path. If any key in the path is not found,
48+
* or the value is undefined at any point, the function will return `undefined`.
49+
*
50+
* @param {Object} oas - The entire OpenAPI Specification object.
51+
* This represents the root of the OpenAPI document.
52+
* @param {string[]} objectPath - An array of strings representing the path to the desired value.
53+
* Each element corresponds to a key in the OAS object hierarchy.
54+
* For example, `['components', 'schemas', 'MySchema', 'properties']`.
55+
* @returns {*} The value at the specified path within the OpenAPI object, or `undefined` if the path is invalid.
56+
*
57+
* @example
58+
* const oas = {
59+
* components: {
60+
* schemas: {
61+
* MySchema: {
62+
* properties: {
63+
* fieldName: { type: 'string' }
64+
* }
65+
* }
66+
* }
67+
* }
68+
* };
69+
*
70+
* const result = resolveObject(oas, ['components', 'schemas', 'MySchema', 'properties']);
71+
* console.log(result); // Output: { fieldName: { type: 'string' } }
72+
*/
73+
export function resolveObject(oas, objectPath) {
74+
return objectPath.reduce((current, key) => {
75+
return current && current[key] ? current[key] : undefined;
76+
}, oas);
77+
}

tools/spectral/ipa/rulesets/functions/utils/pathUtils.js

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)