1
0
Fork 0

fix(visitor): include rule name and selector in error messages

This commit is contained in:
ssube 2019-11-02 14:58:49 -05:00 committed by Sean Sube
parent 5fefe0c79d
commit fcd4740eee
5 changed files with 56 additions and 12 deletions

View File

@ -197,8 +197,30 @@ the easiest to read, and can be pretty-printed by redirecting `stderr` through `
} }
``` ```
Using `jq` allows for additional filtering, for example `>(jq 'select(.level > 30)')` will only print warnings and Using `jq` allows for additional filtering and formatting. For example, `>(jq 'select(.level > 30)')` will only print
errors (log level is also part of the configuration file). warnings and errors (log level is also part of the configuration file).
To print the last line's message and error messages: `>(tail -1 | jq '[.msg, ((.errors // [])[] | .msg)]')`
```shell
> cat test/examples/kubernetes-resources-high.yml | salty-dog \
--rules rules/kubernetes.yml \
--tag kubernetes 2> >(tail -1 | jq '[.msg, ((.errors // [])[] | .msg)]')
[
"all rules passed"
]
> cat test/examples/kubernetes-resources-some.yml | salty-dog \
--rules rules/kubernetes.yml \
--tag kubernetes 2> >(tail -1 | jq '[.msg, ((.errors // [])[] | .msg)]')
[
"some rules failed",
".resources.limits should have required property 'memory' at $.spec.template.spec.containers[*] for kubernetes-resources",
".metadata should have required property 'labels' at $ for kubernetes-labels"
]
```
### Modes ### Modes

View File

@ -156,7 +156,7 @@ export async function parseArgs(argv: Array<string>): Promise<ParseResults> {
.version(VERSION_INFO.package.version) .version(VERSION_INFO.package.version)
.alias('version', 'v'); .alias('version', 'v');
// @TODO: this should not need a cast but the parser's type only has the last option (include-tag) // @TODO: this should not need a cast but the parser's type omits command options and doesn't expose camelCase
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
const args = parser.argv as any; const args = parser.argv as any;
return { return {

View File

@ -57,7 +57,7 @@ export class SchemaRule implements RuleData, Visitor {
if (filter(node)) { if (filter(node)) {
ctx.logger.debug({ item: node }, 'checking item'); ctx.logger.debug({ item: node }, 'checking item');
if (!check(node) && hasItems(check.errors)) { if (!check(node) && hasItems(check.errors)) {
errors.push(...check.errors.map(friendlyError)); errors.push(...check.errors.map((err) => friendlyError(ctx, err, this)));
} }
} else { } else {
ctx.logger.debug({ errors: filter.errors, item: node }, 'skipping item'); ctx.logger.debug({ errors: filter.errors, item: node }, 'skipping item');

View File

@ -1,22 +1,25 @@
import { ErrorObject } from 'ajv'; import { ErrorObject } from 'ajv';
import { isNil } from 'lodash'; import { isNil } from 'lodash';
import { SchemaRule } from '../../rule/SchemaRule';
import { VisitorContext } from '../../visitor/VisitorContext';
import { VisitorError } from '../../visitor/VisitorError'; import { VisitorError } from '../../visitor/VisitorError';
export function friendlyError(err: ErrorObject): VisitorError { export function friendlyError(ctx: VisitorContext, err: ErrorObject, rule: SchemaRule): VisitorError {
return { return {
data: { data: {
err, err,
rule,
}, },
level: 'error', level: 'error',
msg: friendlyErrorMessage(err), msg: friendlyErrorMessage(err, rule),
}; };
} }
export function friendlyErrorMessage(err: ErrorObject): string { export function friendlyErrorMessage(err: ErrorObject, rule: SchemaRule): string {
if (isNil(err.message)) { if (isNil(err.message)) {
return `${err.dataPath} ${err.keyword}`; return `${err.dataPath} ${err.keyword} at ${rule.select} for ${rule.name}`;
} else { } else {
return `${err.dataPath} ${err.message}`; return `${err.dataPath} ${err.message} at ${rule.select} for ${rule.name}`;
} }
} }

View File

@ -1,15 +1,34 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { NullLogger } from 'noicejs';
import { SchemaRule } from '../../../src/rule/SchemaRule';
import { friendlyError } from '../../../src/utils/ajv'; import { friendlyError } from '../../../src/utils/ajv';
import { VisitorContext } from '../../../src/visitor/VisitorContext';
const TEST_NAME = 'test';
describe('friendly errors', () => { describe('friendly errors', () => {
it('should have a message', () => { it('should have a message', () => {
const err = friendlyError({ const err = friendlyError(new VisitorContext({
innerOptions: {
coerce: false,
defaults: false,
mutate: false,
},
logger: NullLogger.global,
}), {
dataPath: 'test-path', dataPath: 'test-path',
keyword: 'test', keyword: TEST_NAME,
params: { /* ? */ }, params: { /* ? */ },
schemaPath: 'test-path', schemaPath: 'test-path',
}); }, new SchemaRule({
check: {},
desc: TEST_NAME,
level: 'info',
name: TEST_NAME,
select: '',
tags: [TEST_NAME],
}));
expect(err.msg).to.not.equal(''); expect(err.msg).to.not.equal('');
}); });
}); });