-
-
Notifications
You must be signed in to change notification settings - Fork 240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: control fail severity and result display #540
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,16 +9,29 @@ spectral lint petstore.yaml | |
Other options include: | ||
|
||
``` text | ||
-e, --encoding=encoding text encoding to use | ||
-f, --format=json|stylish formatter to use for outputting results | ||
-h, --help show CLI help | ||
-o, --output=output output to a file instead of stdout | ||
-q, --quiet no logging - output only | ||
-r, --ruleset=ruleset path to a ruleset file (supports remote files) | ||
-s, --skip-rule=skip-rule ignore certain rules if they are causing trouble | ||
-v, --verbose increase verbosity | ||
--version Show version number [boolean] | ||
--help Show help [boolean] | ||
--encoding, -e text encoding to use [string] [default: "utf8"] | ||
--format, -f formatter to use for outputting results [string] [default: "stylish"] | ||
--output, -o output to a file instead of stdout [string] | ||
--ruleset, -r path/URL to a ruleset file [string] | ||
--skip-rule, -s ignore certain rules if they are causing trouble [string] | ||
--fail-severity, -F results of this level or above will trigger a failure exit code | ||
[string] [choices: "error", "warn", "info", "hint"] [default: "hint"] | ||
--display-only-failures, -D only output results equal to or greater than --fail-severity | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. greater and higher are synonomous and i dont think anyone knows what the internal ints are? This is probably find, if anyone gets confused we can ask them for suggestions. |
||
[boolean] [default: false] | ||
--verbose, -v increase verbosity [boolean] | ||
--quiet, -q no logging - output only [boolean] | ||
``` | ||
|
||
> Note: The Spectral CLI supports both YAML and JSON. | ||
|
||
Currently, Spectral CLI supports validation of OpenAPI v2/v3 documents via our built-in ruleset, or you can create [custom rulesets](../getting-started/rulesets.md) to work with any JSON/YAML documents. | ||
|
||
## Error Results | ||
|
||
Spectral has a few different error severities: `error`, `warn`, `info` and `hint`, and they are in "order" from highest to lowest. By default, all results will be shown regardless of severity, and the presence of any results will cause a failure status code of 1. | ||
|
||
The default behavior is can be modified with the `--fail-severity=` option. Setting fail severity to `--fail-severity=warn` would return a status code of 1 for any warning results or higher, so that would also include error. Using `--fail-severity=error` will only show errors. | ||
|
||
Changing the fail severity will not effect output. To change what results Spectral CLI prints to the screen, add the `--display-only-failures` switch (or just `-D` for short). This will strip out any results which are below the fail severity. |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,7 +2,9 @@ import { Dictionary } from '@stoplight/types'; | |||||
import { CommandModule, showHelp } from 'yargs'; | ||||||
|
||||||
import { pick } from 'lodash'; | ||||||
import { ILintConfig, OutputFormat } from '../../types/config'; | ||||||
import { getDiagnosticSeverity } from '../../rulesets/severity'; | ||||||
import { IRuleResult } from '../../types'; | ||||||
import { FailSeverity, ILintConfig, OutputFormat } from '../../types/config'; | ||||||
import { lint } from '../services/linter'; | ||||||
import { formatOutput, writeOutput } from '../services/output'; | ||||||
|
||||||
|
@@ -58,6 +60,19 @@ const lintCommand: CommandModule = { | |||||
type: 'string', | ||||||
coerce: toArray, | ||||||
}, | ||||||
'fail-severity': { | ||||||
alias: 'F', | ||||||
description: 'results of this level or above will trigger a failure exit code', | ||||||
choices: ['error', 'warn', 'info', 'hint'], | ||||||
default: 'hint', // TODO: BREAKING: raise this to warn in 5.0 | ||||||
type: 'string', | ||||||
}, | ||||||
'display-only-failures': { | ||||||
alias: 'D', | ||||||
description: 'only output results equal to or greater than --fail-severity', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As in docs. |
||||||
type: 'boolean', | ||||||
default: false, | ||||||
}, | ||||||
verbose: { | ||||||
alias: 'v', | ||||||
description: 'increase verbosity', | ||||||
|
@@ -71,20 +86,37 @@ const lintCommand: CommandModule = { | |||||
}), | ||||||
|
||||||
handler: args => { | ||||||
const { document, ruleset, format, output, encoding, ...config } = (args as unknown) as ILintConfig & { | ||||||
const { | ||||||
document, | ||||||
failSeverity, | ||||||
displayOnlyFailures, | ||||||
ruleset, | ||||||
format, | ||||||
output, | ||||||
encoding, | ||||||
...config | ||||||
} = (args as unknown) as ILintConfig & { | ||||||
document: string; | ||||||
failSeverity: FailSeverity; | ||||||
displayOnlyFailures: boolean; | ||||||
}; | ||||||
|
||||||
return lint( | ||||||
document, | ||||||
{ format, output, encoding, ...pick(config, ['ruleset', 'skipRule', 'verbose', 'quiet']) }, | ||||||
ruleset, | ||||||
) | ||||||
.then(results => { | ||||||
if (displayOnlyFailures) { | ||||||
return filterResultsBySeverity(results, failSeverity); | ||||||
} | ||||||
return results; | ||||||
}) | ||||||
.then(results => { | ||||||
if (results.length) { | ||||||
process.exitCode = 1; | ||||||
process.exitCode = severeEnoughToFail(results, failSeverity) ? 1 : 0; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} else if (!config.quiet) { | ||||||
console.log('No errors or warnings found!'); | ||||||
console.log(`No results with a severity of '${failSeverity}' or higher found!`); | ||||||
} | ||||||
const formattedOutput = formatOutput(results, format); | ||||||
return writeOutput(formattedOutput, output); | ||||||
|
@@ -93,9 +125,19 @@ const lintCommand: CommandModule = { | |||||
}, | ||||||
}; | ||||||
|
||||||
function fail(err: Error) { | ||||||
const fail = (err: Error) => { | ||||||
console.error(err); | ||||||
process.exitCode = 2; | ||||||
} | ||||||
}; | ||||||
|
||||||
const filterResultsBySeverity = (results: IRuleResult[], failSeverity: FailSeverity): IRuleResult[] => { | ||||||
const diagnosticSeverity = getDiagnosticSeverity(failSeverity); | ||||||
return results.filter(r => r.severity <= diagnosticSeverity); | ||||||
}; | ||||||
|
||||||
const severeEnoughToFail = (results: IRuleResult[], failSeverity: FailSeverity): boolean => { | ||||||
const diagnosticSeverity = getDiagnosticSeverity(failSeverity); | ||||||
return results.some(r => r.severity <= diagnosticSeverity); | ||||||
}; | ||||||
|
||||||
export default lintCommand; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,13 +21,12 @@ function replaceVars(string: string, replacements: Replacement[]) { | |
return replacements.reduce((str, replace) => str.replace(replace.from, replace.to), string); | ||
} | ||
|
||
describe('cli e2e tests', () => { | ||
const files = process.env.TESTS | ||
? String(process.env.TESTS).split(',') | ||
: glob.readdirSync('**/*.scenario', { cwd: path.join(__dirname, './scenarios') }); | ||
describe('cli acceptance tests', () => { | ||
const cwd = path.join(__dirname, './scenarios'); | ||
const files = process.env.TESTS ? String(process.env.TESTS).split(',') : glob.readdirSync('**/*.scenario', { cwd }); | ||
|
||
files.forEach((file: string) => { | ||
const data = fs.readFileSync(path.join(__dirname, './scenarios/', file), { encoding: 'utf8' }); | ||
const data = fs.readFileSync(path.join(cwd, file), { encoding: 'utf8' }); | ||
const scenario = parseScenarioFile(data); | ||
const replacements: Replacement[] = []; | ||
|
||
|
@@ -60,7 +59,7 @@ describe('cli e2e tests', () => { | |
} | ||
}); | ||
|
||
test(`${file}${os.EOL}${scenario.test}`, () => { | ||
test(`./test-harness/scenarios/${file}${os.EOL}${scenario.test}`, () => { | ||
// TODO split on " " is going to break quoted args | ||
const args = scenario.command.split(' ').map(t => { | ||
const arg = t.trim(); | ||
|
@@ -76,10 +75,12 @@ describe('cli e2e tests', () => { | |
windowsVerbatimArguments: false, | ||
}); | ||
|
||
const expectedStatus = replaceVars(scenario.status.trim(), replacements); | ||
const expectedStdout = replaceVars(scenario.stdout.trim(), replacements); | ||
const expectedStderr = replaceVars(scenario.stderr.trim(), replacements); | ||
const status = commandHandle.status; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's maybe differentiate between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The actualStatus is the status. I think t's ok. |
||
const stderr = commandHandle.stderr.trim(); | ||
const stdout = commandHandle.stdout.trim(); | ||
const expectedStderr = replaceVars(scenario.stderr.trim(), replacements); | ||
const expectedStdout = replaceVars(scenario.stdout.trim(), replacements); | ||
|
||
if (expectedStderr) { | ||
expect(stderr).toEqual(expectedStderr); | ||
|
@@ -90,6 +91,10 @@ describe('cli e2e tests', () => { | |
if (stdout) { | ||
expect(stdout).toEqual(expectedStdout); | ||
} | ||
|
||
if (expectedStatus !== '') { | ||
expect(`status:${status}`).toEqual(`status:${expectedStatus}`); | ||
} | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
====test==== | ||
Request only errors be shown, but no errors exist | ||
====document==== | ||
openapi: '3.0.0' | ||
info: | ||
version: 1.0.0 | ||
title: Swagger Petstore | ||
license: | ||
name: MIT | ||
paths: | ||
/pets/{petId}: | ||
get: | ||
summary: Info for a specific pet | ||
operationId: showPetById | ||
tags: | ||
- pets | ||
parameters: | ||
- name: petId | ||
in: path | ||
required: true | ||
description: The id of the pet to retrieve | ||
schema: | ||
type: string | ||
responses: | ||
'200': | ||
description: Expected response to a valid request | ||
content: | ||
application/json: | ||
schema: | ||
required: | ||
- id | ||
- name | ||
properties: | ||
id: | ||
type: integer | ||
format: int64 | ||
name: | ||
type: string | ||
tag: | ||
type: string | ||
====command==== | ||
lint {document} --fail-severity=error -D | ||
====status==== | ||
0 | ||
====stdout==== | ||
OpenAPI 3.x detected | ||
No results with a severity of 'error' or higher found! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the lack of available formats expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not no. damn where'd they go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR did not remove them, the test harness shows that develop branch is missing them already. Let's carry on with this merge and fix them in another PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alrighty, sounds good.