Skip to content

Commit

Permalink
Fixed validation for csv.header: false (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
SmetDenis committed Mar 24, 2024
1 parent c06a6d0 commit b11dbfe
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 27 deletions.
5 changes: 5 additions & 0 deletions src/Csv/CsvFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ public function validate(bool $quickStop = false): ErrorSuite
return (new ValidatorCsv($this, $this->schema))->validate($quickStop);
}

public function getRealColumNumber(): int
{
return \count($this->getRecordsChunk(0, 1)->first());
}

private function prepareReader(): LeagueReader
{
$reader = LeagueReader::createFromPath($this->csvFilename)
Expand Down
2 changes: 1 addition & 1 deletion src/Rules/Cell/AllowValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class AllowValues extends AbstractCellRule

public function validateRule(string $cellValue): ?string
{
$allowedValues = $this->getOptionAsArray();
$allowedValues = \array_map('\strval', $this->getOptionAsArray());

if (\count($allowedValues) === 0) {
return 'Allowed values are not defined';
Expand Down
2 changes: 1 addition & 1 deletion src/Rules/Cell/NotAllowValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class NotAllowValues extends AbstractCellRule

public function validateRule(string $cellValue): ?string
{
$notAllowedValues = $this->getOptionAsArray();
$notAllowedValues = \array_map('\strval', $this->getOptionAsArray());

if (\count($notAllowedValues) === 0) {
return 'Not allowed values are not defined';
Expand Down
8 changes: 6 additions & 2 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,12 @@ public function getColumnsMappedByHeader(array $header): array
{
$map = [];

foreach ($header as $headerName) {
$map[$headerName] = $this->columns[$headerName] ?? null;
if ($this->getCsvStructure()->isHeader()) {
foreach ($header as $headerName) {
$map[$headerName] = $this->columns[$headerName] ?? null;
}
} else {
return $this->getColumns();
}

return $map;
Expand Down
72 changes: 51 additions & 21 deletions src/Validators/ValidatorCsv.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ private function validateHeader(bool $quickStop = false): ErrorSuite
return $errors;
}

/**
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
private function validateLines(bool $quickStop = false): ErrorSuite
{
$errors = new ErrorSuite();
Expand All @@ -126,16 +129,30 @@ private function validateLines(bool $quickStop = false): ErrorSuite
}

foreach ($this->csv->getRecords() as $line => $record) {
if ($isAggRules) { // Time & memory optimization
$columValues[] = ValidatorColumn::prepareValue($record[$column->getKey()], $aggInputType);
}
$lineNum = (int)$line + 1;

if ($isRules) { // Time optimization
$errors->addErrorSuit($colValidator->validateCell($record[$column->getKey()], (int)$line + 1));
if (!isset($record[$column->getKey()])) {
$errors->addError(
new Error(
'csv.column',
"Column index:{$column->getKey()} not found",
$column->getHumanName(),
$lineNum,
),
);
} else {
$errors->addErrorSuit($colValidator->validateCell($record[$column->getKey()], $lineNum));
}

if ($quickStop && $errors->count() > 0) {
return $errors;
}
}

if ($isAggRules && isset($record[$column->getKey()])) { // Time & memory optimization
$columValues[] = ValidatorColumn::prepareValue($record[$column->getKey()], $aggInputType);
}
}

Utils::debug("<i>Col</i> aggregate: {$column->getKey()}");
Expand Down Expand Up @@ -182,27 +199,40 @@ private function validateColumn(bool $quickStop): ErrorSuite
{
$errors = new ErrorSuite();

if (!$this->schema->getCsvStructure()->isHeader()) {
return $errors;
}
if ($this->schema->getCsvStructure()->isHeader()) {
$realColumns = $this->schema->getColumnsMappedByHeader($this->csv->getHeader());
$schemaColumns = $this->schema->getColumns();

$realColumns = $this->schema->getColumnsMappedByHeader($this->csv->getHeader());
$schemaColumns = $this->schema->getColumns();
$notFoundColums = \array_diff(\array_keys($schemaColumns), \array_keys($realColumns));

$notFoundColums = \array_diff(\array_keys($schemaColumns), \array_keys($realColumns));

if (\count($notFoundColums) > 0) {
$error = new Error(
'csv.header',
'Columns not found in CSV: "<c>' . \implode(', ', $notFoundColums) . '</c>"',
'',
ValidatorColumn::FALLBACK_LINE,
);
if (\count($notFoundColums) > 0) {
$error = new Error(
'csv.header',
'Columns not found in CSV: "<c>' . \implode(', ', $notFoundColums) . '</c>"',
'',
ValidatorColumn::FALLBACK_LINE,
);

$errors->addError($error);
$errors->addError($error);
if ($quickStop) {
return $errors;
}
}
} else {
$schemaColumns = \count($this->schema->getColumns());
$realColumns = $this->csv->getRealColumNumber();
if ($realColumns < $schemaColumns) {
$error = new Error(
'csv.header',
'Real number of columns is less than schema: ' . $realColumns . ' < ' . $schemaColumns,
'',
ValidatorColumn::FALLBACK_LINE,
);

if ($quickStop) {
return $errors;
$errors->addError($error);
if ($quickStop) {
return $errors;
}
}
}

Expand Down
41 changes: 41 additions & 0 deletions tests/Commands/ValidateCsvBasicTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,47 @@ public function testInvalidSchemaAndNotFoundCSV(): void
isSame($expected, $actual);
}

public function testValidateOneCsvNoHeaderNegative(): void
{
[$actual, $exitCode] = Tools::virtualExecution('validate:csv', [
'csv' => Tools::DEMO_CSV,
'schema' => './tests/schemas/simple_no_header.yml',
]);

$expected = $expected = <<<'TXT'
Found Schemas : 1
Found CSV files : 1
Pairs by pattern: 1
Check schema syntax: 1
(1/1) OK: ./tests/schemas/simple_no_header.yml
CSV file validation: 1
(1/1) Schema: ./tests/schemas/simple_no_header.yml
(1/1) CSV : ./tests/fixtures/demo.csv
(1/1) Issues: 2
+------+-----------+---------- demo.csv -----------------------------+
| Line | id:Column | Rule | Message |
+------+-----------+------------------+------------------------------+
| 2 | 0: | not_allow_values | Value "Clyde" is not allowed |
| 5 | 2: | not_allow_values | Value "74" is not allowed |
+------+-----------+---------- demo.csv -----------------------------+
Summary:
1 pairs (schema to csv) were found based on `filename_pattern`.
No issues in 1 schemas.
Found 2 issues in 1 out of 1 CSV files.
Schemas have no filename_pattern and are applied to all CSV files found:
- ./tests/schemas/simple_no_header.yml
TXT;

isSame(1, $exitCode, $actual);
isSame($expected, $actual);
}

public function testSchemaNotFound(): void
{
$this->expectExceptionMessage('Schema file(s) not found: invalid_schema_path.yml');
Expand Down
13 changes: 11 additions & 2 deletions tests/Validators/CsvValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,19 @@ public function testValidWithHeader(): void
isSame('', \strip_tags((string)$csv->validate()));
}

public function testValidWithoutHeader(): void
public function testInvalidWithoutHeader(): void
{
$csv = new CsvFile(Tools::CSV_SIMPLE_NO_HEADER, Tools::SCHEMA_SIMPLE_NO_HEADER);
isSame('', \strip_tags((string)$csv->validate()));
isSame(
<<<'TEXT'
"csv.header" at line 1. Real number of columns is less than schema: 2 < 3.
"csv.column" at line 1, column "2:". Column index:2 not found.
"csv.column" at line 2, column "2:". Column index:2 not found.
"csv.column" at line 3, column "2:". Column index:2 not found.
TEXT,
\strip_tags((string)$csv->validate()),
);
}

public function testInvalidSchemaFile(): void
Expand Down
5 changes: 5 additions & 0 deletions tests/schemas/simple_no_header.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ csv:
header: false

columns:
- rules:
not_allow_values: [ Clyde ]
- rules:
not_empty: true
- rules:
not_allow_values: [ 74 ]
not_empty: true
aggregate_rules:
is_unique: true

0 comments on commit b11dbfe

Please sign in to comment.