1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154/*
/* Converts Vera results to Equalify v2 Blockers format (formerly stream2)
/*
/* See equalifyv2 format types in shared/types/
*/
import {
Blocker,
StreamResults,
} from "../types/streamResults.equalifyV2_format";
// Inferred Interfaces from results
interface CheckDetail {
// Whether this individual check instance passed or failed โ lowercase in actual output
// e.g. "failed", "passed", "warning"
status: string;
// XPath-like path to the PDF object that triggered this check
// e.g. "root/document[0]/pages[0]/page[0]/annots[0]/annot[0]"
context: string;
// Human-readable error message, with errorArguments substituted in
errorMessage: string;
// Arguments substituted into the error message template; entries may be null
errorArguments: (string | null)[];
// Present in some veraPDF versions โ secondary location info
location?: {
level: string;
context: string;
};
}
interface RuleSummary {
// Lowercase status matching CheckDetail โ e.g. "failed", "passed"
status: string;
// Uppercase aggregate status for the rule as a whole - "FAILED" | "PASSED" | "WARNING";
ruleStatus: string;
// The standards body document โ e.g. "ISO 14289-2:2024", "WCAG 2.1"
specification: string;
// Section/clause within the specification โ e.g. "7.1", "Table 1", "Annex A"
clause: string;
// Test number within the clause โ e.g. 1, 2
testNumber: number;
// Human-readable description of what the rule checks
description: string;
// The PDF object type this rule operates on
// e.g. "PDDocument", "PDPage", "PDAnnot", "CosStream", "PDStructElem"
object: string;
// The actual validation test expression evaluated against the object
// e.g. "hasTag" or a boolean XPath expression โ often highly technical
test: string;
// Classification tags โ e.g. ["PDF/UA-2"], ["WCAG21", "PDF/UA-2"]
tags?: string[] | null;
// Absent (not 0) when recordPasses=false and no passes were recorded
passedChecks?: number;
failedChecks: number;
// Individual check instances โ only present for FAILED rules when recordPasses=false
checks?: CheckDetail[];
}
interface ValidationResult {
details: {
// Aggregate of all tags across all rule summaries in this validation result
tags: string[];
ruleSummaries: RuleSummary[];
};
}
interface ReportJob {
validationResult: ValidationResult[];
}
export interface ReportData {
report: {
jobs: ReportJob[];
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function convertVeraToEqualifyV2(reportData: ReportData, job: any): StreamResults {
const blockers: Blocker[] = [];
try {
// Ensure the path to jobs exists before proceeding
if (!reportData?.report?.jobs) {
/* console.warn(
"Input JSON structure error!"
); */
throw new Error;
}
// Traverse the jobs array
for (const job of reportData.report.jobs) {
if (!job.validationResult) continue;
// Traverse the validation results array
for (const validation of job.validationResult) {
if (!validation.details?.ruleSummaries) continue;
// Traverse the rule summaries array
for (const ruleSummary of validation.details.ruleSummaries) {
// Only process rules that have failed and have detailed check data
if (ruleSummary.ruleStatus !== "FAILED" || !ruleSummary.checks) {
continue;
}
// Map each individual check detail to the Blocker format
for (const check of ruleSummary.checks) {
const blocker: Blocker = {
source: 'pdf-scan',
tags: ruleSummary.tags || null,
description: ruleSummary.description,
// Fields specific to the individual CheckDetail instance
test: ruleSummary.specification + " - " + ruleSummary.clause, // This ultimately maps to category
summary: check.errorMessage,
node: check.context,
};
blockers.push(blocker);
}
}
}
}
} catch (error) {
throw error;
}
const timeNow = new Date().toISOString();
const out: StreamResults = {
auditId: job.auditId,
scanId: job.scanId,
urlId: job.urlId,
blockers,
date: timeNow,
message: `Vera scan of ${job.url} complete.`,
status: "complete"
};
return out;
}
export default convertVeraToEqualifyV2;