formalize visitor, results, etc
This commit is contained in:
parent
aec3ea9e4e
commit
eb1fdd3f30
|
@ -36,6 +36,7 @@ export default {
|
||||||
commonjs({
|
commonjs({
|
||||||
namedExports: {
|
namedExports: {
|
||||||
'node_modules/deep-diff/index.js': [
|
'node_modules/deep-diff/index.js': [
|
||||||
|
'applyDiff',
|
||||||
'diff',
|
'diff',
|
||||||
],
|
],
|
||||||
'node_modules/lodash/lodash.js': [
|
'node_modules/lodash/lodash.js': [
|
||||||
|
@ -63,4 +64,4 @@ export default {
|
||||||
rollupCommonJSResolveHack: true,
|
rollupCommonJSResolveHack: true,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -56,7 +56,7 @@ rules:
|
||||||
limits:
|
limits:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
cpu: &resources-cpu
|
cpu: *resources-cpu
|
||||||
|
|
||||||
# ensure the limits aren't *too* low
|
# ensure the limits aren't *too* low
|
||||||
check:
|
check:
|
||||||
|
@ -99,4 +99,4 @@ rules:
|
||||||
properties:
|
properties:
|
||||||
replica:
|
replica:
|
||||||
type: number
|
type: number
|
||||||
minimum: 1
|
minimum: 1
|
||||||
|
|
27
src/index.ts
27
src/index.ts
|
@ -1,5 +1,5 @@
|
||||||
import { createLogger } from 'bunyan';
|
import { createLogger } from 'bunyan';
|
||||||
import { diff } from 'deep-diff';
|
import { applyDiff, diff } from 'deep-diff';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { Options, usage } from 'yargs';
|
import { Options, usage } from 'yargs';
|
||||||
|
|
||||||
|
@ -117,19 +117,24 @@ export async function main(argv: Array<string>): Promise<number> {
|
||||||
const activeRules = await resolveRules(rules, args as any);
|
const activeRules = await resolveRules(rules, args as any);
|
||||||
|
|
||||||
for (const rule of activeRules) {
|
for (const rule of activeRules) {
|
||||||
const workingCopy = cloneDeep(data);
|
const items = await rule.pick(ctx, data);
|
||||||
const ruleErrors = await rule.visit(ctx, workingCopy);
|
for (const item of items) {
|
||||||
|
const itemCopy = cloneDeep(item);
|
||||||
|
const itemResult = await rule.visit(ctx, itemCopy);
|
||||||
|
|
||||||
if (ruleErrors > 0) {
|
if (itemResult.errors.length > 0) {
|
||||||
logger.warn({ rule }, 'rule failed');
|
logger.warn({ count: itemResult.errors.length, 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;
|
ctx.mergeResult(itemResult);
|
||||||
} else {
|
} else {
|
||||||
logger.info({ rule }, 'rule passed');
|
const itemDiff = diff(item, itemCopy);
|
||||||
|
if (Array.isArray(itemDiff) && itemDiff.length > 0) {
|
||||||
|
logger.info({ diff: itemDiff, item, rule }, 'rule passed with modifications');
|
||||||
|
|
||||||
|
applyDiff(item, itemDiff);
|
||||||
|
} else {
|
||||||
|
logger.info({ rule }, 'rule passed');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
63
src/rule.ts
63
src/rule.ts
|
@ -6,6 +6,7 @@ import { YamlParser } from 'src/parser/YamlParser';
|
||||||
import { readFileSync } from 'src/source';
|
import { readFileSync } from 'src/source';
|
||||||
import { Visitor } from 'src/visitor';
|
import { Visitor } from 'src/visitor';
|
||||||
import { VisitorContext } from 'src/visitor/context';
|
import { VisitorContext } from 'src/visitor/context';
|
||||||
|
import { VisitorResult } from 'src/visitor/result';
|
||||||
|
|
||||||
export interface RuleData {
|
export interface RuleData {
|
||||||
// metadata
|
// metadata
|
||||||
|
@ -92,7 +93,11 @@ export async function resolveRules(rules: Array<Rule>, selector: RuleSelector):
|
||||||
return Array.from(activeRules);
|
return Array.from(activeRules);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Rule implements RuleData, Visitor {
|
export interface RuleResult extends VisitorResult {
|
||||||
|
rule: Rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Rule implements RuleData, Visitor<RuleResult> {
|
||||||
public readonly check: any;
|
public readonly check: any;
|
||||||
public readonly desc: string;
|
public readonly desc: string;
|
||||||
public readonly filter?: any;
|
public readonly filter?: any;
|
||||||
|
@ -115,40 +120,48 @@ export class Rule implements RuleData, Visitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async visit(ctx: VisitorContext, node: any): Promise<number> {
|
public async pick(ctx: VisitorContext, root: any): Promise<Array<any>> {
|
||||||
const check = ctx.ajv.compile(this.check);
|
|
||||||
const filter = this.compileFilter(ctx);
|
|
||||||
const scopes = JSONPath({
|
const scopes = JSONPath({
|
||||||
json: node,
|
json: root,
|
||||||
path: this.select,
|
path: this.select,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isNil(scopes) || scopes.length === 0) {
|
if (isNil(scopes) || scopes.length === 0) {
|
||||||
ctx.logger.debug('no data selected');
|
ctx.logger.debug('no data selected');
|
||||||
return 0;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const item of scopes) {
|
return scopes;
|
||||||
ctx.logger.debug({ item }, 'filtering item');
|
}
|
||||||
if (filter(item)) {
|
|
||||||
ctx.logger.debug({ item }, 'checking item')
|
public async visit(ctx: VisitorContext, node: any): Promise<RuleResult> {
|
||||||
if (!check(item)) {
|
ctx.logger.debug({ item: node, rule: this}, 'visiting node');
|
||||||
const errors = Array.from(check.errors);
|
|
||||||
ctx.logger.warn({
|
const check = ctx.ajv.compile(this.check);
|
||||||
errors,
|
const filter = this.compileFilter(ctx);
|
||||||
name: this.name,
|
const result: RuleResult = {
|
||||||
item,
|
changes: [],
|
||||||
rule: this,
|
errors: [],
|
||||||
}, 'rule failed on item');
|
rule: this,
|
||||||
ctx.errors.push(...errors);
|
};
|
||||||
return errors.length;
|
|
||||||
}
|
if (filter(node)) {
|
||||||
} else {
|
ctx.logger.debug({ item: node }, 'checking item')
|
||||||
ctx.logger.debug({ errors: filter.errors, item }, 'skipping item');
|
if (!check(node)) {
|
||||||
|
const errors = Array.from(check.errors);
|
||||||
|
ctx.logger.warn({
|
||||||
|
errors,
|
||||||
|
name: this.name,
|
||||||
|
item: node,
|
||||||
|
rule: this,
|
||||||
|
}, 'rule failed on item');
|
||||||
|
result.errors.push(...errors);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ctx.logger.debug({ errors: filter.errors, item: node }, 'skipping item');
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected compileFilter(ctx: VisitorContext): any {
|
protected compileFilter(ctx: VisitorContext): any {
|
||||||
|
@ -158,4 +171,4 @@ export class Rule implements RuleData, Visitor {
|
||||||
return ctx.ajv.compile(this.filter);
|
return ctx.ajv.compile(this.filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import * as Ajv from 'ajv';
|
import * as Ajv from 'ajv';
|
||||||
import { Logger } from 'noicejs';
|
import { Logger } from 'noicejs';
|
||||||
|
|
||||||
|
import { VisitorResult } from 'src/visitor/result';
|
||||||
|
|
||||||
export interface VisitorContextOptions {
|
export interface VisitorContextOptions {
|
||||||
coerce: boolean;
|
coerce: boolean;
|
||||||
defaults: boolean;
|
defaults: boolean;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VisitorContext {
|
export class VisitorContext implements VisitorContextOptions, VisitorResult {
|
||||||
public readonly ajv: any;
|
public readonly ajv: any;
|
||||||
public readonly changes: Array<any>;
|
public readonly changes: Array<any>;
|
||||||
public readonly coerce: boolean;
|
public readonly coerce: boolean;
|
||||||
|
@ -31,4 +33,10 @@ export class VisitorContext {
|
||||||
this.logger.error(options, msg);
|
this.logger.error(options, msg);
|
||||||
this.errors.push(options || msg);
|
this.errors.push(options || msg);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public mergeResult(other: VisitorResult): this {
|
||||||
|
this.changes.push(...other.changes);
|
||||||
|
this.errors.push(...other.errors);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
import { VisitorContext } from 'src/visitor/context';
|
import { VisitorContext } from 'src/visitor/context';
|
||||||
|
import { VisitorResult } from 'src/visitor/result';
|
||||||
|
|
||||||
export interface Visitor {
|
export interface Visitor<TResult extends VisitorResult> {
|
||||||
visit(ctx: VisitorContext, node: any): Promise<number>;
|
/**
|
||||||
}
|
* Select nodes eligible to be visited.
|
||||||
|
**/
|
||||||
|
pick(ctx: VisitorContext, root: any): Promise<Array<any>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visit a node.
|
||||||
|
*/
|
||||||
|
visit(ctx: VisitorContext, node: any): Promise<TResult>;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface VisitorResult {
|
||||||
|
changes: Array<any>;
|
||||||
|
errors: Array<any>;
|
||||||
|
}
|
Loading…
Reference in New Issue