@snyk/sweater-comb
Advanced tools
Comparing version 0.1.2 to 0.3.1
module.exports = { | ||
"env": { | ||
"commonjs": true, | ||
"jest": true, | ||
"node": true, | ||
"es2021": true | ||
}, | ||
"extends": "eslint:recommended", | ||
"parserOptions": { | ||
"ecmaVersion": 12 | ||
}, | ||
"rules": { | ||
} | ||
env: { | ||
commonjs: true, | ||
jest: true, | ||
node: true, | ||
es2021: true, | ||
}, | ||
extends: ['eslint:recommended', 'prettier'], | ||
parserOptions: { | ||
ecmaVersion: 12, | ||
}, | ||
rules: {}, | ||
}; |
@@ -1,4 +0,2 @@ | ||
const camelCaseRegex = /^([a-z]+([A-Z][a-z]+)*)$/; | ||
module.exports = (targetVal, opts, context) => { | ||
module.exports = (targetVal, opts) => { | ||
const re = new RegExp(opts.match); | ||
@@ -8,2 +6,3 @@ const result = targetVal.filter((item) => { | ||
}); | ||
if (!result || result.length === 0) { | ||
@@ -10,0 +9,0 @@ return [ |
@@ -1,6 +0,4 @@ | ||
const camelCaseRegex = /^([a-z]+([A-Z][a-z]+)*)$/; | ||
module.exports = (targetVal, opts, context) => { | ||
for (var i = 0; i < opts.path.length; i++) { | ||
targetVal = targetVal[opts.path[i]]; | ||
module.exports = (targetVal, opts) => { | ||
for (let item of opts.path) { | ||
targetVal = targetVal && targetVal[item]; | ||
if (!targetVal) { | ||
@@ -7,0 +5,0 @@ break; |
{ | ||
"name": "@snyk/sweater-comb", | ||
"version": "v0.1.2+202109280930-0ebd495", | ||
"version": "0.3.1", | ||
"description": "Snyk API linting rules", | ||
@@ -30,5 +30,10 @@ "main": "apinext.yaml", | ||
"eslint": "^7.32.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-plugin-jest": "^24.5.2", | ||
"immer": "^9.0.6", | ||
"jest": "^27.0.6", | ||
"pac-resolver": "^5.0.0", | ||
"prettier": "^2.3.2" | ||
} | ||
}, | ||
"dependencies": {} | ||
} |
# @snyk/sweater-comb | ||
OpenAPI linting rules for Snyk APIs. | ||
Sweats the small stuff, so you don't have to. OpenAPI linting rules for Snyk APIs. | ||
# Usage | ||
# [Intro](docs/intro.md) | ||
At Snyk, we're starting an API program that aims to maximize the value we provide to developers and the extensibility of our platform through our APIs. | ||
Such an API needs some guardrails to stay cohesive, consistent and "unsurprising" to its consumers, as the platform scales in the number of concepts it provides and the number of teams delivering them. | ||
Sweater Comb helps provide some of those guardrails with automation, initially by applying custom [Spectral](https://stoplight.io/open-source/spectral/) linter rules to our OpenAPI specifications. | ||
[Read more about our API program here](docs/intro.md). | ||
# [JSON API: The Good Parts](docs/jsonapi.md) | ||
## What is JSON API? | ||
[JSON API](https://jsonapi.org/) is a standard for representing resources as JSON data. | ||
Generally, our API adheres closely to the [JSON API specification](https://jsonapi.org/format/). [JSON API: The Good Parts](docs/jsonapi.md) describes how we adapted JSON API into our API standards. | ||
## Why build on JSON API? | ||
We found JSON API to be an excellent starting point for a resource-based API, formatting and structuring JSON data in requests and responses. Leveraging JSON API's opinionated choices enabled us to focus more on designing and building the actual content of our API. | ||
## Our JSON API implementation, by example | ||
What does JSON API look like? What do I need to know to get started building a resource in 5 minutes? Let’s cover the basics first; you can always refer to the JSON API specification for a deeper understanding of specific details. | ||
[Read more about our experiences with JSON API here](docs/jsonapi.md). | ||
# [Versioning](docs/version.md) | ||
How we version our API, and more to the point, API requirements necessary in order to implement our versioning scheme. | ||
[Read more about how we version here](docs/version.md). | ||
# [Snyk API Standards](docs/standards.md) | ||
[Everything else; other requirements we found necessary to keep our API nice and neat](docs/standards.md). | ||
# Installation | ||
## NPM | ||
@@ -19,16 +57,1 @@ | ||
``` | ||
## Other projects | ||
## Coming soon | ||
Integration with [vervet](https://github.com/snyk/vervet), for comprehensive | ||
linting of OpenAPI content at the project level: directory structure, applying | ||
rules to different API conventions appropriately (json:api, RPC, miscellanous | ||
endpoints). | ||
# Contact | ||
[#ask-elk](https://snyk.slack.com/archives/C01HY22DV0F) |
const { Spectral } = require('@stoplight/spectral-core'); | ||
const { loadRules, loadSpec } = require('./utils'); | ||
const { loadRules, loadSpec, getAllErrors } = require('./utils'); | ||
let rules; | ||
const openApiDocument = { | ||
openapi: '3.0.3', | ||
info: { | ||
title: 'MyService', | ||
version: '3.0.0', | ||
}, | ||
servers: [], | ||
components: { | ||
headers: {}, | ||
parameters: {}, | ||
responses: {}, | ||
schemas: {}, | ||
}, | ||
paths: {}, | ||
}; | ||
beforeAll(async () => { | ||
rules = await loadRules('jsonapi.yaml'); | ||
}); | ||
describe('JSON API Schema', () => { | ||
let spectral; | ||
let rules; | ||
let spec; | ||
it('fails on version schema rules', async () => { | ||
const spectral = new Spectral(); | ||
spectral.setRuleset(rules); | ||
const result = await spectral.run( | ||
loadSpec('fixtures/jsonapiSchema.fail.yaml'), | ||
); | ||
expect(result).not.toHaveLength(0); | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-get-post-response-data-schema', | ||
message: '"properties" property must have required property "id"', | ||
path: [ | ||
'paths', | ||
'/goof/badJsonApi/missingDataId', | ||
'get', | ||
'responses', | ||
'200', | ||
'content', | ||
'application/vnd.api+json', | ||
'schema', | ||
'properties', | ||
'data', | ||
'properties', | ||
], | ||
}), | ||
expect.objectContaining({ | ||
code: 'jsonapi-get-post-response-data-schema', | ||
message: '"data" property must have required property "properties"', | ||
path: [ | ||
'paths', | ||
'/goof/badJsonApi/dataIdNotUuid', | ||
'get', | ||
'responses', | ||
'200', | ||
'content', | ||
'application/vnd.api+json', | ||
'schema', | ||
'properties', | ||
'data', | ||
], | ||
}), | ||
expect.objectContaining({ | ||
code: 'jsonapi-get-post-response-data-schema', | ||
message: '"properties.data" property must exist', | ||
path: [ | ||
'paths', | ||
'/goof/badJsonApi/dataMissingType', | ||
'get', | ||
'responses', | ||
'200', | ||
'content', | ||
'application/vnd.api+json', | ||
'schema', | ||
'properties', | ||
], | ||
}), | ||
expect.objectContaining({ | ||
code: 'jsonapi-response-jsonapi', | ||
message: 'JSON:API response schema requires jsonapi property', | ||
path: [ | ||
'paths', | ||
'/goof/badJsonApi/dataMissingType', | ||
'get', | ||
'responses', | ||
'200', | ||
'content', | ||
'application/vnd.api+json', | ||
], | ||
}), | ||
expect.objectContaining({ | ||
code: 'jsonapi-get-post-response-data', | ||
message: 'JSON:API response schema requires data property', | ||
path: [ | ||
'paths', | ||
'/goof/badJsonApi/dataMissingType', | ||
'get', | ||
'responses', | ||
'200', | ||
'content', | ||
'application/vnd.api+json', | ||
], | ||
}), | ||
]), | ||
); | ||
beforeAll(async () => { | ||
rules = await loadRules('jsonapi.yaml'); | ||
spectral = new Spectral(); | ||
spectral.setRuleset(rules); | ||
spec = loadSpec('fixtures/jsonapiSchema.fail.yaml'); | ||
}); | ||
describe('Schema rules', () => { | ||
let result; | ||
it('fails on version schema rules', async () => { | ||
// Act | ||
result = await spectral.run(spec); | ||
// Assert | ||
expect(result).not.toHaveLength(0); | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-get-post-response-data-schema', | ||
message: '"properties" property must have required property "id"', | ||
path: [ | ||
'paths', | ||
'/goof/badJsonApi/missingDataId', | ||
'get', | ||
'responses', | ||
'200', | ||
'content', | ||
'application/vnd.api+json', | ||
'schema', | ||
'properties', | ||
'data', | ||
'properties', | ||
], | ||
}), | ||
expect.objectContaining({ | ||
code: 'jsonapi-get-post-response-data-schema', | ||
message: '"data" property must have required property "properties"', | ||
path: [ | ||
'paths', | ||
'/goof/badJsonApi/dataIdNotUuid', | ||
'get', | ||
'responses', | ||
'200', | ||
'content', | ||
'application/vnd.api+json', | ||
'schema', | ||
'properties', | ||
'data', | ||
], | ||
}), | ||
expect.objectContaining({ | ||
code: 'jsonapi-get-post-response-data-schema', | ||
message: '"properties.data" property must exist', | ||
path: [ | ||
'paths', | ||
'/goof/badJsonApi/dataMissingType', | ||
'get', | ||
'responses', | ||
'200', | ||
'content', | ||
'application/vnd.api+json', | ||
'schema', | ||
'properties', | ||
], | ||
}), | ||
expect.objectContaining({ | ||
code: 'jsonapi-response-jsonapi', | ||
message: 'JSON:API response schema requires jsonapi property', | ||
path: [ | ||
'paths', | ||
'/goof/badJsonApi/dataMissingType', | ||
'get', | ||
'responses', | ||
'200', | ||
'content', | ||
'application/vnd.api+json', | ||
], | ||
}), | ||
expect.objectContaining({ | ||
code: 'jsonapi-get-post-response-data', | ||
message: 'JSON:API response schema requires data property', | ||
path: [ | ||
'paths', | ||
'/goof/badJsonApi/dataMissingType', | ||
'get', | ||
'responses', | ||
'200', | ||
'content', | ||
'application/vnd.api+json', | ||
], | ||
}), | ||
expect.objectContaining({ | ||
code: 'jsonapi-post-response-201', | ||
message: | ||
'Post responses must respond with a 201 status code on success', | ||
path: [ | ||
'paths', | ||
'/goof/bad_post_status_code', | ||
'post', | ||
'responses', | ||
'200', | ||
], | ||
}), | ||
expect.objectContaining({ | ||
code: 'jsonapi-patch-response-data', | ||
message: 'JSON:API patch 200 response requires a schema', | ||
path: [ | ||
'paths', | ||
'/goof/patch_missing_content', | ||
'patch', | ||
'responses', | ||
'200', | ||
], | ||
}), | ||
]), | ||
); | ||
}); | ||
it('fails if 200 patch response contains invalid properties', async () => { | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-patch-response-200-schema', | ||
message: 'Property "anotherField" is not expected to be here', | ||
path: [ | ||
'paths', | ||
'/goof/patch_no_meta_only', | ||
'patch', | ||
'responses', | ||
'200', | ||
'content', | ||
'application/vnd.api+json', | ||
'schema', | ||
'properties', | ||
], | ||
}), | ||
]), | ||
); | ||
}); | ||
it('fails if 200 patch response does not have meta property', async () => { | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-patch-response-200-schema', | ||
message: '"properties" property must have required property "meta"', | ||
path: [ | ||
'paths', | ||
'/goof/patch_no_data', | ||
'patch', | ||
'responses', | ||
'200', | ||
'content', | ||
'application/vnd.api+json', | ||
'schema', | ||
'properties', | ||
], | ||
}), | ||
]), | ||
); | ||
}); | ||
it('fails if 204 patch response has content', async () => { | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-patch-response-204-schema', | ||
message: '"content" property must be falsy', | ||
}), | ||
]), | ||
); | ||
}); | ||
it('fails if delete response does not use 200 or 204 status code', async () => { | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-delete-response-statuses', | ||
message: 'Delete endpoints can only use 200 or 204 status codes', | ||
}), | ||
]), | ||
); | ||
}); | ||
it('fails if 4xx is not 400,401,403,404,409, or 429', async () => { | ||
const result = await spectral.run(spec); | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-4xx-response-codes', | ||
message: | ||
'Only 400, 401, 403, 404, 409, and 429 status codes can be returned in the 4xx.', | ||
}), | ||
]), | ||
); | ||
}); | ||
it('fails if 200 delete response does not have meta', async () => { | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-delete-response-200', | ||
message: '"properties" property must have required property "meta"', | ||
}), | ||
]), | ||
); | ||
}); | ||
it('fails if 204 delete response has content', async () => { | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-delete-response-204', | ||
message: '"content" property must be falsy', | ||
}), | ||
]), | ||
); | ||
}); | ||
}); | ||
describe('Schema Rules', () => { | ||
let result; | ||
it('fails if a non-204 response has no content', async () => { | ||
// Arrange | ||
const spec = { | ||
...openApiDocument, | ||
}; | ||
spec.paths = { | ||
'/org/{org_id}/no_content': { | ||
get: { | ||
responses: { | ||
200: {}, | ||
201: {}, | ||
204: {}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
// Act | ||
result = await spectral.run(spec); | ||
// Assert | ||
const errorArray = getAllErrors(result, 'jsonapi-content-non-204'); | ||
expect(errorArray).toHaveLength(2); | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-content-non-204', | ||
message: 'Responses from non-204 statuses must have content', | ||
}), | ||
]), | ||
); | ||
}); | ||
}); | ||
describe('Self linking rules', () => { | ||
it('fails if a GET 200 response does not have a top-level self link', async () => { | ||
// Arrange | ||
const spec = { | ||
...openApiDocument, | ||
}; | ||
spec.paths = { | ||
'/org/{org_id}/example': { | ||
get: { | ||
responses: { | ||
200: { | ||
content: { | ||
'application/vnd.api+json': { | ||
schema: { | ||
type: 'object', | ||
properties: { | ||
data: {}, | ||
jsonapi: {}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
const errorArray = getAllErrors(result, 'jsonapi-self-links-get-patch'); | ||
expect(errorArray).toHaveLength(1); | ||
}); | ||
it('fails if a PATCH 200 response does not have a top-level self link', async () => { | ||
// Arrange | ||
const spec = { | ||
...openApiDocument, | ||
}; | ||
spec.paths = { | ||
'/org/{org_id}/example': { | ||
patch: { | ||
parameters: [], | ||
responses: { | ||
200: { | ||
content: { | ||
'application/vnd.api+json': { | ||
schema: { | ||
type: 'object', | ||
properties: { | ||
meta: {}, | ||
}, | ||
required: ['meta'], | ||
additionalProperties: false, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
const errorArray = getAllErrors(result, 'jsonapi-self-links-get-patch'); | ||
expect(errorArray).toHaveLength(1); | ||
}); | ||
it('fails if a POST 201 response does not have a top-level self link', async () => { | ||
// Arrange | ||
const spec = { | ||
...openApiDocument, | ||
}; | ||
spec.paths = { | ||
'/org/{org_id}/example': { | ||
post: { | ||
parameters: [], | ||
responses: { | ||
201: { | ||
content: { | ||
'application/vnd.api+json': { | ||
schema: { | ||
type: 'object', | ||
properties: { | ||
jsonapi: {}, | ||
data: {}, | ||
}, | ||
required: ['jsonapi', 'data'], | ||
additionalProperties: false, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
const errorArray = getAllErrors(result, 'jsonapi-self-links-post'); | ||
expect(errorArray).toHaveLength(1); | ||
}); | ||
}); | ||
describe('Collection Rules', () => { | ||
it('fails if collection requests do not include pagination parameters', async () => { | ||
// Arrange | ||
const spec = { | ||
...openApiDocument, | ||
}; | ||
spec.paths = { | ||
'/org/{org_id}/collection_pagination_parameters': { | ||
get: { | ||
parameters: [ | ||
{ | ||
description: | ||
'Return the page of results immediately after this cursor', | ||
in: 'query', | ||
name: 'starting_after', | ||
schema: { | ||
type: 'string', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-pagination-collection-parameters', | ||
message: 'Collection requests must include pagination parameters', | ||
}), | ||
]), | ||
); | ||
}); | ||
it('fails if collection responses do not include pagination links', async () => { | ||
// Arrange | ||
const spec = { | ||
...openApiDocument, | ||
}; | ||
spec.paths = { | ||
'/org/{org_id}/collection_pagination_links': { | ||
get: { | ||
responses: { | ||
200: { | ||
content: { | ||
'application/vnd.api+json': { | ||
schema: { | ||
type: 'object', | ||
properties: { | ||
jsonapi: { | ||
$ref: '#/components/schemas/JsonApi', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-pagination-collection-links', | ||
message: | ||
'Responses for collection requests must include pagination links', | ||
}), | ||
]), | ||
); | ||
}); | ||
it('fails if non-GET for collections requests include pagination parameters', async () => { | ||
// Arrange | ||
const spec = { | ||
...openApiDocument, | ||
}; | ||
spec.components.parameters = { | ||
StartingAfter: { | ||
description: | ||
'Return the page of results immediately after this cursor', | ||
in: 'query', | ||
name: 'starting_after', | ||
schema: { | ||
type: 'string', | ||
}, | ||
}, | ||
}; | ||
spec.paths = { | ||
'/org/{org_id}/non_get_pagination_parameters': { | ||
post: { | ||
parameters: [ | ||
{ | ||
$ref: '#/components/parameters/Version', | ||
}, | ||
{ | ||
$ref: '#/components/parameters/StartingAfter', | ||
}, | ||
], | ||
}, | ||
patch: { | ||
parameters: [ | ||
{ | ||
$ref: '#/components/parameters/Version', | ||
}, | ||
{ | ||
$ref: '#/components/parameters/StartingAfter', | ||
}, | ||
], | ||
}, | ||
delete: { | ||
parameters: [ | ||
{ | ||
$ref: '#/components/parameters/Version', | ||
}, | ||
{ | ||
$ref: '#/components/parameters/StartingAfter', | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
expect(result).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
code: 'jsonapi-no-pagination-parameters', | ||
message: 'Non-GET requests should not allow pagination parameters', | ||
}), | ||
]), | ||
); | ||
}); | ||
}); | ||
describe('Relationships', () => { | ||
it('passes with correct relationship schema', async () => { | ||
// Arrange | ||
const spec = { | ||
...openApiDocument, | ||
}; | ||
spec.paths = { | ||
'/org/{org_id}/relationship': { | ||
get: { | ||
responses: { | ||
200: { | ||
content: { | ||
'application/vnd.api+json': { | ||
schema: { | ||
type: 'object', | ||
properties: { | ||
data: { | ||
type: 'object', | ||
properties: { | ||
type: { | ||
type: 'string', | ||
}, | ||
id: { | ||
type: 'string', | ||
}, | ||
attributes: {}, | ||
relationships: { | ||
type: 'object', | ||
additionalProperties: { | ||
type: 'object', | ||
properties: { | ||
data: { | ||
properties: { | ||
type: { | ||
type: 'string', | ||
}, | ||
id: { | ||
type: 'string', | ||
format: 'uuid', | ||
}, | ||
}, | ||
required: ['type', 'id'], | ||
}, | ||
links: { | ||
type: 'object', | ||
properties: { | ||
related: { | ||
type: 'string', | ||
}, | ||
}, | ||
}, | ||
}, | ||
required: ['data'], | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
const errors = getAllErrors( | ||
result, | ||
'jsonapi-response-relationship-schema', | ||
); | ||
expect(errors).toHaveLength(0); | ||
}); | ||
it('fails on incorrect relationship schema', async () => { | ||
// Arrange | ||
const spec = { | ||
...openApiDocument, | ||
}; | ||
spec.paths = { | ||
'/org/{org_id}/relationship': { | ||
get: { | ||
responses: { | ||
200: { | ||
content: { | ||
'application/vnd.api+json': { | ||
schema: { | ||
type: 'object', | ||
properties: { | ||
data: { | ||
type: 'object', | ||
properties: { | ||
type: { | ||
type: 'string', | ||
}, | ||
id: { | ||
type: 'string', | ||
}, | ||
attributes: {}, | ||
relationships: { | ||
type: 'object', | ||
additionalProperties: { | ||
type: 'object', | ||
properties: { | ||
data: { | ||
properties: { | ||
type: { | ||
type: 'string', | ||
}, | ||
id: { | ||
type: 'string', | ||
format: 'uuid', | ||
}, | ||
}, | ||
required: ['type', 'id'], | ||
}, | ||
}, | ||
required: ['data'], | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
// Act | ||
const result = await spectral.run(spec); | ||
// Assert | ||
const errors = getAllErrors( | ||
result, | ||
'jsonapi-response-relationship-schema', | ||
); | ||
expect(errors).toHaveLength(1); | ||
}); | ||
}); | ||
}); |
@@ -25,5 +25,12 @@ const path = require('path'); | ||
function getAllErrors(errors, errorCode = '') { | ||
if (!Array.isArray(errors)) return []; | ||
return errors.filter((item) => item.code && item.code === errorCode); | ||
} | ||
module.exports = { | ||
loadRules, | ||
loadSpec, | ||
getAllErrors, | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
449767
68
1437
57
12
2