@zapier/spectral-api-ruleset
Advanced tools
Comparing version 0.0.3 to 0.0.4
# @zapier/spectral-api-ruleset | ||
## 0.0.4 | ||
### Patch Changes | ||
- c85b6fa: Add description, documentationUrl, unit tests and integration tests | ||
## 0.0.3 | ||
@@ -4,0 +10,0 @@ |
import type { RuleDefinition } from '@stoplight/spectral-core'; | ||
export declare type Function = Extract<RuleDefinition['then'], Array<any>>[any]['function']; | ||
export declare type Message = Extract<ReturnType<Function>, Array<any>>[any]; |
@@ -11,30 +11,48 @@ 'use strict'; | ||
/** | ||
* Checks: Each path has a version that follows the pattern 'v<MAJOR_VERSION_NUMBER' and the number is | ||
* the same for all paths. | ||
* | ||
* Versioning API Guidelines: https://engineering.zapier.com/guides/api-design-guidelines/versioning/ | ||
*/ | ||
const checkMajorVersion = (paths, _options, { | ||
document | ||
const doNotUseValue = (targetVal, { | ||
values | ||
}) => { | ||
if (paths === null) { | ||
return undefined; | ||
if (values.includes(targetVal)) { | ||
return [{ | ||
message: `Do not use: ${targetVal}` | ||
}]; | ||
} | ||
}; | ||
const base_url = ___default["default"].get(document, 'data.servers[0].url', ''); | ||
const singleMajorVersionInPaths = (paths, _options, { | ||
document, | ||
path | ||
}) => { | ||
// Find URL of first non-local server | ||
let baseURL = ''; | ||
const servers = ___default["default"].get(document, 'data.servers', ''); | ||
if (Array.isArray(servers)) { | ||
for (const server of servers) { | ||
const url = ___default["default"].get(server, 'url'); | ||
if (typeof url === 'string' && url.startsWith('https://')) { | ||
baseURL = url; | ||
break; | ||
} | ||
} | ||
} | ||
const messages = []; | ||
const foundVersions = new Set(); | ||
Object.keys(paths).forEach(path => { | ||
const url = new URL(`${base_url}${path}`, 'https://example.com'); | ||
const pathname_segments = url.pathname.split('/'); | ||
const version = pathname_segments.find(segment => segment.match(/v[0-9]+/)); | ||
Object.keys(paths).forEach(endpointPath => { | ||
// Prepend server URL to the possibly relative path | ||
const url = new URL(`${baseURL}${endpointPath}`, 'https://example.com'); | ||
const fullPath = url.pathname; | ||
const match = fullPath.match(/\/(v[0-9])\//); | ||
if (version !== undefined) { | ||
foundVersions.add(version); | ||
if (match) { | ||
foundVersions.add(match[1]); | ||
} else { | ||
messages.push({ | ||
severity: 'error', | ||
message: `Major version missing in path ${path}` | ||
message: `Major version missing in path: ${fullPath}`, | ||
// When returning multiple messages, each must have a unique and correct path | ||
// https://meta.stoplight.io/docs/spectral/ZG9jOjI1MTkw-custom-functions#returning-multiple-results | ||
path: [...path, endpointPath] | ||
}); | ||
@@ -46,4 +64,4 @@ } | ||
messages.push({ | ||
severity: 'error', | ||
message: `Found multiple major versions: ${[...foundVersions].join(', ')}` | ||
message: `Found multiple major versions: ${[...foundVersions].join(', ')}`, | ||
path | ||
}); | ||
@@ -55,40 +73,27 @@ } | ||
/** | ||
* Checks: The sorting parameter should be `order_by` | ||
* | ||
* Sorting API Guidelines: https://engineering.zapier.com/guides/api-design-guidelines/searching-filtering-sorting/#sorting | ||
*/ | ||
const checkSortingParameterName = parameters => { | ||
if (parameters === null) { | ||
return; | ||
} | ||
const messages = []; | ||
for (const parameter of parameters) { | ||
if (['sort_by', 'ordering'].includes(parameter.name)) { | ||
messages.push({ | ||
severity: 'error', | ||
message: `Found sorting parameter name which doesn't conform to the API Guidelines: ${parameter.name}, should use: order_by.` | ||
}); | ||
} | ||
} | ||
return messages; | ||
}; | ||
const UNWANTED_SORTING_PARAMETERS = ['sort_by', 'ordering']; | ||
const ruleset = { | ||
rules: { | ||
'major-version': { | ||
given: ['$.paths'], | ||
'single-major-version-in-paths': { | ||
description: 'All paths need to contain the same major version as /v[0-9]+/', | ||
documentationUrl: 'https://engineering.zapier.com/guides/api-design-guidelines/versioning/', | ||
given: '$.paths', | ||
message: '{{error}}', | ||
severity: 'error', | ||
then: { | ||
function: checkMajorVersion | ||
function: singleMajorVersionInPaths | ||
} | ||
}, | ||
'sorting-parameter': { | ||
given: '$.paths[*][*].parameters', | ||
description: `For sorting order_by should be used and not: ${UNWANTED_SORTING_PARAMETERS.join(', ')}`, | ||
documentationUrl: 'https://engineering.zapier.com/guides/api-design-guidelines/searching-filtering-sorting/#sorting', | ||
given: '$..parameters[*]', | ||
message: '{{error}}', | ||
severity: 'error', | ||
then: { | ||
function: checkSortingParameterName | ||
field: 'name', | ||
function: doNotUseValue, | ||
functionOptions: { | ||
values: UNWANTED_SORTING_PARAMETERS | ||
} | ||
} | ||
@@ -95,0 +100,0 @@ } |
@@ -11,30 +11,48 @@ 'use strict'; | ||
/** | ||
* Checks: Each path has a version that follows the pattern 'v<MAJOR_VERSION_NUMBER' and the number is | ||
* the same for all paths. | ||
* | ||
* Versioning API Guidelines: https://engineering.zapier.com/guides/api-design-guidelines/versioning/ | ||
*/ | ||
const checkMajorVersion = (paths, _options, { | ||
document | ||
const doNotUseValue = (targetVal, { | ||
values | ||
}) => { | ||
if (paths === null) { | ||
return undefined; | ||
if (values.includes(targetVal)) { | ||
return [{ | ||
message: `Do not use: ${targetVal}` | ||
}]; | ||
} | ||
}; | ||
const base_url = ___default["default"].get(document, 'data.servers[0].url', ''); | ||
const singleMajorVersionInPaths = (paths, _options, { | ||
document, | ||
path | ||
}) => { | ||
// Find URL of first non-local server | ||
let baseURL = ''; | ||
const servers = ___default["default"].get(document, 'data.servers', ''); | ||
if (Array.isArray(servers)) { | ||
for (const server of servers) { | ||
const url = ___default["default"].get(server, 'url'); | ||
if (typeof url === 'string' && url.startsWith('https://')) { | ||
baseURL = url; | ||
break; | ||
} | ||
} | ||
} | ||
const messages = []; | ||
const foundVersions = new Set(); | ||
Object.keys(paths).forEach(path => { | ||
const url = new URL(`${base_url}${path}`, 'https://example.com'); | ||
const pathname_segments = url.pathname.split('/'); | ||
const version = pathname_segments.find(segment => segment.match(/v[0-9]+/)); | ||
Object.keys(paths).forEach(endpointPath => { | ||
// Prepend server URL to the possibly relative path | ||
const url = new URL(`${baseURL}${endpointPath}`, 'https://example.com'); | ||
const fullPath = url.pathname; | ||
const match = fullPath.match(/\/(v[0-9])\//); | ||
if (version !== undefined) { | ||
foundVersions.add(version); | ||
if (match) { | ||
foundVersions.add(match[1]); | ||
} else { | ||
messages.push({ | ||
severity: 'error', | ||
message: `Major version missing in path ${path}` | ||
message: `Major version missing in path: ${fullPath}`, | ||
// When returning multiple messages, each must have a unique and correct path | ||
// https://meta.stoplight.io/docs/spectral/ZG9jOjI1MTkw-custom-functions#returning-multiple-results | ||
path: [...path, endpointPath] | ||
}); | ||
@@ -46,4 +64,4 @@ } | ||
messages.push({ | ||
severity: 'error', | ||
message: `Found multiple major versions: ${[...foundVersions].join(', ')}` | ||
message: `Found multiple major versions: ${[...foundVersions].join(', ')}`, | ||
path | ||
}); | ||
@@ -55,40 +73,27 @@ } | ||
/** | ||
* Checks: The sorting parameter should be `order_by` | ||
* | ||
* Sorting API Guidelines: https://engineering.zapier.com/guides/api-design-guidelines/searching-filtering-sorting/#sorting | ||
*/ | ||
const checkSortingParameterName = parameters => { | ||
if (parameters === null) { | ||
return; | ||
} | ||
const messages = []; | ||
for (const parameter of parameters) { | ||
if (['sort_by', 'ordering'].includes(parameter.name)) { | ||
messages.push({ | ||
severity: 'error', | ||
message: `Found sorting parameter name which doesn't conform to the API Guidelines: ${parameter.name}, should use: order_by.` | ||
}); | ||
} | ||
} | ||
return messages; | ||
}; | ||
const UNWANTED_SORTING_PARAMETERS = ['sort_by', 'ordering']; | ||
const ruleset = { | ||
rules: { | ||
'major-version': { | ||
given: ['$.paths'], | ||
'single-major-version-in-paths': { | ||
description: 'All paths need to contain the same major version as /v[0-9]+/', | ||
documentationUrl: 'https://engineering.zapier.com/guides/api-design-guidelines/versioning/', | ||
given: '$.paths', | ||
message: '{{error}}', | ||
severity: 'error', | ||
then: { | ||
function: checkMajorVersion | ||
function: singleMajorVersionInPaths | ||
} | ||
}, | ||
'sorting-parameter': { | ||
given: '$.paths[*][*].parameters', | ||
description: `For sorting order_by should be used and not: ${UNWANTED_SORTING_PARAMETERS.join(', ')}`, | ||
documentationUrl: 'https://engineering.zapier.com/guides/api-design-guidelines/searching-filtering-sorting/#sorting', | ||
given: '$..parameters[*]', | ||
message: '{{error}}', | ||
severity: 'error', | ||
then: { | ||
function: checkSortingParameterName | ||
field: 'name', | ||
function: doNotUseValue, | ||
functionOptions: { | ||
values: UNWANTED_SORTING_PARAMETERS | ||
} | ||
} | ||
@@ -95,0 +100,0 @@ } |
import _ from 'lodash'; | ||
/** | ||
* Checks: Each path has a version that follows the pattern 'v<MAJOR_VERSION_NUMBER' and the number is | ||
* the same for all paths. | ||
* | ||
* Versioning API Guidelines: https://engineering.zapier.com/guides/api-design-guidelines/versioning/ | ||
*/ | ||
const checkMajorVersion = (paths, _options, { | ||
document | ||
const doNotUseValue = (targetVal, { | ||
values | ||
}) => { | ||
if (paths === null) { | ||
return undefined; | ||
if (values.includes(targetVal)) { | ||
return [{ | ||
message: `Do not use: ${targetVal}` | ||
}]; | ||
} | ||
}; | ||
const base_url = _.get(document, 'data.servers[0].url', ''); | ||
const singleMajorVersionInPaths = (paths, _options, { | ||
document, | ||
path | ||
}) => { | ||
// Find URL of first non-local server | ||
let baseURL = ''; | ||
const servers = _.get(document, 'data.servers', ''); | ||
if (Array.isArray(servers)) { | ||
for (const server of servers) { | ||
const url = _.get(server, 'url'); | ||
if (typeof url === 'string' && url.startsWith('https://')) { | ||
baseURL = url; | ||
break; | ||
} | ||
} | ||
} | ||
const messages = []; | ||
const foundVersions = new Set(); | ||
Object.keys(paths).forEach(path => { | ||
const url = new URL(`${base_url}${path}`, 'https://example.com'); | ||
const pathname_segments = url.pathname.split('/'); | ||
const version = pathname_segments.find(segment => segment.match(/v[0-9]+/)); | ||
Object.keys(paths).forEach(endpointPath => { | ||
// Prepend server URL to the possibly relative path | ||
const url = new URL(`${baseURL}${endpointPath}`, 'https://example.com'); | ||
const fullPath = url.pathname; | ||
const match = fullPath.match(/\/(v[0-9])\//); | ||
if (version !== undefined) { | ||
foundVersions.add(version); | ||
if (match) { | ||
foundVersions.add(match[1]); | ||
} else { | ||
messages.push({ | ||
severity: 'error', | ||
message: `Major version missing in path ${path}` | ||
message: `Major version missing in path: ${fullPath}`, | ||
// When returning multiple messages, each must have a unique and correct path | ||
// https://meta.stoplight.io/docs/spectral/ZG9jOjI1MTkw-custom-functions#returning-multiple-results | ||
path: [...path, endpointPath] | ||
}); | ||
@@ -37,4 +55,4 @@ } | ||
messages.push({ | ||
severity: 'error', | ||
message: `Found multiple major versions: ${[...foundVersions].join(', ')}` | ||
message: `Found multiple major versions: ${[...foundVersions].join(', ')}`, | ||
path | ||
}); | ||
@@ -46,40 +64,27 @@ } | ||
/** | ||
* Checks: The sorting parameter should be `order_by` | ||
* | ||
* Sorting API Guidelines: https://engineering.zapier.com/guides/api-design-guidelines/searching-filtering-sorting/#sorting | ||
*/ | ||
const checkSortingParameterName = parameters => { | ||
if (parameters === null) { | ||
return; | ||
} | ||
const messages = []; | ||
for (const parameter of parameters) { | ||
if (['sort_by', 'ordering'].includes(parameter.name)) { | ||
messages.push({ | ||
severity: 'error', | ||
message: `Found sorting parameter name which doesn't conform to the API Guidelines: ${parameter.name}, should use: order_by.` | ||
}); | ||
} | ||
} | ||
return messages; | ||
}; | ||
const UNWANTED_SORTING_PARAMETERS = ['sort_by', 'ordering']; | ||
const ruleset = { | ||
rules: { | ||
'major-version': { | ||
given: ['$.paths'], | ||
'single-major-version-in-paths': { | ||
description: 'All paths need to contain the same major version as /v[0-9]+/', | ||
documentationUrl: 'https://engineering.zapier.com/guides/api-design-guidelines/versioning/', | ||
given: '$.paths', | ||
message: '{{error}}', | ||
severity: 'error', | ||
then: { | ||
function: checkMajorVersion | ||
function: singleMajorVersionInPaths | ||
} | ||
}, | ||
'sorting-parameter': { | ||
given: '$.paths[*][*].parameters', | ||
description: `For sorting order_by should be used and not: ${UNWANTED_SORTING_PARAMETERS.join(', ')}`, | ||
documentationUrl: 'https://engineering.zapier.com/guides/api-design-guidelines/searching-filtering-sorting/#sorting', | ||
given: '$..parameters[*]', | ||
message: '{{error}}', | ||
severity: 'error', | ||
then: { | ||
function: checkSortingParameterName | ||
field: 'name', | ||
function: doNotUseValue, | ||
functionOptions: { | ||
values: UNWANTED_SORTING_PARAMETERS | ||
} | ||
} | ||
@@ -86,0 +91,0 @@ } |
{ | ||
"name": "@zapier/spectral-api-ruleset", | ||
"version": "0.0.3", | ||
"version": "0.0.4", | ||
"description": "Node package with Spectral ruleset for Zapier API Guidelines.", | ||
@@ -23,3 +23,5 @@ "repository": "https://gitlab.com/zapier/spectral-api-ruleset/-/tree/main", | ||
"lint": "eslint \"{src/**/*,*}.{js,ts,tsx}\"", | ||
"test:integration": "yarn spectral lint test-schema.yaml", | ||
"pretest:spectral": "yarn build", | ||
"test:spectral": "yarn spectral lint tests/schema.yaml --ruleset tests/spectral.yaml --fail-severity hint --verbose", | ||
"pretest": "yarn build", | ||
"test": "jest", | ||
@@ -31,17 +33,16 @@ "prettier-check": "prettier --check \"{,**/}*.{js,ts,tsx}\"", | ||
"release": "yarn changeset publish", | ||
"validate": "yarn typecheck && yarn lint && yarn test && yarn test:integrate" | ||
"validate": "yarn typecheck && yarn lint && yarn test && yarn test:spectral" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.15.8", | ||
"@babel/preset-env": "^7.15.8", | ||
"@babel/preset-typescript": "^7.15.0", | ||
"@babel/core": "^7.16.0", | ||
"@babel/preset-env": "^7.16.0", | ||
"@babel/preset-typescript": "^7.16.0", | ||
"@changesets/cli": "^2.17.0", | ||
"@preconstruct/cli": "^2.1.5", | ||
"@stoplight/spectral-cli": "^6.1.0", | ||
"@stoplight/spectral-core": "^1.6.0", | ||
"@types/jest": "^27.0.2", | ||
"@types/lodash": "^4.14.175", | ||
"@typescript-eslint/parser": "^5.1.0", | ||
"eslint": "^8.0.1", | ||
"eslint-plugin-jest": "^25.2.2", | ||
"@types/lodash": "^4.14.176", | ||
"@typescript-eslint/parser": "^5.3.0", | ||
"eslint": "^8.1.0", | ||
"eslint-plugin-jest": "^25.2.3", | ||
"husky": "^4.3.8", | ||
@@ -63,3 +64,4 @@ "jest": "^27.3.1", | ||
"lodash": "^4.17.21" | ||
} | ||
} | ||
}, | ||
"peerDependencies": {} | ||
} |
@@ -82,8 +82,12 @@ <h1 align="center"> | ||
You can add rules to `spectral.js`. See the [Alternative JS Ruleset Format docs](https://meta.stoplight.io/docs/spectral/ZG9jOjI1MTg5-custom-rulesets#alternative-js-ruleset-format) for details. | ||
You can add rules to `src/index.ts`. See the [Alternative JS Ruleset Format docs](https://meta.stoplight.io/docs/spectral/ZG9jOjI1MTg5-custom-rulesets#alternative-js-ruleset-format) for details. | ||
> We're using the JS format so that the package ruleset can also be [used in JavaScript](https://meta.stoplight.io/docs/spectral/ZG9jOjI1MTg3-spectral-in-java-script). | ||
- Try to use the [Core Functions](https://meta.stoplight.io/docs/spectral/ZG9jOjExNg-core-functions) to avoid custom functions. | ||
- Include a unit test for any custom functions you add to `functions/`. | ||
- Update `test-schema.yaml` to meet all rules (run `yarn test:integrate` to verify). | ||
- Provide the correct [`severity`](https://meta.stoplight.io/docs/spectral/ZG9jOjI1MTg5-custom-rulesets#severity) (`error` for **musts** and `warn` for **shoulds**). | ||
- Provide a clear `description`, as well as a [`documentationUrl`](https://meta.stoplight.io/docs/spectral/ZG9jOjI1MTg5-custom-rulesets#documentation-url) that points to the relevant guide. | ||
- Provide a [`message`](https://meta.stoplight.io/docs/spectral/ZG9jOjI1MTg5-custom-rulesets#message), which in most cases should just be `{{error}}`. | ||
- Prefer [Core Functions](https://meta.stoplight.io/docs/spectral/ZG9jOjExNg-core-functions) over [Custom Functions](https://meta.stoplight.io/docs/spectral/ZG9jOjI1MTkw-custom-functions). | ||
- Include a unit test for any custom functions you add to [`src/functions`](src/functions). | ||
- Update `tests/schema.yaml` to meet all rules (run `yarn test:spectral` to verify). | ||
- Add failing schemas to `tests/__fixtures__` and run `yarn test integrations --updateSnapshot` to update `test/__snapshots__/integration.test.ts.snap` and verify the found issues. |
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
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
15211
16
282
93
1