Skip to content

Commit 8394b67

Browse files
address the comments
1 parent 3cceac4 commit 8394b67

File tree

2 files changed

+110
-63
lines changed

2 files changed

+110
-63
lines changed

tools/spectral/CONTRIBUTING.md

Lines changed: 109 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
Thank you for your interest in contributing! We welcome contributions of all kinds—bug fixes, new features, documentation improvements, and more.
44

5-
**Note:** For MongoDB engineers, please review https://go/ipa-validation-internal-wiki for additional information.
5+
> **Note:** For MongoDB engineers, please review https://go/ipa-validation-internal-wiki for additional information.
66
7-
## Legacy Spectral Rule Implementation
7+
---
8+
## Legacy Spectral Rule Development
89

910
### Updating the .spectral.yaml Ruleset
1011

@@ -13,8 +14,8 @@ When adding new rules or updating the `.spectral.yaml` file, the validations wil
1314
1. Open a pull request (PR) in the `mongodb/openapi` repository with changes to `tools/spectral/.spectral.yaml`.
1415
2. Ensure that the new Spectral lint checks pass.
1516
3. Review and merge the PR.
16-
17-
## IPA Rule Implementation
17+
---
18+
## IPA Rule Development
1819

1920
The rule validations are custom JS functions (see [/rulesets/functions](https://github.com/mongodb/openapi/tree/main/tools/spectral/ipa/rulesets/functions)). To learn more about custom functions, refer to the [Spectral Documentation](https://docs.stoplight.io/docs/spectral/a781e290eb9f9-custom-functions).
2021

@@ -33,7 +34,7 @@ Instead of using the [Spectral overrides approach](https://docs.stoplight.io/doc
3334
"xgen-IPA-104-resource-has-GET": "Legacy API, not used by infrastructure-as-code tooling",
3435
}
3536
```
36-
37+
---
3738
## Testing
3839

3940
- IPA Validation related code is tested using [Jest](https://jestjs.io/)
@@ -51,6 +52,7 @@ To run a single test, in this case `singletonHasNoId.test.js`:
5152
```
5253
npm run test -- singletonHasNoId
5354
```
55+
---
5456

5557
## Code Style
5658

@@ -61,6 +63,7 @@ npx prettier . --write
6163
```
6264

6365
- [ESLint](https://eslint.org/) is being used for linting
66+
---
6467

6568
## Pull Request Checklist
6669

@@ -73,102 +76,118 @@ npm run gen-ipa-docs
7376
```
7477

7578
- [ ] Reference related issues (e.g., Closes #123)
76-
77-
## Technical Decisions
79+
---
80+
## Getting Started with IPA Rule Development
7881

7982
### Resource & Singleton Evaluation
8083

81-
In the IPA Spectral validation, a resource can be identified using a resource collection path.
84+
In IPA Spectral validation, a **resource** is typically identified using a *resource collection path*, such as `/resource`.
8285

83-
For example, resource collection path: `/resource`
86+
To develop rules that evaluate resource and singleton patterns, you can use the following utility functions:
8487

85-
- To get all paths and the path objects for this resource, use [getResourcePathItems](https://github.com/mongodb/openapi/blob/99823b3dfd315f892c5f64f1db50f2124261929c/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js#L143)
88+
#### Retrieve Resource Path Items
8689

87-
- Will return path objects for paths (if present):
88-
- Resource collection path `/resource`
89-
- Single resource path `/resource + /{someId}`
90-
- Custom method path(s)
91-
- `/resource + /{someId} + :customMethod`
92-
- `/resource + :customMethod`
90+
Use [`getResourcePathItems`](https://github.com/mongodb/openapi/blob/99823b3dfd315f892c5f64f1db50f2124261929c/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js#L143) to retrieve all relevant path objects for a given resource:
9391

94-
- To check if a resource is a singleton, take the returned object from [getResourcePathItems](https://github.com/mongodb/openapi/blob/99823b3dfd315f892c5f64f1db50f2124261929c/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js#L143) and evaluate the resource using [isSingletonResource](https://github.com/mongodb/openapi/blob/99823b3dfd315f892c5f64f1db50f2124261929c/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js#L71)
95-
- To check if a path belongs to a resource collection, use [isResourceCollectionIdentifier](https://github.com/mongodb/openapi/blob/99823b3dfd315f892c5f64f1db50f2124261929c/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js#L13)
96-
- To check if a path belongs to a single resource, use [isSingleResourceIdentifier](https://github.com/mongodb/openapi/blob/99823b3dfd315f892c5f64f1db50f2124261929c/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js#L31)
92+
- Returns path objects for:
93+
- Resource collection path: `/resource`
94+
- Single resource path: `/resource/{someId}`
95+
- Custom method paths:
96+
- `/resource/{someId}:customMethod`
97+
- `/resource:customMethod`
9798

98-
![info](https://img.shields.io/badge/info-blue) Note: Paths like `/resource/resource` or `/resource/{id}/{id}` are not evaluated as valid resource or single resource paths using isResourceCollectionIdentifier or isSingleResourceIdentifier.
99+
#### Determine if Resource is a Singleton
99100

100-
### Rule Implementation Guidelines
101+
Use [`isSingletonResource`](https://github.com/mongodb/openapi/blob/99823b3dfd315f892c5f64f1db50f2124261929c/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js#L71) to check if the resource behaves as a singleton. Pass the object returned by `getResourcePathItems`.
101102

102-
#### How to Decide when to collect adoption and violation
103+
#### Identify Resource Collection or Single Resource Paths
103104

104-
The collection of adoption, violation, and exemption should be at the same component level - i.e., the same jsonPath level.
105+
Use the following helpers to check the type of a path:
105106

106-
Use
107+
- [`isResourceCollectionIdentifier`](https://github.com/mongodb/openapi/blob/99823b3dfd315f892c5f64f1db50f2124261929c/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js#L13): Determines if a path represents a resource collection (e.g., `/resource`).
108+
- [`isSingleResourceIdentifier`](https://github.com/mongodb/openapi/blob/99823b3dfd315f892c5f64f1db50f2124261929c/tools/spectral/ipa/rulesets/functions/utils/resourceEvaluation.js#L31): Determines if a path represents a single resource (e.g., `/resource/{someId}`).
107109

108-
- [collectAndReturnViolation(jsonPath, ruleName, errorData)](https://github.com/mongodb/openapi/blob/cd4e085a68cb3bb6078e85dba85ad8ce1674f7da/tools/spectral/ipa/rulesets/functions/utils/collectionUtils.js#L14) for violation collection
109-
- [collectAdoption(jsonPath,ruleName)](https://github.com/mongodb/openapi/blob/cd4e085a68cb3bb6078e85dba85ad8ce1674f7da/tools/spectral/ipa/rulesets/functions/utils/collectionUtils.js#L32) for adoption collection
110-
- [collectException(object, ruleName, jsonPath)](https://github.com/mongodb/openapi/blob/cd4e085a68cb3bb6078e85dba85ad8ce1674f7da/tools/spectral/ipa/rulesets/functions/utils/collectionUtils.js#L32) for exception collection
110+
> **Note:** Paths such as `/resource/resource` or `/resource/{id}/{id}` are not recognized as valid resource or single resource identifiers using `isResourceCollectionIdentifier` or `isSingleResourceIdentifier`.
111111
112-
The rule developer should decide on which cases the rule will be considered as adopted and violated.
113112

114-
- **Example**: IPA guideline - Enumeration values must be UPPER_SNAKE_CASE
115-
- **Decision Process**
113+
### Deciding When to Collect Adoption, Violation, or Exception
116114

117-
Custom Spectral rule functions in the format of
115+
In IPA rule development, **adoption**, **violation**, and **exception** must be collected at the same component level — that is, they must share the same `jsonPath`.
118116

119-
```
117+
#### Helper Functions
118+
119+
Use the following helper functions from the `collectionUtils` module:
120+
121+
- [`collectAndReturnViolation(jsonPath, ruleName, errorData)`](https://github.com/mongodb/openapi/blob/cd4e085a68cb3bb6078e85dba85ad8ce1674f7da/tools/spectral/ipa/rulesets/functions/utils/collectionUtils.js#L14) — for reporting rule violations.
122+
- [`collectAdoption(jsonPath, ruleName)`](https://github.com/mongodb/openapi/blob/cd4e085a68cb3bb6078e85dba85ad8ce1674f7da/tools/spectral/ipa/rulesets/functions/utils/collectionUtils.js#L32) — for marking rule adoption.
123+
- [`collectException(object, ruleName, jsonPath)`](https://github.com/mongodb/openapi/blob/cd4e085a68cb3bb6078e85dba85ad8ce1674f7da/tools/spectral/ipa/rulesets/functions/utils/collectionUtils.js#L32) — for recording rule exceptions.
124+
---
125+
#### Rule Design Guidance
126+
127+
As a rule developer, you need to define:
128+
129+
- What qualifies as a **violation**?
130+
- What qualifies as an **adoption**?
131+
- When should an **exception** be collected?
132+
---
133+
#### Custom Rule Function Signature
134+
135+
Spectral custom rule functions follow this format:
136+
137+
```js
120138
export default (input, _, { path, documentInventory })
121139
```
140+
- `input`: The current component from the OpenAPI spec. Derived from the given and field values in the rule definition.
141+
- `path`: JSONPath array to the current component.
142+
- `documentInventory`: The entire OpenAPI specification (use `resolved` or `unresolved` depending on rule context).
122143

123-
- `input`: When the Spectral rule is processed according to the given and field parameters of the rule definition, input is the current component being processed.
124-
- `path`: JSONPath to current input
125-
- `documentInventory`: Whole OpenAPI spec (retrieve resolved or unresolved according to the need)
144+
---
126145

127-
When implementing a custom Spectral rule, please follow these conventions:
128-
129-
- If adoption, violation, and exception data should be collected at the same component level, ensure all helper functions use the same `jsonPath`.
130-
This path is either passed directly as the Spectral rule function's parameter or derived from it.
131-
- Use this `jsonPath` consistently in:
146+
#### Rule Implementation Conventions
132147

148+
- Use the **same `jsonPath`** for:
133149
- `collectAndReturnViolation`
134150
- `collectAdoption`
135151
- `collectException`
136152

137-
- Input assumptions:
138-
The custom rule function assumes its input is never undefined. You do not need to validate the presence or structure of the input parameter.
153+
> 💡 This path should either be the `path` parameter from the rule function or a derived value from it.
139154
140-
- Expected behavior: A rule must collect exactly one of:
155+
- A rule must collect **only one** of the following for each evaluation:
156+
- An **adoption**
157+
- A **violation**
158+
- An **exception**
141159

142-
- an adoption,
143-
- a violation, or
144-
- an exception.
160+
- You can include **multiple error messages** for a violation. To do so:
161+
- Gather the messages into an array
162+
- Pass them to `collectAndReturnViolation`
145163

146-
- Violations can include multiple error messages, if needed.
147-
In that case, gather all messages into an array and pass it to `collectAndReturnViolation`, which is responsible for displaying messages to users.
164+
- The `input` parameter is assumed to be **defined** when the rule runs. No need to check for its existence.
148165

149-
💡 Example:
166+
---
150167

151-
If you're validating an `enum` and want to highlight each invalid value, collect the individual error messages into an array and pass it to `collectAndReturnViolation`.
168+
#### Example: Enum Case Validation
169+
To validate an enum and show a separate error for each invalid value:
152170

153-
```
154-
const errors = []
155-
...
171+
```js
172+
const errors = [];
156173

157-
errors.push({
158-
path: <jsonPath array for violation>,
159-
message: <custom error message>
160-
});
161-
162-
....
174+
for (const value of enumValues) {
175+
if (!isUpperSnakeCase(value)) {
176+
errors.push({
177+
path: [...path, 'enum'],
178+
message: `${value} is not in UPPER_SNAKE_CASE`,
179+
});
180+
}
181+
}
163182

164183
if (errors.length === 0) {
165-
collectAdoption(jsonPath, RULE_NAME);
184+
collectAdoption(path, RULE_NAME);
166185
} else {
167-
return collectAndReturnViolation(jsonPath, RULE_NAME, errors);
186+
return collectAndReturnViolation(path, RULE_NAME, errors);
168187
}
169188
```
170189

171-
#### How to Decide the component level at which the rule will be processed
190+
### How to Decide the component level at which the rule will be processed
172191

173192
When designing a rule, think from the custom rule function’s perspective.
174193
Consider which `input` and `jsonPath` values will be most helpful for accurately evaluating the rule and collecting adoption, violation, or exception data.
@@ -246,3 +265,31 @@ then:
246265
field: @key
247266
function: "customRuleFunction"
248267
```
268+
269+
**Case 3**: Parameterized rules
270+
271+
The `functionOptions` in the rule definition can be used to pass additional parameters to your custom rule function. This is useful when you need to configure or provide specific settings to the rule function for more flexible behavior.
272+
273+
- **Example**: Define `functionOptions` within the rule to adjust behavior:
274+
275+
```yaml
276+
xgen-IPA-xxx-rule-name:
277+
description: "Rule description"
278+
message: "{{error}} http:://go/ipa/x"
279+
severity: warn
280+
given: '$.paths[*].get'
281+
then:
282+
function: "customRuleFunction"
283+
functionOptions:
284+
option1: "value1"
285+
option2: "value2"
286+
```
287+
288+
In the custom rule function:
289+
```js
290+
export default (input, opts, { path, documentInventory }) => {
291+
const { option1, option2 } = opts.functionOptions;
292+
293+
// Use the options in your rule logic
294+
};
295+
```

tools/spectral/ipa/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ For more information about Spectral GitHub action, see the [GitHub repo](https:/
104104

105105
You can create a validation script similar to this:
106106

107-
```
107+
```bash
108108
#!/bin/bash
109109
spectral lint <openapi-spec-file> --ruleset=<spectral-ruleset-file>
110110
if [ $? -ne 0 ]; then

0 commit comments

Comments
 (0)