Handling validation issues
What validate(), validateXml() and parse() report, and how to act on it: the fields of an Issue, fixing a document by rule id, where the parser's diagnostics point, and the ValidationError of assertValid(). The rule ids and messages are those of the rule index.
What an issue says
const withoutReferences: InvoiceInput = { ...baseInvoice };
delete withoutReferences.buyerReference; // no buyer reference, and the base has no order reference
const invalidInvoice = createInvoice(withoutReferences);
const [issue] = validate(invalidInvoice).fatal;
const anatomy = issue && {
id: issue.id, // 'PEPPOL-EN16931-R003'
severity: issue.severity, // 'fatal'
message: issue.message, // 'A buyer reference or purchase order reference MUST be provided.'
xpath: issue.xpath, // '/ubl:Invoice/cbc:BuyerReference': where the element should be
pathString: issue.pathString, // 'buyerReference': the same place in the model
family: issue.family, // 'PEPPOL-EN16931-R'
definedBy: issue.definedBy, // 'peppol'
docsUrl: issue.docsUrl, // 'https://docs.peppol.eu/poacc/billing/3.0/rules/ubl-peppol/PEPPOL-EN16931-R003/'
};path is the same place as pathString, as an array. An issue can also carry bt (the business terms the rule is about), data (expected and actual values), contextXPath (the element the rule was evaluated on) and ruleset (the composition that ran).
Fix by rule id
Rule ids change far less often than messages, so test ids in your code. Messages are the specification's wording and may change with any release.
const result = validate(invalidInvoice);
const fixed = result.fatal.some((found) => found.id === 'PEPPOL-EN16931-R003')
? createInvoice({ ...withoutReferences, buyerReference: '0150abc' })
: invalidInvoice;Parser diagnostics
Diagnostics of the parser also carry a location, with the line and column in the XML.
const invalidXml = serialize(createInvoice(baseInvoice)).replace(
'<cbc:DocumentCurrencyCode>',
'<cbc:Colour>Blue</cbc:Colour>\n <cbc:DocumentCurrencyCode>',
);
const { diagnostics } = parse(invalidXml);
const where = diagnostics.map((found) => `${found.id} line ${String(found.location?.line)}`);
// ['EINV-XML-UNKNOWN-ELEMENT line 9']The ids are the syntax rules of EN 16931 (UBL-CR-*, UBL-SR-*, UBL-DT-*) or, where no rule covers the problem, this library's EINV-XML-* codes.
Values the schema would reject
A decimal, boolean or date written in a form the UBL schema does not allow is reported once as EINV-XSD-LEXICAL, and the rules that would read the value skip it rather than report follow-up errors.
const invalidDecimal = serialize(createInvoice(baseInvoice)).replace(
'<cbc:PriceAmount currencyID="EUR">400</cbc:PriceAmount>',
'<cbc:PriceAmount currencyID="EUR">400,00</cbc:PriceAmount>',
);
const lexical = validateXml(invalidDecimal).fatal.map((found) => found.id); // ['EINV-XSD-LEXICAL']ValidationError
assertValid() throws a ValidationError whose message lists the fatal rule ids and whose issues holds every issue, warnings included.
let summary = '';
try {
assertValid(invalidInvoice);
} catch (error) {
if (error instanceof ValidationError) {
summary = error.message;
// 'Validation against en16931+peppol@1.3.15+3.0.21 failed with 1 fatal issue(s): PEPPOL-EN16931-R003.'
}
}