Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import testRule from './__helpers__/testRule';
import { DiagnosticSeverity } from '@stoplight/types';

const componentSchemas = {
schemas: {
SchemaRequest: {
type: 'object',
},
Schema: {
type: 'object',
},
},
};
testRule('xgen-IPA-106-create-method-request-body-is-request-suffixed-object', [
{
name: 'valid methods',
document: {
components: componentSchemas,
paths: {
'/resource': {
post: {
requestBody: {
content: {
'application/vnd.atlas.2023-01-01+json': {
schema: {
$ref: '#/components/schemas/SchemaRequest',
},
},
'application/vnd.atlas.2024-01-01+json': {
schema: {
$ref: '#/components/schemas/SchemaRequest',
},
},
},
},
},
},
'/resource/{id}': {
post: {
requestBody: {
content: {
'application/vnd.atlas.2023-01-01+json': {
schema: {
$ref: '#/components/schemas/SchemaRequest',
},
},
'application/vnd.atlas.2024-01-01+json': {
schema: {
$ref: '#/components/schemas/SchemaRequest',
},
},
},
},
},
},
'/resource/{id}:customMethod': {
post: {
requestBody: {
content: {
'application/vnd.atlas.2023-01-01+json': {
schema: {
$ref: '#/components/schemas/Schema',
},
},
'application/vnd.atlas.2024-01-01+json': {
schema: {
$ref: '#/components/schemas/SchemaRequest',
},
},
},
},
},
},
},
},
errors: [],
},
{
name: 'invalid methods',
document: {
components: componentSchemas,
paths: {
'/resource': {
post: {
requestBody: {
content: {
'application/vnd.atlas.2023-01-01+json': {
schema: {
$ref: '#/components/schemas/Schema',
},
},
},
},
},
},
'/resource2': {
post: {
requestBody: {
content: {
'application/vnd.atlas.2023-01-01+json': {
schema: {
$ref: '#/components/schemas/Schema',
},
},
'application/vnd.atlas.2024-01-01+json': {
schema: {
$ref: '#/components/schemas/Schema',
},
},
},
},
},
},
'/resource3': {
post: {
requestBody: {
content: {
'application/vnd.atlas.2023-01-01+json': {
schema: {
type: 'object',
},
},
},
},
},
},
},
},
errors: [
{
code: 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object',
message: 'The response body schema must reference a schema with a Request suffix. http://go/ipa/106',
path: ['paths', '/resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object',
message: 'The response body schema must reference a schema with a Request suffix. http://go/ipa/106',
path: ['paths', '/resource2', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object',
message: 'The response body schema must reference a schema with a Request suffix. http://go/ipa/106',
path: ['paths', '/resource2', 'post', 'requestBody', 'content', 'application/vnd.atlas.2024-01-01+json'],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object',
message: 'The response body schema is defined inline and must reference a predefined schema. http://go/ipa/106',
path: ['paths', '/resource3', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'],
severity: DiagnosticSeverity.Warning,
},
],
},
{
name: 'invalid method with exception',
document: {
components: componentSchemas,
paths: {
'/resource': {
post: {
requestBody: {
content: {
'application/vnd.atlas.2023-01-01+json': {
schema: {
$ref: '#/components/schemas/Schema',
},
'x-xgen-IPA-exception': {
'xgen-IPA-106-create-method-request-body-is-request-suffixed-object': 'reason',
},
},
},
},
},
},
'/resource2': {
post: {
requestBody: {
content: {
'application/vnd.atlas.2023-01-01+json': {
schema: {
$ref: '#/components/schemas/Schema',
},
'x-xgen-IPA-exception': {
'xgen-IPA-106-create-method-request-body-is-request-suffixed-object': 'reason',
},
},
'application/vnd.atlas.2024-01-01+json': {
schema: {
$ref: '#/components/schemas/Schema',
},
'x-xgen-IPA-exception': {
'xgen-IPA-106-create-method-request-body-is-request-suffixed-object': 'reason',
},
},
},
},
},
},
'/resource3': {
post: {
requestBody: {
content: {
'application/vnd.atlas.2023-01-01+json': {
schema: {
type: 'object',
},
'x-xgen-IPA-exception': {
'xgen-IPA-106-create-method-request-body-is-request-suffixed-object': 'reason',
},
},
},
},
},
},
},
},
errors: [],
},
]);
1 change: 1 addition & 0 deletions tools/spectral/ipa/ipa-spectral.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ extends:
- ./rulesets/IPA-109.yaml
- ./rulesets/IPA-113.yaml
- ./rulesets/IPA-123.yaml
- ./rulesets/IPA-106.yaml

overrides:
- files:
Expand Down
15 changes: 15 additions & 0 deletions tools/spectral/ipa/rulesets/IPA-106.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# IPA-104: Create
# http://go/ipa/106

functions:
- createMethodRequestBodyIsRequestSuffixedObject

rules:
xgen-IPA-106-create-method-request-body-is-request-suffixed-object:
description: 'The Create method request should be a Request suffixed object. http://go/ipa/106'
message: '{{error}} http://go/ipa/106'
severity: warn
given: '$.paths[*].post.requestBody.content'
then:
field: '@key'
function: 'createMethodRequestBodyIsRequestSuffixedObject'
8 changes: 8 additions & 0 deletions tools/spectral/ipa/rulesets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ For rule definitions, see [IPA-104.yaml](https://github.com/mongodb/openapi/blob
| xgen-IPA-104-get-method-returns-single-resource | The purpose of the get method is to return data from a single resource. http://go/ipa/104 | warn |
| xgen-IPA-104-get-method-response-code-is-200 | The Get method must return a 200 OK response. http://go/ipa/104 | warn |

### IPA-106

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 |

Comment on lines +43 to +44
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you point me to where this is expected in the IPA 106? I disagree with enforcing this particular rule because it creates unnecessary redundancy in our codebase since developers are forced to duplicate views with only minor name changes

Copy link
Contributor

Choose a reason for hiding this comment

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

API producers should implement as a Request suffixed object
A Request object must include only input fields

Copy link
Collaborator

Choose a reason for hiding this comment

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

ah I missed it. Thanks

I’m curious about the reasons behind prefixing view objects with “Request”. This naming convention concerns me because it might force us to create duplicate view definitions with different names, when we could potentially use Schema.AccessMode to maintain a single view that adapts based on context. Am I missing something? Maybe I misunderstood the proposed change 🤔

Copy link
Collaborator Author

@yelizhenden-mdb yelizhenden-mdb Mar 5, 2025

Choose a reason for hiding this comment

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

You can think of CreateRequest suffixed schemas as an example for this guideline. View suffixed schemas are being used for responses as far as I know

Copy link
Contributor

Choose a reason for hiding this comment

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

I’m curious about the reasons behind prefixing view objects wit

It's a somewhat recommend practice that helps isolate changes between payloads, some feel strongly it helps to make clear what's write only vs read only while like you some have concerns on duplication

### IPA-109

For rule definitions, see [IPA-109.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-109.yaml).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { hasException } from './utils/exceptions.js';
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
import { isCustomMethod } from './utils/resourceEvaluation.js';
import { resolveObject } from './utils/componentUtils.js';

const RULE_NAME = 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object';
const ERROR_MESSAGE_SCHEMA_NAME = 'The response body schema must reference a schema with a Request suffix.';
const ERROR_MESSAGE_SCHEMA_REF = 'The response body schema is defined inline and must reference a predefined schema.';

export default (input, _, { path, documentInventory }) => {
const oas = documentInventory.unresolved;
const resourcePath = path[1];

if (isCustomMethod(resourcePath)) {
return;
}

const contentPerMediaType = resolveObject(oas, path);

if (hasException(contentPerMediaType, RULE_NAME)) {
collectException(contentPerMediaType, RULE_NAME, path);
return;
}

if (contentPerMediaType.schema) {
const schema = contentPerMediaType.schema;
if (!schema.$ref) {
return collectAndReturnViolation(path, RULE_NAME, ERROR_MESSAGE_SCHEMA_REF);
}

if (!schema.$ref.endsWith('Request')) {
return collectAndReturnViolation(path, RULE_NAME, ERROR_MESSAGE_SCHEMA_NAME);
}
}

collectAdoption(path, RULE_NAME);
};
Loading