checkDataSafety function
checkDataSafetyFile over already-read csv.
Implementation
List<ReleaseProblem> checkDataSafety(
String csv, {
String where = 'data safety',
}) {
final problems = <ReleaseProblem>[];
final List<CsvRecord> records;
try {
records = parseCsvRecords(csv);
} on FormatException catch (e) {
return [ReleaseProblem(where, e.message)];
}
if (records.isEmpty) {
return [
ReleaseProblem(
where,
'the file is empty — export it again from the Play Console, Data '
'safety → Export to CSV',
),
];
}
final header = records.first.fields;
if (header.length != dataSafetyColumns.length ||
!_sameOrder(header, dataSafetyColumns)) {
return [
ReleaseProblem(
where,
'the header row is not the one Play exports.\n'
' found: ${header.join(' | ')}\n'
' expected: ${dataSafetyColumns.join(' | ')}',
),
];
}
// Every later check reads fields by position, so a short or long row is
// reported here and skipped rather than being silently read as some other
// row's data.
final rows = <DataSafetyRow>[];
for (var i = 1; i < records.length; i++) {
final record = records[i].fields;
final line = records[i].line;
if (record.length != dataSafetyColumns.length) {
problems.add(
ReleaseProblem(
'$where line $line',
'has ${record.length} fields, and every row has to have '
'${dataSafetyColumns.length} — a value containing a comma must be '
'quoted',
),
);
continue;
}
rows.add(
DataSafetyRow(
line: line,
questionId: record[0],
responseId: record[1],
responseValue: record[2],
requirement: record[3],
label: record[4],
),
);
}
for (final row in rows) {
if (row.questionId.isEmpty) {
problems.add(
ReleaseProblem('$where line ${row.line}', 'has no question id'),
);
}
if (row.requirement.isEmpty) {
problems.add(
ReleaseProblem(
'$where line ${row.line}',
'${row.questionId} has no answer requirement — the column that says '
'whether it needs an answer is blank',
),
);
}
if (row.requirement == dataSafetyRequired && row.responseValue.isEmpty) {
problems.add(
ReleaseProblem(
'$where line ${row.line}',
'${row.questionId} is REQUIRED and has no response value — Play '
'refuses the declaration, after the release has been committed',
),
);
}
}
problems.addAll(_duplicates(rows, where));
if (rows.every((r) => r.requirement != dataSafetyRequired)) {
problems.add(
ReleaseProblem(
where,
'no REQUIRED question in ${rows.length} row(s), which a Play export '
'always has — this is more likely a truncated or hand-assembled file '
'than a declaration with nothing to answer',
),
);
}
return problems;
}