UNPKG

jinaga

Version:

Data management for web and mobile applications.

144 lines (129 loc) 5.59 kB
import { describeSpecification } from "../specification/description"; import { Condition, Match, Role, Specification } from "../specification/specification"; export function testSpecificationForCompliance(specification: Specification, purgeConditions: Specification[]): string[] { return specification.matches.map(m => testMatchForCompliance(m, purgeConditions)).flat(); } function testMatchForCompliance(match: Match, purgeConditions: Specification[]): string[] { var failedUnknownConditions = purgeConditions.filter(pc => pc.given[0].type === match.unknown.type && !hasCondition(match.conditions, pc) ); if (failedUnknownConditions.length > 0) { const specificationDescriptions = failedUnknownConditions.map(pc => describePurgeCondition(pc)).join(""); return [`The match for ${match.unknown.type} is missing purge conditions:\n${specificationDescriptions}`]; } var failedIntermediateConditions = purgeConditions.filter(pc => match.conditions.some(c => hasIntermediateType(c, pc.given[0].type)) ) if (failedIntermediateConditions.length > 0) { const specificationDescriptions = failedIntermediateConditions.map(pc => describePurgeCondition(pc)).join(""); return [`The match for ${match.unknown.type} passes through types that should have purge conditions:\n${specificationDescriptions}`]; } return []; } function hasCondition(conditions: Condition[], purgeCondition: Specification) { return conditions.some(c => conditionMatches(c, purgeCondition)); } function conditionMatches(condition: Condition, purgeCondition: Specification) { if (condition.type === "existential") { if (condition.exists) { // We only match negative existential conditions. return false; } // Compare the matches of the condition with the matches of the purge condition. if (condition.matches.length !== purgeCondition.matches.length) { return false; } return condition.matches.every((m, i) => matchesAreEquivalent(m, purgeCondition.matches[i])); } } function matchesAreEquivalent(match: Match, purgeMatch: Match): boolean { if (match.unknown.type !== purgeMatch.unknown.type) { return false; } if (match.conditions.length !== purgeMatch.conditions.length) { return false; } return match.conditions.every((c, i) => conditionsAreEquivalent(c, purgeMatch.conditions[i])); } function conditionsAreEquivalent(condition: Condition, purgeCondition: Condition) { if (condition.type === "path") { if (purgeCondition.type !== "path") { return false; } if (condition.rolesLeft.length !== purgeCondition.rolesLeft.length) { return false; } if (condition.rolesRight.length !== purgeCondition.rolesRight.length) { return false; } return condition.rolesLeft.every((r, i) => rolesAreEquivalent(r, purgeCondition.rolesLeft[i])) && condition.rolesRight.every((r, i) => rolesAreEquivalent(r, purgeCondition.rolesRight[i])); } else if (condition.type === "existential") { if (purgeCondition.type !== "existential") { return false; } if (condition.exists !== purgeCondition.exists) { return false; } if (condition.matches.length !== purgeCondition.matches.length) { return false; } return condition.matches.every((m, i) => matchesAreEquivalent(m, purgeCondition.matches[i])); } } function rolesAreEquivalent(role: Role, purgeRole: Role) { return role.predecessorType === purgeRole.predecessorType && role.name === purgeRole.name; } function hasIntermediateType(condition: Condition, type: string) { if (condition.type === "path") { var leftOnly = condition.rolesRight.length === 0; var rightOnly = condition.rolesLeft.length === 0; // If we only have left roles, then ignore the last role on the right. // If any of the roles is the type we're looking for, then we have an intermediate type. if (leftOnly) { var found = condition.rolesLeft.some((r, i) => r.predecessorType === type && i < condition.rolesLeft.length - 1); if (found) { return true; } } else { var found = condition.rolesLeft.some(r => r.predecessorType === type); if (found) { return true; } } // If we only have right roles, then ignore the last role on the left. // If any of the roles is the type we're looking for, then we have an intermediate type. if (rightOnly) { var found = condition.rolesRight.some((r, i) => r.predecessorType === type && i < condition.rolesRight.length - 1); if (found) { return true; } } else { var found = condition.rolesRight.some(r => r.predecessorType === type); if (found) { return true; } } } return false; } function describePurgeCondition(specification: Specification): string { var specificationWithoutProjection: Specification = { ...specification, projection: { type: "composite", components: [] } }; var description = describeSpecification(specificationWithoutProjection, 0); return `!E ${description}`; }