Skip to content

Commit

Permalink
feat: Created Collections rules (DPE-135)
Browse files Browse the repository at this point in the history
  • Loading branch information
sps-benjacobs committed Jul 11, 2024
1 parent 6a0445e commit a0e0daf
Show file tree
Hide file tree
Showing 12 changed files with 881 additions and 11 deletions.
1 change: 1 addition & 0 deletions rulesets/src/.spectral.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extends:
- serialization.ruleset.yml
- url-structure.ruleset.yml
- webhooks.ruleset.yml
- collections.ruleset.yml

rules:
# not keeping this, just off for testing in this repo
Expand Down
136 changes: 136 additions & 0 deletions rulesets/src/collections.ruleset.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
rules:
##### General #####
sps-no-collection-paging-capability:
description: Response bodies from collection endpoints must offer paging capability.
severity: error
given: $.paths[?([email protected](/.*\/\{[^}]+\}$/))].get.responses['200'].content.application/json.schema.properties
then:
- field: "paging"
function: truthy
- field: "paging.type"
function: pattern
functionOptions:
match: "object"

##### Root Element #####
sps-collection-missing-results-array:
description: Response bodies must have a root element called results and is an array of objects.
severity: error
given: $.paths[?([email protected](/.*\/\{[^}]+\}$/))].get.responses['200'].content.application/json.schema.properties.results
then:
- field: type
function: pattern
functionOptions:
match: "array"
- field: items.type
function: pattern
functionOptions:
match: "object"

##### Pagination #####
sps-missing-pagination-query-parameters:
description: 'Collection GET endpoints must support pagination using query parameters: limit, offset, cursor.'
severity: error
given: $.paths[?([email protected](/.*\/\{[^}]+\}$/))].get
then:
- field: parameters
function: schema
functionOptions:
schema:
type: array
items:
type: object
properties:
name:
type: string
required:
- name
minItems: 3
contains:
anyOf:
- properties:
name:
const: offset
- properties:
name:
const: limit
- properties:
name:
const: cursor

sps-post-request-body-missing-paging-object:
description: "POST collection endpoints MUST have a request body schema that includes paging parameters."
severity: error
given: $.paths[?([email protected](/.*\/\{[^}]+\}$/))].post.requestBody.content.application/json.schema.properties.paging
then:
field: "type"
function: pattern
functionOptions:
match: "object"

##### FILTERING #####
sps-disallow-resource-identifier-filtering:
description: "Resource identifier filtering is not allowed. Use the resource identifier in the URL path."
severity: warn
given: $.paths..get.parameters.[?(@.in=='query' && @.name=='id')]
then:
field: "name"
function: pattern
functionOptions:
notMatch: "^id$"

sps-unreasonable-query-parameters-limit:
description: "Filtering query parameters SHOULD have a reasonable limit, no more than 12."
severity: warn
given: $.paths..get
then:
function: schema
functionOptions:
dialect: "draft2020-12"
schema:
type: object
properties:
parameters:
type: array
minContains: 0
maxContains: 12
contains:
type: object
properties:
in:
const: query

sps-multiple-filter-parameters:
description: "Ensures the 'filter' query parameter is specified only once."
severity: error
given: $.paths..get
then:
function: schema
functionOptions:
dialect: "draft2020-12"
schema:
type: object
properties:
parameters:
type: array
minContains: 0
maxContains: 1
contains:
type: object
properties:
in:
const: query
name:
pattern: "(filter|Filter)"

##### SORTING #####
sps-sorting-parameters-only-get-requests:
description: "Sorting query parameters SHOULD only be used with GET endpoints."
severity: error
given: $.paths.*[?(@property!='get')].parameters.[?(@.in=='query')]
then:
field: "name"
function: pattern
functionOptions:
notMatch: "^sort|sorting|sort_by|order|ordering|order_by$"

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const { SpectralTestHarness } = require("../harness/spectral-test-harness.js");

describe("sps-collection-missing-results-array", () => {
let spectral = null;
const ruleName = "sps-collection-missing-results-array";
const ruleset = "src/collections.ruleset.yml";

beforeEach(async () => {
spectral = new SpectralTestHarness(ruleset);
});

test("valid - collection response with results array of objects", async () => {
const spec = `
openapi: 3.0.0
info:
title: Sample API
version: 1.0.0
paths:
/users:
get:
summary: Get a list of users
responses:
'200':
description: A list of users
content:
application/json:
schema:
properties:
results:
type: array
items:
type: object
paging:
type: object
`;

await spectral.validateSuccess(spec, ruleName);
});

test("invalid - collection response - results is not an array", async () => {
const spec = `
openapi: 3.0.0
info:
title: Sample API
version: 1.0.0
paths:
/users:
get:
summary: Get a list of users
responses:
'200':
description: A list of users
content:
application/json:
schema:
properties:
results:
type: object
paging:
type: object
`;

await spectral.validateFailure(spec, ruleName, "Error", 1);
});

test("invalid - collection response - results is not an array of objects", async () => {
const spec = `
openapi: 3.0.0
info:
title: Sample API
version: 1.0.0
paths:
/users:
get:
summary: Get a list of users
responses:
'200':
description: A list of users
content:
application/json:
schema:
properties:
results:
type: array
items:
type: string
paging:
type: object
`;

await spectral.validateFailure(spec, ruleName, "Error", 1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const { SpectralTestHarness } = require("../harness/spectral-test-harness.js");

describe("sps-disallow-resource-identifier-filtering", () => {
let spectral = null;
const ruleName = "sps-disallow-resource-identifier-filtering";
const ruleset = "src/collections.ruleset.yml";

beforeEach(async () => {
spectral = new SpectralTestHarness(ruleset);
});

test("valid - resource identifier is within the path of the endpoint", async () => {
const spec = `
openapi: 3.0.0
info:
title: Sample API
version: 1.0.0
paths:
/users/{id}:
get:
summary: Get a list of users
parameters:
- name: id
in: path
required: true
responses:
'200':
description: A list of users
`;

await spectral.validateSuccess(spec, ruleName);
});

test("invalid - resource identifier is defined as a query parameter", async () => {
const spec = `
openapi: 3.0.0
info:
title: Sample API
version: 1.0.0
paths:
/users:
get:
summary: Get a list of users
parameters:
- name: id
in: query
required: false
`;

await spectral.validateFailure(spec, ruleName, "Warning", 1);
});
});
Loading

0 comments on commit a0e0daf

Please sign in to comment.