Skip to content

Commit

Permalink
feat: Change policy validation result from string to object (#1450)
Browse files Browse the repository at this point in the history
  • Loading branch information
john-fletcher-aot authored Jun 27, 2024
1 parent 110b992 commit 07dd94c
Show file tree
Hide file tree
Showing 10 changed files with 247 additions and 62 deletions.
1 change: 1 addition & 0 deletions policy/src/enum/permit-app-info.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
export enum PermitAppInfo {
PermitStartDate = 'permitData.startDate',
PermitDateFormat = 'YYYY-MM-DD',
CompanyName = 'permitData.companyName',
}
6 changes: 6 additions & 0 deletions policy/src/enum/validation-result-code.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum ValidationResultCode {
PermitTypeUnknown = 'permit-type-unknown',
FieldValidationError = 'field-validation-error',
ConfigurationInvalid = 'configuration-invalid',
GeneralResult = 'general-result',
}
15 changes: 11 additions & 4 deletions policy/src/policy-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import PermitType from './interface/permit-type.interface';
import { Engine, EngineResult } from 'json-rules-engine';
import { getRulesEngines } from './helper/rules-engine.helper';
import PermitApplication from './type/permit-application.type';
import ValidationResults from './validation-results';
import ValidationResult from './validation-result';
import { addRuntimeFacts, transformPermitFacts } from './helper/facts.helper';
import { ValidationResultType } from './enum/validation-result-type.enum';
import { ValidationResultCode } from './enum/validation-result-code.enum';

/** Class representing commercial vehicle policy. */
class Policy {
Expand Down Expand Up @@ -34,15 +37,19 @@ class Policy {
* @param permit The permit application to validate against policy
* @returns Results of the validation of the permit application
*/
async validate(permit: PermitApplication): Promise<ValidationResult> {
async validate(permit: PermitApplication): Promise<ValidationResults> {
const engine = this.rulesEngines.get(permit.permitType);
if (!engine) {
// If the permit type being validated has no configuration in the
// policy definition, there will be no engine for it. Return with
// a single violation result.
const validationResult: ValidationResult = new ValidationResult();
const validationResult: ValidationResults = new ValidationResults();
validationResult.violations.push(
`Permit type ${permit.permitType} not permittable`,
new ValidationResult(
ValidationResultType.Violation,
ValidationResultCode.PermitTypeUnknown,
`Permit type ${permit.permitType} unknown`,
)
);
return validationResult;
} else {
Expand All @@ -56,7 +63,7 @@ class Policy {
const engineResult: EngineResult = await engine.run(permitFacts);

// Wrap the json-rules-engine result in a ValidationResult object
return new ValidationResult(engineResult);
return new ValidationResults(engineResult);
}
}

Expand Down
79 changes: 21 additions & 58 deletions policy/src/validation-result.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,28 @@
import { EngineResult } from 'json-rules-engine';
import { ValidationResultType } from './enum/validation-result-type.enum';

/** Represents the result of a permit application validation against policy */
class ValidationResult {
/** Array of policy violations found during validation. */
violations: Array<string> = [];
/**
* Represents a single validation result when a permit application is
* validated against policy. A single validation run can result in multiple
* validation result messages.
*/
export class ValidationResult {
/**
* Array of additional requirements found during validation. For example,
* a permit may require that other additional permits be applied for in
* order to be legal.
* Creates a new ValidationResult from the supplied type, code, and message.
*
* @param type type of result, one of the ValidationResultType enum
* @param code code for identifying common categories of results
* @param message text message describing the result
*/
requirements: Array<string> = [];
/** Array of policy warnings found during validation. */
warnings: Array<string> = [];
/**
* Array of policy messages found during validation. These should be
* considered informational only and do not impact the validity of the
* permit.
*/
information: Array<string> = [];

/**
* Creates a new ValidationResult from the json-rules-engine result.
* Populates the violations, requirements, warnings, and messages arrays
* based on the events in the json-rules-engine result.
* @param engineResult json-rules-engine run result.
*/
constructor(engineResult?: EngineResult) {
if (engineResult) {
engineResult.events.forEach((e) => {
let message: string;
if (e.params && e.params.message) {
message = e.params.message;
} else {
// All events should have a message, if there is no message
// push the entire params in (likely error scenario).
message = `Unknown message: params=${JSON.stringify(e.params)}`;
}

switch (e.type) {
case ValidationResultType.Violation:
this.violations.push(message);
break;
case ValidationResultType.Requirement:
this.requirements.push(message);
break;
case ValidationResultType.Warning:
this.warnings.push(message);
break;
case ValidationResultType.Information:
this.information.push(message);
break;
default:
// Treat any unknown validation event as a violation since
// it is an unexpected scenario.
console.log('Unknown validation event encountered');
this.violations.push(message);
}
});
}
constructor(type: string, code: string, message: string) {
this.type = type;
this.code = code;
this.message = message;
}

type: string;
code: string;
message: string;
fieldReference?: string;
}

export default ValidationResult;
export default ValidationResult;
69 changes: 69 additions & 0 deletions policy/src/validation-results.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { EngineResult } from 'json-rules-engine';
import { ValidationResultType } from './enum/validation-result-type.enum';
import { ValidationResultCode } from './enum/validation-result-code.enum';
import { ValidationResult } from './validation-result';

/** Represents the results of a permit application validation against policy */
class ValidationResults {
/** Array of policy violations found during validation. */
violations: Array<ValidationResult> = [];
/**
* Array of additional requirements found during validation. For example,
* a permit may require that other additional permits be applied for in
* order to be legal.
*/
requirements: Array<ValidationResult> = [];
/** Array of policy warnings found during validation. */
warnings: Array<ValidationResult> = [];
/**
* Array of policy messages found during validation. These should be
* considered informational only and do not impact the validity of the
* permit.
*/
information: Array<ValidationResult> = [];

/**
* Creates a new ValidationResults from the json-rules-engine result.
* Populates the violations, requirements, warnings, and messages arrays
* based on the events in the json-rules-engine result.
* @param engineResult json-rules-engine run result.
*/
constructor(engineResult?: EngineResult) {
if (engineResult) {
engineResult.events.forEach((e) => {
let message: string = e.params?.message ?? `Unknown message: params=${JSON.stringify(e.params)}`;
let code: string = e.params?.code ?? ValidationResultCode.GeneralResult;
let type: string = e.type ?? ValidationResultType.Violation.toString();

let result: ValidationResult = new ValidationResult(type, code, message);
result.fieldReference = e.params?.fieldReference;

switch (type) {
case ValidationResultType.Violation:
this.violations.push(result);
break;
case ValidationResultType.Requirement:
this.requirements.push(result);
break;
case ValidationResultType.Warning:
this.warnings.push(result);
break;
case ValidationResultType.Information:
this.information.push(result);
break;
default:
// Treat any unknown validation event as a violation since
// it is an unexpected scenario.
console.log('Unknown validation event encountered');
this.violations.push(new ValidationResult(
ValidationResultType.Violation,
ValidationResultCode.GeneralResult,
'Unknown validation event encountered'
));
}
});
}
}
}

export default ValidationResults;
30 changes: 30 additions & 0 deletions policy/test/policy-config/master.sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Company is required',
code: 'field-validation-error',
fieldReference: 'permitData.companyName',
},
},
},
Expand All @@ -31,6 +33,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Contact first name is required',
code: 'field-validation-error',
fieldReference: 'permitData.contactDetails.firstName',
},
},
},
Expand All @@ -46,6 +50,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Contact last name is required',
code: 'field-validation-error',
fieldReference: 'permitData.contactDetails.lastName',
},
},
},
Expand All @@ -61,6 +67,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Contact phone number is required',
code: 'field-validation-error',
fieldReference: 'permitData.contactDetails.phone1',
},
},
},
Expand All @@ -76,6 +84,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Company contact email is required',
code: 'field-validation-error',
fieldReference: 'permitData.contactDetails.email',
},
},
},
Expand All @@ -95,6 +105,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Permit start date cannot be in the past',
code: 'field-validation-error',
fieldReference: 'permitData.startDate',
},
},
},
Expand All @@ -111,6 +123,8 @@ export const masterPolicyConfig: PolicyDefinition = {
params: {
message:
'Vehicle Identification Number (vin) must be 6 alphanumeric characters',
code: 'field-validation-error',
fieldReference: 'permitData.vehicleDetails.vin',
},
},
},
Expand All @@ -126,6 +140,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Vehicle plate is required',
code: 'field-validation-error',
fieldReference: 'permitData.vehicleDetails.plate',
},
},
},
Expand All @@ -141,6 +157,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Vehicle make is required',
code: 'field-validation-error',
fieldReference: 'permitData.vehicleDetails.make',
},
},
},
Expand All @@ -156,6 +174,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Vehicle year is required',
code: 'field-validation-error',
fieldReference: 'permitData.vehicleDetails.year',
},
},
},
Expand All @@ -171,6 +191,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Vehicle country of registration is required',
code: 'field-validation-error',
fieldReference: 'permitData.vehicleDetails.countryCode',
},
},
},
Expand Down Expand Up @@ -210,6 +232,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Duration must be in 30 day increments or a full year',
code: 'field-validation-error',
fieldReference: 'permitData.permitDuration',
},
},
},
Expand All @@ -227,6 +251,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Vehicle type not permittable for this permit type',
code: 'field-validation-error',
fieldReference: 'permitData.vehicleDetails.vehicleSubType',
},
},
},
Expand Down Expand Up @@ -266,6 +292,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Duration must be in 30 day increments or a full year',
code: 'field-validation-error',
fieldReference: 'permitData.permitDuration',
},
},
},
Expand All @@ -283,6 +311,8 @@ export const masterPolicyConfig: PolicyDefinition = {
type: 'violation',
params: {
message: 'Vehicle type not permittable for this permit type',
code: 'field-validation-error',
fieldReference: 'permitData.vehicleDetails.vehicleSubType',
},
},
},
Expand Down
2 changes: 2 additions & 0 deletions policy/test/policy-config/tros-no-allowed-vehicles.sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export const trosNoAllowedVehicles: PolicyDefinition = {
type: 'violation',
params: {
message: 'Vehicle type not permittable for this permit type',
code: 'field-validation-error',
fieldReference: 'permitData.vehicleDetails.vehicleSubType',
},
},
},
Expand Down
Loading

0 comments on commit 07dd94c

Please sign in to comment.