Skip to content

Fix crash on schema invalid files #568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "pwa-node"
"type": "node"
},
{
"name": "Attach to Node Functions",
Expand Down
84 changes: 49 additions & 35 deletions services/rulesValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const getRuleMethodName = (ruleName) => {

const getFullContext = (xml, xpathContext, lineCount) => {
const elementNode = select(xpathContext.xpath, xml).find(
(element) => element.lineNumber + lineCount === xpathContext.lineNumber
(element) => element.lineNumber + lineCount === xpathContext.lineNumber,
);

const parentElement = elementNode.parentNode;
Expand Down Expand Up @@ -88,18 +88,18 @@ class Rules {
if (path.nodeName === 'reference') {
text = `For the ${parentNodeName} "${select(
'string(../title/narrative)',
path
path,
)}"`;
} else if (parentNodeName === 'transaction') {
const transactionType =
transactionTypes[select('string(../transaction-type/@code)', path)];

text = `For the ${transactionType || parentNodeName} of ${select(
'string(../transaction-date/@iso-date)',
path
path,
)} with value ${select('string(../value/@currency)', path)}${select(
'string(../value)',
path
path,
)}`;
}
return {
Expand All @@ -111,8 +111,8 @@ class Rules {
columnNumber: path.columnNumber,
text,
};
})
)
}),
),
);
}
if ('prefix' in oneCase) {
Expand All @@ -129,7 +129,7 @@ class Rules {
}));
}
return {};
})
}),
);
}
['less', 'more', 'start', 'date', 'end'].forEach((timeCase) => {
Expand All @@ -141,15 +141,15 @@ class Rules {
if ('idCondition' in oneCase) {
if (oneCase.idCondition === 'NOT_EXISTING_ORG_ID') {
this.idCondition = this.pathMatchesText.every(
(pathText) => !idSets['ORG-ID'].has(pathText)
(pathText) => !idSets['ORG-ID'].has(pathText),
);
}
if (oneCase.idCondition === 'NOT_EXISTING_ORG_ID_PREFIX') {
this.idCondition = this.pathMatchesText.every(
(pathText) =>
!Array.from(idSets['ORG-ID']).some((orgId) =>
pathText.startsWith(`${orgId}-`)
)
pathText.startsWith(`${orgId}-`),
),
);
}
}
Expand Down Expand Up @@ -258,7 +258,7 @@ class Rules {
this.addFailureContext(currency);
result = false;
}
})
}),
);
return result;
default:
Expand All @@ -277,19 +277,19 @@ class Rules {

strictSum(oneCase) {
const computedSum = Number(
this.pathMatchesText.reduce((acc, val) => Number(acc) + Number(val), 0).toFixed(4)
this.pathMatchesText.reduce((acc, val) => Number(acc) + Number(val), 0).toFixed(4),
);
const limitSum = Number(oneCase.sum);
if (computedSum !== limitSum) {
const elements = Array.from(
new Set(this.pathMatches.map((path) => path.ownerElement.nodeName))
new Set(this.pathMatches.map((path) => path.ownerElement.nodeName)),
).join(', ');
const vocabularies = Array.from(
new Set(
this.pathMatches.map((path) =>
select('string(@vocabulary | ../@vocabulary)', path.ownerElement)
)
)
select('string(@vocabulary | ../@vocabulary)', path.ownerElement),
),
),
).join(', ');
const text = `The sum is ${computedSum} for ${elements} in vocabulary ${vocabularies}`;
this.failContext.push({
Expand Down Expand Up @@ -350,7 +350,7 @@ class Rules {
regex(oneCase, allMatches) {
const regEx = new RegExp(oneCase.regex);
return this.pathMatchesText.every(
(pathMatchText) => pathMatchText === '' || regEx.test(pathMatchText) === allMatches
(pathMatchText) => pathMatchText === '' || regEx.test(pathMatchText) === allMatches,
);
}

Expand All @@ -377,7 +377,7 @@ class Rules {
}
// get text matches for start into Array
const startMatchesText = _.flatten(
oneCase.prefix.map((path) => select(path, this.element))
oneCase.prefix.map((path) => select(path, this.element)),
).map((match) => getText(match));

// every path match (e.g. iati-identifier), must start with at least one (some) start match (e.g. reporting-org/@ref)
Expand All @@ -386,7 +386,7 @@ class Rules {
const separator = _.has(oneCase, 'separator') ? oneCase.separator : '';
const textWithSep = startText + separator;
return pathMatchText.startsWith(textWithSep);
})
}),
);
}

Expand All @@ -409,7 +409,7 @@ class Rules {

noSpaces() {
return this.pathMatchesText.every(
(pathMatchText) => pathMatchText === pathMatchText.trim()
(pathMatchText) => pathMatchText === pathMatchText.trim(),
);
}
}
Expand Down Expand Up @@ -474,7 +474,7 @@ const testRuleLoop = (contextXpath, element, oneCase, idSets, lineCount = 0) =>
}
});
results.push(
testRule(contextXpath, element, subRule, subCaseTest, idSets, lineCount)
testRule(contextXpath, element, subRule, subCaseTest, idSets, lineCount),
);
});
});
Expand Down Expand Up @@ -512,11 +512,11 @@ const testRuleset = (ruleset, xml, idSets, lineCount = 0) => {
theCases.forEach((oneCase) => {
if (rule === 'loop') {
result = result.concat(
testRuleLoop(contextXpath, element, oneCase, idSets, lineCount)
testRuleLoop(contextXpath, element, oneCase, idSets, lineCount),
);
} else {
result.push(
testRule(contextXpath, element, rule, oneCase, idSets, lineCount)
testRule(contextXpath, element, rule, oneCase, idSets, lineCount),
);
}
});
Expand Down Expand Up @@ -547,7 +547,7 @@ const createPathsContext = (caseContext, xpathContext, concatenate) => {
`${acc}${i === 0 ? 'For ' : ' and '}${xpathContext.xpath}/${path.xpath} = '${
path.value
}' at line: ${path.lineNumber}, column: ${path.columnNumber}`,
''
'',
);
return [{ text }];
}
Expand Down Expand Up @@ -581,7 +581,7 @@ const standardiseResultFormat = (result, showDetails, xml, lineCount) => {
elementContext = `<${xpathContext.xpath.split('/').pop()}>`;
}
context.push({
text: `For ${elementContext} at line: ${xpathContext.lineNumber}, column: ${xpathContext.columnNumber}`
text: `For ${elementContext} at line: ${xpathContext.lineNumber}, column: ${xpathContext.columnNumber}`,
});
break;
case 'dateNow':
Expand Down Expand Up @@ -661,8 +661,8 @@ const standardiseResultFormat = (result, showDetails, xml, lineCount) => {
caseContext.prefix.map((prefixPath) =>
caseContext.paths.map((casePath) => ({
text: `For prefix: ${xpathContext.xpath}/${prefixPath.xpath} = '${prefixPath.value}' at line: ${prefixPath.lineNumber}, column: ${prefixPath.columnNumber} and ${xpathContext.xpath}/${casePath.xpath} = '${casePath.value}' at line: ${casePath.lineNumber}, column: ${casePath.columnNumber}`,
}))
)
})),
),
);
}
break;
Expand Down Expand Up @@ -699,9 +699,23 @@ const standardiseResultFormat = (result, showDetails, xml, lineCount) => {
};

const validateSchema = (xmlString, schema, identifier, title, showDetails, lineOffset = 0) => {
const xmlDoc = libxml.parseXml(xmlString);
let xmlDoc = null;

try {
xmlDoc = libxml.parseXml(xmlString);
} catch (error) {
return [
{
id: '0.3.1',
category: 'schema',
severity: 'critical',
message: error.message,
context: [{ text: `At line ${lineOffset + error.line}` }],
},
];
}

if (!xmlDoc.validate(schema)) {
if (xmlDoc != null && !xmlDoc.validate(schema)) {
const curSchemaErrors = xmlDoc.validationErrors.reduce((acc, error) => {
let errContext;
const errorDetail = error;
Expand Down Expand Up @@ -809,7 +823,7 @@ const splitXMLTransform = (root, elementName) => {

// push single subelement, wrapped in root
this.push(
`${rootOpen}${spacer}${doc.slice(0, closeIndex + close.length)}${rootClose}`
`${rootOpen}${spacer}${doc.slice(0, closeIndex + close.length)}${rootClose}`,
);

// remove subelement that's been pushed
Expand Down Expand Up @@ -844,7 +858,7 @@ const validateIATI = async (
idSets,
schema,
showDetails = false,
showElementMeta = false
showElementMeta = false,
) => {
let schemaErrors = [];
const ruleErrors = {};
Expand Down Expand Up @@ -934,13 +948,13 @@ const validateIATI = async (
result,
showDetails,
singleActivityDoc,
lineCount
)
lineCount,
),
);
}
return acc;
},
[]
[],
);

if (errors.length > 0) {
Expand All @@ -961,7 +975,7 @@ const validateIATI = async (
await pipeline(
Readable.from(xml),
splitXMLTransform(fileType, fileDefinition[fileType].subRoot),
processActivity()
processActivity(),
);

// if no iati child elements found, evaluate schema errors at file level
Expand Down
Loading