feat: display diff when rule modifies data (fixes #3)
This commit is contained in:
parent
e3588577ac
commit
6f15d1c621
|
@ -35,6 +35,9 @@ export default {
|
|||
}),
|
||||
commonjs({
|
||||
namedExports: {
|
||||
'node_modules/deep-diff/index.js': [
|
||||
'diff',
|
||||
],
|
||||
'node_modules/lodash/lodash.js': [
|
||||
'cloneDeep',
|
||||
'intersection',
|
||||
|
|
|
@ -7,7 +7,6 @@ spec:
|
|||
- name: test
|
||||
resources:
|
||||
limits:
|
||||
cpu: 2m
|
||||
memory: 5Mi
|
||||
requests:
|
||||
cpu: 1m
|
||||
|
|
|
@ -21,9 +21,11 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/bunyan": "^1.8.6",
|
||||
"@types/deep-diff": "^1.0.0",
|
||||
"@types/js-yaml": "^3.12.1",
|
||||
"@types/lodash": "^4.14.134",
|
||||
"@types/yargs": "^13.0.0",
|
||||
"deep-diff": "^1.0.2",
|
||||
"jsonpath-plus": "^0.20.1",
|
||||
"rollup": "^1.15.5",
|
||||
"rollup-plugin-commonjs": "^10.0.0",
|
||||
|
|
|
@ -22,11 +22,13 @@ rules:
|
|||
required: [cpu, memory]
|
||||
properties:
|
||||
cpu: &resources-cpu
|
||||
oneOf:
|
||||
- type: number
|
||||
minimum: 1
|
||||
- type: string
|
||||
pattern: "[1-9][0-9]*m"
|
||||
default: 500m
|
||||
type: string
|
||||
# oneOf:
|
||||
# - type: number
|
||||
# minimum: 1
|
||||
# - type: string
|
||||
# pattern: "[1-9][0-9]*m"
|
||||
memory: &resources-memory
|
||||
oneOf:
|
||||
- type: number
|
||||
|
|
44
src/index.ts
44
src/index.ts
|
@ -1,4 +1,6 @@
|
|||
import { createLogger } from 'bunyan';
|
||||
import { diff } from 'deep-diff';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { Options, usage } from 'yargs';
|
||||
|
||||
import { loadConfig } from 'src/config';
|
||||
|
@ -11,6 +13,8 @@ import { VisitorContext } from 'src/visitor/context';
|
|||
const CONFIG_ARGS_NAME = 'config-name';
|
||||
const CONFIG_ARGS_PATH = 'config-path';
|
||||
|
||||
const MODES = ['check', 'fix'];
|
||||
|
||||
const RULE_OPTION: Options = {
|
||||
default: [],
|
||||
group: 'Rules:',
|
||||
|
@ -87,6 +91,11 @@ export async function main(argv: Array<string>): Promise<number> {
|
|||
logger.info(VERSION_INFO, 'version info');
|
||||
logger.info({ args }, 'main arguments');
|
||||
|
||||
// check mode
|
||||
if (!MODES.includes(args.mode)) {
|
||||
logger.error({ mode: args.mode }, 'unsupported mode');
|
||||
}
|
||||
|
||||
// const schema = new Schema();
|
||||
const result = { errors: [], valid: true }; // schema.match(config);
|
||||
if (!result.valid) {
|
||||
|
@ -97,9 +106,6 @@ export async function main(argv: Array<string>): Promise<number> {
|
|||
const rules = await loadRules(args.rules);
|
||||
const source = await loadSource(args.source);
|
||||
|
||||
const parser = new YamlParser();
|
||||
const data = parser.parse(source);
|
||||
|
||||
const activeRules = await resolveRules(rules, args as any);
|
||||
const ctx = new VisitorContext({
|
||||
coerce: args.coerce,
|
||||
|
@ -107,23 +113,29 @@ export async function main(argv: Array<string>): Promise<number> {
|
|||
logger,
|
||||
});
|
||||
|
||||
switch (args.mode) {
|
||||
case 'check':
|
||||
case 'fix':
|
||||
for (const rule of activeRules) {
|
||||
if (rule.visit(ctx, data)) {
|
||||
logger.info({ rule }, 'passed rule');
|
||||
} else {
|
||||
logger.warn({ rule }, 'failed rule');
|
||||
}
|
||||
const parser = new YamlParser();
|
||||
let data = parser.parse(source);
|
||||
|
||||
for (const rule of activeRules) {
|
||||
const workingCopy = cloneDeep(data);
|
||||
const ruleErrors = await rule.visit(ctx, workingCopy);
|
||||
|
||||
if (ruleErrors > 0) {
|
||||
logger.warn({ rule }, 'rule failed');
|
||||
} else {
|
||||
const ruleDiff = diff(data, workingCopy);
|
||||
if (Array.isArray(ruleDiff) && ruleDiff.length > 0) {
|
||||
logger.info({ diff: ruleDiff, rule }, 'rule passed with modifications');
|
||||
|
||||
data = workingCopy;
|
||||
} else {
|
||||
logger.info({ rule }, 'rule passed');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ctx.error({ mode: args.mode }, 'unsupported mode');
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.errors.length > 0) {
|
||||
logger.error({ errors: ctx.errors }, 'some rules failed');
|
||||
logger.error({ count: ctx.errors.length, errors: ctx.errors }, 'some rules failed');
|
||||
if (args.count) {
|
||||
return Math.min(ctx.errors.length, 255);
|
||||
} else {
|
||||
|
|
13
src/rule.ts
13
src/rule.ts
|
@ -101,7 +101,7 @@ export class Rule implements RuleData, Visitor {
|
|||
}
|
||||
}
|
||||
|
||||
public async visit(ctx: VisitorContext, node: any): Promise<VisitorContext> {
|
||||
public async visit(ctx: VisitorContext, node: any): Promise<number> {
|
||||
const check = ctx.ajv.compile(this.check);
|
||||
const filter = this.compileFilter(ctx);
|
||||
const scopes = JSONPath({
|
||||
|
@ -111,7 +111,7 @@ export class Rule implements RuleData, Visitor {
|
|||
|
||||
if (isNil(scopes) || scopes.length === 0) {
|
||||
ctx.logger.debug('no data selected');
|
||||
return ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const item of scopes) {
|
||||
|
@ -119,19 +119,22 @@ export class Rule implements RuleData, Visitor {
|
|||
if (filter(item)) {
|
||||
ctx.logger.debug({ item }, 'checking item')
|
||||
if (!check(item)) {
|
||||
const errors = Array.from(check.errors);
|
||||
ctx.logger.warn({
|
||||
errors,
|
||||
name: this.name,
|
||||
item,
|
||||
rule: this,
|
||||
}, 'rule failed on item');
|
||||
ctx.errors.push(...check.errors);
|
||||
return ctx;
|
||||
ctx.errors.push(...errors);
|
||||
return errors.length;
|
||||
}
|
||||
} else {
|
||||
ctx.logger.debug({ errors: filter.errors, item }, 'skipping item');
|
||||
}
|
||||
}
|
||||
|
||||
return ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected compileFilter(ctx: VisitorContext): any {
|
||||
|
|
|
@ -10,6 +10,8 @@ export interface VisitorContextOptions {
|
|||
export class VisitorContext {
|
||||
public readonly ajv: any;
|
||||
public readonly changes: Array<any>;
|
||||
public readonly coerce: boolean;
|
||||
public readonly defaults: boolean;
|
||||
public readonly errors: Array<any>;
|
||||
public readonly logger: Logger;
|
||||
|
||||
|
@ -19,6 +21,8 @@ export class VisitorContext {
|
|||
useDefaults: options.defaults,
|
||||
});
|
||||
this.changes = [];
|
||||
this.coerce = options.coerce;
|
||||
this.defaults = options.defaults;
|
||||
this.errors = [];
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { VisitorContext } from 'src/visitor/context';
|
||||
|
||||
export interface Visitor {
|
||||
visit(ctx: VisitorContext, node: any): Promise<VisitorContext>;
|
||||
visit(ctx: VisitorContext, node: any): Promise<number>;
|
||||
}
|
10
yarn.lock
10
yarn.lock
|
@ -9,6 +9,11 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/deep-diff@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/deep-diff/-/deep-diff-1.0.0.tgz#7eba3202a99b3a207f758f351f7f86387269fc40"
|
||||
integrity sha512-ENsJcujGbCU/oXhDfQ12mSo/mCBWodT2tpARZKmatoSrf8+cGRCPi0KVj3I0FORhYZfLXkewXu7AoIWqiBLkNw==
|
||||
|
||||
"@types/estree@0.0.39":
|
||||
version "0.0.39"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||
|
@ -577,6 +582,11 @@ decode-uri-component@^0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
|
||||
|
||||
deep-diff@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-1.0.2.tgz#afd3d1f749115be965e89c63edc7abb1506b9c26"
|
||||
integrity sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==
|
||||
|
||||
define-property@^0.2.5:
|
||||
version "0.2.5"
|
||||
resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
|
||||
|
|
Loading…
Reference in New Issue