Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
298 changes: 298 additions & 0 deletions tools/spectral/ipa/__tests__/getResourceIsNotPaginated.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
import testRule from './__helpers__/testRule';
import { DiagnosticSeverity } from '@stoplight/types';

const componentSchemas = {
schemas: {
Schema: {
properties: {
exampleProperty: {
type: 'string',
},
},
},
PaginatedSchema: {
type: 'array',
},
ArraySchema: {
properties: {
results: {
type: 'array',
},
},
},
},
};

testRule('xgen-IPA-104-GET-resource-not-paginated', [
{
name: 'valid resources',
document: {
paths: {
'/resource': {
get: {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
$ref: '#/components/schemas/PaginatedSchema',
},
},
},
},
},
},
},
'/resource/{id}': {
get: {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
$ref: '#/components/schemas/Schema',
},
},
},
},
},
},
},
'/resource/{id}:getAllThings': {
get: {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
$ref: '#/components/schemas/ArraySchema',
},
},
},
},
},
},
},
'/singleton': {
get: {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
$ref: '#/components/schemas/Schema',
},
},
},
},
},
},
},
},
components: componentSchemas,
},
errors: [],
},
{
name: 'invalid resources',
document: {
paths: {
'/arrayResource': {
get: {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
$ref: '#/components/schemas/PaginatedSchema',
},
},
},
},
},
},
},
'/arrayResource/{id}': {
get: {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
$ref: '#/components/schemas/ArraySchema',
},
},
},
},
},
},
},
'/paginatedResource': {
get: {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
$ref: '#/components/schemas/PaginatedSchema',
},
},
},
},
},
},
},
'/paginatedResource/{id}': {
get: {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
$ref: '#/components/schemas/PaginatedSchema',
},
},
},
},
},
},
},
'/arraySingleton': {
get: {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
$ref: '#/components/schemas/ArraySchema',
},
},
},
},
},
},
},
'/paginatedSingleton': {
get: {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
$ref: '#/components/schemas/PaginatedSchema',
},
},
},
},
},
},
},
},
components: componentSchemas,
},
errors: [
{
code: 'xgen-IPA-104-GET-resource-not-paginated',
message:
'Get methods should return data for a single resource. This method returns an array or a paginated response. http://go/ipa/104',
path: [
'paths',
'/arrayResource/{id}',
'get',
'responses',
'200',
'content',
'application/vnd.atlas.2024-08-05+json',
],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-104-GET-resource-not-paginated',
message:
'Get methods should return data for a single resource. This method returns an array or a paginated response. http://go/ipa/104',
path: [
'paths',
'/paginatedResource/{id}',
'get',
'responses',
'200',
'content',
'application/vnd.atlas.2024-08-05+json',
],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-104-GET-resource-not-paginated',
message:
'Get methods should return data for a single resource. This method returns an array or a paginated response. http://go/ipa/104',
path: [
'paths',
'/arraySingleton',
'get',
'responses',
'200',
'content',
'application/vnd.atlas.2024-08-05+json',
],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-104-GET-resource-not-paginated',
message:
'Get methods should return data for a single resource. This method returns an array or a paginated response. http://go/ipa/104',
path: [
'paths',
'/paginatedSingleton',
'get',
'responses',
'200',
'content',
'application/vnd.atlas.2024-08-05+json',
],
severity: DiagnosticSeverity.Warning,
},
],
},
{
name: 'invalid resource with exception',
document: {
paths: {
'/arrayResource': {
get: {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
$ref: '#/components/schemas/PaginatedSchema',
},
},
},
},
},
},
},
'/arrayResource/{id}': {
get: {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
'x-xgen-IPA-exception': {
'xgen-IPA-104-GET-resource-not-paginated': 'reason',
},
schema: {
$ref: '#/components/schemas/ArraySchema',
},
},
},
},
},
},
},
},
components: componentSchemas,
},
errors: [],
},
]);
8 changes: 8 additions & 0 deletions tools/spectral/ipa/rulesets/IPA-104.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

functions:
- eachResourceHasGetMethod
- getResourceIsNotPaginated

rules:
xgen-IPA-104-resource-has-GET:
Expand All @@ -13,3 +14,10 @@ rules:
then:
field: '@key'
function: 'eachResourceHasGetMethod'
xgen-IPA-104-GET-resource-not-paginated:
description: 'The purpose of the get method is to return data from a single resource. http://go/ipa/104'
message: '{{error}} http://go/ipa/104'
severity: warn
given: '$.paths[*].get'
then:
function: 'getResourceIsNotPaginated'
7 changes: 4 additions & 3 deletions tools/spectral/ipa/rulesets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ For rule definitions, see [IPA-102.yaml](https://github.com/mongodb/openapi/blob

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

| Rule Name | Description | Severity |
| ----------------------------- | --------------------------------------------------------------- | -------- |
| xgen-IPA-104-resource-has-GET | APIs must provide a get method for resources. http://go/ipa/104 | warn |
| Rule Name | Description | Severity |
| --------------------------------------- | ----------------------------------------------------------------------------------------- | -------- |
| xgen-IPA-104-resource-has-GET | APIs must provide a get method for resources. http://go/ipa/104 | warn |
| xgen-IPA-104-GET-resource-not-paginated | The purpose of the get method is to return data from a single resource. http://go/ipa/104 | warn |

### IPA-109

Expand Down
39 changes: 39 additions & 0 deletions tools/spectral/ipa/rulesets/functions/getResourceIsNotPaginated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { isChild, isCustomMethod, isSingletonResource, getResourcePaths } from './utils/resourceEvaluation.js';
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
import { getAllSuccessfulResponseSchemas } from './utils/methodUtils.js';
import { hasException } from './utils/exceptions.js';
import { schemaIsArray, schemaIsPaginated } from './utils/schemaUtils.js';
import { resolveObject } from './utils/componentUtils.js';

const RULE_NAME = 'xgen-IPA-104-GET-resource-not-paginated';
const ERROR_MESSAGE_STANDARD_RESOURCE =
'Get methods should return data for a single resource. This method returns an array or a paginated response.';

export default (input, _, { path, documentInventory }) => {
const oas = documentInventory.resolved;
const resourcePath = path[1];
const resourcePaths = getResourcePaths(resourcePath, Object.keys(oas.paths));

if (isCustomMethod(resourcePath) || (!isChild(resourcePath) && !isSingletonResource(resourcePaths))) {
return;
}

const errors = [];

const responseSchemas = getAllSuccessfulResponseSchemas(input);
responseSchemas.forEach(({ schemaPath, schema }) => {
const fullPath = path.concat(schemaPath);
const responseObject = resolveObject(oas, fullPath);

if (hasException(responseObject, RULE_NAME)) {
collectException(responseObject, RULE_NAME, fullPath);
} else if (schemaIsPaginated(schema) || schemaIsArray(schema)) {
collectAndReturnViolation(fullPath, RULE_NAME, ERROR_MESSAGE_STANDARD_RESOURCE);
errors.push({ path: fullPath, message: ERROR_MESSAGE_STANDARD_RESOURCE });
} else {
collectAdoption(fullPath, RULE_NAME);
}
});

return errors;
};
6 changes: 3 additions & 3 deletions tools/spectral/ipa/rulesets/functions/singletonHasNoId.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
isSingletonResource,
} from './utils/resourceEvaluation.js';
import { hasException } from './utils/exceptions.js';
import { getAllSuccessfulGetResponseSchemas } from './utils/methodUtils.js';
import { getAllSuccessfulResponseSchemas } from './utils/methodUtils.js';
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';

const RULE_NAME = 'xgen-IPA-113-singleton-must-not-have-id';
Expand All @@ -28,8 +28,8 @@ export default (input, opts, { path, documentInventory }) => {
const resourcePaths = getResourcePaths(resourcePath, Object.keys(oas.paths));

if (isSingletonResource(resourcePaths) && hasGetMethod(input)) {
const resourceSchemas = getAllSuccessfulGetResponseSchemas(input);
if (resourceSchemas.some((schema) => schemaHasIdProperty(schema))) {
const resourceSchemas = getAllSuccessfulResponseSchemas(input['get']);
if (resourceSchemas.some(({ schema }) => schemaHasIdProperty(schema))) {
return collectAndReturnViolation(path, RULE_NAME, ERROR_MESSAGE);
}
}
Expand Down
Loading
Loading