Skip to content

Commit

Permalink
feat(rulesets): add new rule that requires sibling items field for ty…
Browse files Browse the repository at this point in the history
…pe array (#2632)

* feat(test-harness): make test-harness locale agnostic

* chore(deps): use node 18.18.2

* chore(test-harness): updated test-harness readme

* feat(rulesets): add new rule that requires sibling items field for type array

* chore(repo): update documentation

---------

Co-authored-by: Vazha Omanashvili <[email protected]>
  • Loading branch information
rainum and Vazha Omanashvili authored Jun 7, 2024
1 parent 9e906ea commit 24198bc
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
lts/*
18.18.2
31 changes: 31 additions & 0 deletions docs/reference/openapi-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,37 @@ TheBadModel:
- 8
```

### array-items

Schemas with `type: array`, require a sibling `items` field.

**Recommended:** Yes

**Good Example**

```yaml
TheGoodModel:
type: object
properties:
favoriteColorSets:
type: array
items:
type: array
items: {}
```

**Bad Example**

```yaml
TheBadModel:
type: object
properties:
favoriteColorSets:
type: array
items:
type: array
```

## OpenAPI v2.0-only

These rules will only apply to OpenAPI v2.0 documents.
Expand Down
81 changes: 81 additions & 0 deletions packages/rulesets/src/oas/__tests__/array-items.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { DiagnosticSeverity } from '@stoplight/types';

import testRule from '../../__tests__/__helpers__/tester';

testRule('array-items', [
{
name: 'valid case',
document: {
swagger: '2.0',
securityDefinitions: {
apikey: {},
},
paths: {
'/path': {
get: {
security: [
{
apikey: [],
},
],
},
},
},
},
errors: [],
},

{
name: 'array items sibling is present',
document: {
$ref: '#/',
responses: {
200: {
type: 'array',
items: {},
},
201: {
type: 'array',
items: {
type: 'array',
items: {},
},
},
},
openapi: '3.0.0',
},
errors: [],
},
{
name: 'array items sibling is missing',
document: {
$ref: '#/',
responses: {
200: {
type: 'array',
},
201: {
type: 'array',
items: {
type: 'array',
},
},
},
openapi: '3.0.0',
},
errors: [
{
code: 'array-items',
message: 'Schemas with "type: array", require a sibling "items" field',
path: ['responses', '200'],
severity: DiagnosticSeverity.Error,
},
{
code: 'array-items',
message: 'Schemas with "type: array", require a sibling "items" field',
path: ['responses', '201', 'items'],
severity: DiagnosticSeverity.Error,
},
],
},
]);
12 changes: 12 additions & 0 deletions packages/rulesets/src/oas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,18 @@ const ruleset = {
function: refSiblings,
},
},
'array-items': {
formats: [oas3_0],
message: 'Schemas with "type: array", require a sibling "items" field',
severity: 0,
recommended: true,
resolved: false,
given: "$..[?(@.type === 'array')]",
then: {
function: truthy,
field: 'items',
},
},
'typed-enum': {
description: 'Enum values must respect the specified type.',
message: '{{error}}',
Expand Down
15 changes: 10 additions & 5 deletions test-harness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@
## Prerequisites

* Install the project dependencies with `yarn`
* Generate the binary for your platform with `yarn build.binary`. This will *also* compile the project from TS -> JS
* Generate the binary for your platform with `yarn workspace @stoplight/spectral-cli build.binary`. This will *also* compile the project from TS -> JS

## Running the suite

Run `yarn test.harness` from your terminal

### Running a selected tests

You can run one or selected tests using `TESTS` env variable.
If you want multiple test files to be run separate them with commas.
Use paths relative to the `./scenarios` directory.
Test Harness uses Jest under the hood. You can use all CLI options that Jest supports: https://jestjs.io/docs/cli

E.g. run `TESTS=parameters-ac1.oas2.scenario,validate-body-params/form-byte-format-fail.oas2.scenario yarn test.harness`
You can run one or multiple tests by passing a path to `test-harness` command.
All scenarios are converted to `.js` files under the `./tests` directory.
Hence you must use paths relative to the `./tests` directory, like in the following example:

```bash
# path to scenario file: `test-harness/scenarios/require-module.scenario`
yarn test.harness test-harness/tests/require-module
```

### Matching test files

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
====test====
Schemas with "type: array", require a sibling "items" field
====document====
openapi: 3.0.3
info:
title: test
description: Test specification file
version: '1.0'
contact:
name: John Doe
url: 'https://example.com'
email: [email protected]
license:
name: Apache 2.0
url: 'https://www.apache.org/licenses/LICENSE-2.0'
servers:
- url: 'http://localhost:3000'
tags:
- name: list-endpoint
description: Endpoint for listing objects
paths:
/users:
get:
summary: List Users
operationId: get-users
description: List all Users
tags:
- list-endpoint
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
favoriteColorSets:
type: array
items:
type: array

====asset:ruleset====
const { oas } = require('@stoplight/spectral-rulesets');
module.exports = oas;
====command====
{bin} lint {document} --ruleset "{asset:ruleset}"
====stdout====
{document}
36:27 error array-items Schemas with "type: array", require a sibling "items" field paths./users.get.responses[200].content.application/json.schema.properties.favoriteColorSets.items

✖ 1 problem (1 error, 0 warnings, 0 infos, 0 hints)
10 changes: 8 additions & 2 deletions test-harness/src/suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ if (scenario.command === null) {
// executing Date() before or after spawnNode were constantly leading to occasional mismatches,
// as the success of that approach was highly bound to the time spent on the actual spawnNode call
// this is a tad smarter, because instead of naively hoping the date will match, we try to extract the date from the actual output
// this regular expression matches "00:43:59" in "Thu Jul 08 2021 00:43:59 GMT+0200 (Central European Summer Time)"
const date = RegExp(escapeRegExp(String(Date())).replace(/(\d\d:){2}\d\d/, '(\\d\\d:){2}\\d\\d'));
// this regular expression matches "00:43:59" in "Thu Jul 08 2021 00:43:59 GMT+0200 (Central European Summer Time)".
// this regular expression is locale agnostic: it will match "Fri May 31 2024 18:26:32 GMT+0300 (за східноєвропейським літнім часом)"
const date = RegExp(
escapeRegExp(String(Date()))
.replace(/(\d\d:){2}\d\d/, '(\\d\\d:){2}\\d\\d')
.replace(/\s\\\(.+\\\)/, '\\s+\\([\\w\\s]+\\)'),
);

Reflect.defineProperty(env, 'date', {
configurable: true,
enumerable: true,
Expand Down

0 comments on commit 24198bc

Please sign in to comment.