From 1dbdbe101766334082559ebc72acfeab0af753e8 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 00:54:14 +0400 Subject: [PATCH 01/17] Refactoring This commit updates the GitHub Actions workflows by adding a new "test-phar" job for verifying the functionality of the PHAR binary. The names and actions of existing jobs were updated to be more informative. The README file was also cleaned up for better readability. Changes also included the addition of planned features related to tool versioning. --- .github/workflows/demo.yml | 60 ---------------------------------- .github/workflows/main.yml | 66 ++++++++++++++++++++++++++++++-------- README.md | 23 ++----------- 3 files changed, 55 insertions(+), 94 deletions(-) diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index fcdbf71d..b0c153d9 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -111,63 +111,3 @@ jobs: validate:csv \ --csv=/parent-host/tests/fixtures/batch/*.csv \ --schema=/parent-host/tests/schemas/demo_invalid.yml - - - phar: - name: Phar - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.3 - tools: composer - - - name: Build the project - run: make build --no-print-directory - - - name: 👍 Valid CSV file - run: | - ./build/csv-blueprint.phar \ - validate:csv \ - --csv=./tests/fixtures/batch/*.csv \ - --schema=./tests/schemas/demo_valid.yml - - - name: 👎 Invalid CSV file - run: | - ! ./build/csv-blueprint.phar \ - validate:csv \ - --csv=./tests/fixtures/batch/*.csv \ - --schema=./tests/schemas/demo_invalid.yml - - - php: - name: Pure PHP - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.3 - tools: composer - - - name: Build the Project - run: make build-install --no-print-directory - - - name: 👍 Valid CSV file - run: | - ./csv-blueprint \ - validate:csv \ - --csv=./tests/fixtures/batch/*.csv \ - --schema=./tests/schemas/demo_valid.yml - - - name: 👎 Invalid CSV file - run: | - ! ./csv-blueprint \ - validate:csv \ - --csv=./tests/fixtures/batch/*.csv \ - --schema=./tests/schemas/demo_invalid.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 89eb356c..60422c44 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -139,8 +139,40 @@ jobs: name: Reports - ${{ matrix.php-version }} path: build/ - phar: - name: Phar + + test-php-binary: + name: Verify PHP binary + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + tools: composer + + - name: Build the Project + run: make build-install --no-print-directory + + - name: 👍 Valid CSV file + run: | + ./csv-blueprint \ + validate:csv \ + --csv=./tests/fixtures/batch/*.csv \ + --schema=./tests/schemas/demo_valid.yml + + - name: 👎 Invalid CSV file + run: | + ! ./csv-blueprint \ + validate:csv \ + --csv=./tests/fixtures/batch/*.csv \ + --schema=./tests/schemas/demo_invalid.yml + + + test-phar: + name: Verify PHAR runs-on: ubuntu-latest strategy: matrix: @@ -148,26 +180,35 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} - coverage: xdebug tools: composer - extensions: ast - name: Build the project run: make build --no-print-directory - - name: Building Phar binary file - run: make build-phar --no-print-directory - - - name: Trying to use the phar file + - name: Test help and logo run: ./build/csv-blueprint.phar + - name: 👍 Valid CSV file + run: | + ./build/csv-blueprint.phar \ + validate:csv \ + --csv=./tests/fixtures/batch/*.csv \ + --schema=./tests/schemas/demo_valid.yml \ + --ansi + + - name: 👎 Invalid CSV file + run: | + ! ./build/csv-blueprint.phar \ + validate:csv \ + --csv=./tests/fixtures/batch/*.csv \ + --schema=./tests/schemas/demo_invalid.yml \ + --ansi + - name: Upload Artifacts uses: actions/upload-artifact@v3 continue-on-error: true @@ -177,7 +218,7 @@ jobs: docker: - name: Docker + name: Verify as Docker runs-on: ubuntu-latest steps: - name: Checkout code @@ -190,5 +231,4 @@ jobs: run: docker run --rm jbzoo/csv-blueprint --ansi - name: Reporting example via Docker - run: make demo-docker --no-print-directory - continue-on-error: true + run: ! make demo-docker --no-print-directory diff --git a/README.md b/README.md index c88fea3e..ec837198 100644 --- a/README.md +++ b/README.md @@ -4,27 +4,6 @@ [![Stable Version](https://poser.pugx.org/jbzoo/csv-blueprint/version)](https://packagist.org/packages/jbzoo/csv-blueprint/) [![Total Downloads](https://poser.pugx.org/jbzoo/csv-blueprint/downloads)](https://packagist.org/packages/jbzoo/csv-blueprint/stats) [![Docker Pulls](https://img.shields.io/docker/pulls/jbzoo/csv-blueprint.svg)](https://hub.docker.com/r/jbzoo/csv-blueprint) [![Dependents](https://poser.pugx.org/jbzoo/csv-blueprint/dependents)](https://packagist.org/packages/jbzoo/csv-blueprint/dependents?order_by=downloads) [![GitHub License](https://img.shields.io/github/license/jbzoo/csv-blueprint)](https://github.com/JBZoo/Csv-Blueprint/blob/master/LICENSE) - -* [Introduction](#introduction) -* [Why validate CSV files in CI?](#why-validate-csv-files-in-ci) -* [Features](#features) -* [Live Demo](#live-demo) -* [Usage](#usage) - * [As GitHub Action](#as-github-action) - * [As Docker container](#as-docker-container) - * [As PHP binary](#as-php-binary) - * [As PHP project](#as-php-project) - * [CLI Help Message](#cli-help-message) - * [Report examples](#report-examples) - * [Schema Definition](#schema-definition) - * [Schema file examples](#schema-file-examples) -* [Coming soon](#coming-soon) -* [Disadvantages?](#disadvantages) -* [Contributing](#contributing) -* [License](#license) -* [See Also](#see-also) - - ## Introduction The JBZoo/Csv-Blueprint tool is a powerful and flexible utility designed for validating CSV files against @@ -531,6 +510,8 @@ Release workflow * [ ] Build and release Docker image [via GitHub Actions, tags and labels](https://docs.docker.com/build/ci/github-actions/manage-tags-labels/). Review it. * [ ] Upgrading to PHP 8.3.x * [ ] Build phar file and release via GitHub Actions. +* [ ] Auto insert tool version into the Docker image and phar file. It's important to know the version of the tool you are using. +* [ ] Show version as part of output. Performance and optimization * [ ] Parallel validation of really-really large files (1GB+ ?). I know you have them and not so much memory. From 8cf6da1a8c6ae8cacfc3f3ada084655fc394bcd3 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 00:59:37 +0400 Subject: [PATCH 02/17] The README has been updated to reflect this change and ensure users are aware of the new output format. --- .github/workflows/demo.yml | 6 ++++-- README.md | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index b0c153d9..44d3f8b1 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -101,7 +101,8 @@ jobs: --rm jbzoo/csv-blueprint \ validate:csv \ --csv=/parent-host/tests/fixtures/batch/*.csv \ - --schema=/parent-host/tests/schemas/demo_valid.yml + --schema=/parent-host/tests/schemas/demo_valid.yml \ + --ansi - name: 👎 Invalid CSV file run: | @@ -110,4 +111,5 @@ jobs: --rm jbzoo/csv-blueprint \ validate:csv \ --csv=/parent-host/tests/fixtures/batch/*.csv \ - --schema=/parent-host/tests/schemas/demo_invalid.yml + --schema=/parent-host/tests/schemas/demo_invalid.yml \ + --ansi diff --git a/README.md b/README.md index ec837198..ffee22dd 100644 --- a/README.md +++ b/README.md @@ -496,6 +496,7 @@ Batch processing Validation * [x] ~~`filename_pattern` validation with regex (like "all files in the folder should be in the format `/^[\d]{4}-[\d]{2}-[\d]{2}\.csv$/`").~~ +* [ ] Flag to ignore file name pattern. It's useful when you have a lot of files and you don't want to validate the file name. * [ ] Agregate rules (like "at least one of the fields should be not empty" or "all values must be unique"). * [ ] Handle empty files and files with only a header row, or only with one line of data. One column wthout header is also possible. * [ ] Using multiple schemas for one csv file. @@ -526,6 +527,7 @@ Mock data generation Reporting * [ ] More report formats (like JSON, XML, etc). Any ideas? * [ ] Gitlab and JUnit reports must be as one structure. It's not so easy to implement. But it's a good idea. +* [ ] Merge reports from multiple CSV files into one report. It's useful when you have a lot of files and you want to see all errors in one place. Especially for GitLab and JUnit reports. Misc * [ ] Use it as PHP SDK. Examples in Readme. From 8554aacc6794c3e61aeba93a18325a6e06340907 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 01:05:49 +0400 Subject: [PATCH 03/17] Update Docker actions in GitHub Workflow Added distinct Docker run actions for valid and invalid CSV file validation in the GitHub workflows main.yml file. Extended the workflow steps to individually deal with valid and invalid CSV files. Related image and volume parameters have been adjusted accordingly to run the validation tests properly. --- .github/workflows/main.yml | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 60422c44..bf96b2cd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -227,8 +227,25 @@ jobs: - name: 🐳 Building Docker Image run: make build-docker - - name: Trying to use the Docker Image + - name: Test help and logo run: docker run --rm jbzoo/csv-blueprint --ansi - - name: Reporting example via Docker - run: ! make demo-docker --no-print-directory + - name: 👍 Valid CSV file + run: | + docker run --rm \ + -v `pwd`:/parent-host \ + jbzoo/csv-blueprint \ + validate:csv \ + --csv=/parent-host/tests/fixtures/demo.csv \ + --schema=/parent-host/tests/schemas/demo_valid.yml \ + --ansi + + - name: 👎 Invalid CSV file + run: | + ! docker run --rm \ + -v `pwd`:/parent-host \ + jbzoo/csv-blueprint \ + validate:csv \ + --csv=/parent-host/tests/fixtures/demo.csv \ + --schema=/parent-host/tests/schemas/demo_invalid.yml \ + --ansi From 4f4013ccb43e1c5804d5d45f935e9a8cf925d911 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 01:14:23 +0400 Subject: [PATCH 04/17] Refactor Docker action name and enhance code functions --- .github/workflows/main.yml | 2 +- csv-blueprint.php | 4 ++++ src/Utils.php | 8 +++++++- tests/Blueprint/ReadmeTest.php | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bf96b2cd..4d76443f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -218,7 +218,7 @@ jobs: docker: - name: Verify as Docker + name: Verify Docker runs-on: ubuntu-latest steps: - name: Checkout code diff --git a/csv-blueprint.php b/csv-blueprint.php index 330bce06..99219ee0 100644 --- a/csv-blueprint.php +++ b/csv-blueprint.php @@ -17,6 +17,7 @@ namespace JBZoo\CsvBlueprint; use JBZoo\Cli\CliApplication; +use JBZoo\Utils\Cli; \define('PATH_ROOT', __DIR__); @@ -33,6 +34,9 @@ } } +var_dump(Cli::getNumberOfColumns()); +exit(1); + require_once JBZOO_AUTOLOAD_FILE; \date_default_timezone_set('UTC'); diff --git a/src/Utils.php b/src/Utils.php index 02b99f78..57beb1cc 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -95,6 +95,12 @@ public static function findFiles(array $paths): array public static function cutPath(string $fullpath): string { - return \str_replace((string)\getcwd(), '.', $fullpath); + $pwd = (string)\getcwd(); + + if (\strlen($pwd) <= 1) { + return $fullpath; + } + + return \str_replace($pwd, '.', $fullpath); } } diff --git a/tests/Blueprint/ReadmeTest.php b/tests/Blueprint/ReadmeTest.php index 9373d3dd..f1b10cf1 100644 --- a/tests/Blueprint/ReadmeTest.php +++ b/tests/Blueprint/ReadmeTest.php @@ -19,6 +19,7 @@ use JBZoo\PHPUnit\PHPUnit; use JBZoo\PHPUnit\TestTools; use JBZoo\Utils\Cli; +use JBZoo\Utils\Sys; use Symfony\Component\Console\Input\StringInput; use function JBZoo\PHPUnit\isFileContains; From cfa28017b7cbdb4a7e66cb0b653a683577e0b949 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 01:17:35 +0400 Subject: [PATCH 05/17] Update PHPStan and Roave/SecurityAdvisories versions The commit updates the versions of PHPStan and Roave/SecurityAdvisories in the composer.lock file. Both libraries have been upgraded to newer versions, with the reference and URL addresses correspondingly updated. This ensures that the project is using the most recent --- composer.lock | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/composer.lock b/composer.lock index ff94acda..703111e6 100644 --- a/composer.lock +++ b/composer.lock @@ -4355,16 +4355,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.60", + "version": "1.10.62", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe" + "reference": "cd5c8a1660ed3540b211407c77abf4af193a6af9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/95dcea7d6c628a3f2f56d091d8a0219485a86bbe", - "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd5c8a1660ed3540b211407c77abf4af193a6af9", + "reference": "cd5c8a1660ed3540b211407c77abf4af193a6af9", "shasum": "" }, "require": { @@ -4413,7 +4413,7 @@ "type": "tidelift" } ], - "time": "2024-03-07T13:30:19+00:00" + "time": "2024-03-13T12:27:20+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -5198,12 +5198,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "bb15a6dc9a8493ace041a6de2929eb63ba0809ef" + "reference": "eedc674d89085b0199bd96bfad410404fb2f5dbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/bb15a6dc9a8493ace041a6de2929eb63ba0809ef", - "reference": "bb15a6dc9a8493ace041a6de2929eb63ba0809ef", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/eedc674d89085b0199bd96bfad410404fb2f5dbf", + "reference": "eedc674d89085b0199bd96bfad410404fb2f5dbf", "shasum": "" }, "conflict": { @@ -5930,7 +5930,7 @@ "type": "tidelift" } ], - "time": "2024-03-10T05:04:21+00:00" + "time": "2024-03-13T21:04:41+00:00" }, { "name": "sabre/event", @@ -6851,6 +6851,7 @@ "type": "github" } ], + "abandoned": true, "time": "2020-09-28T06:45:17+00:00" }, { From c45e619d03489dd98d505c62a07774876cde9bc9 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 01:18:29 +0400 Subject: [PATCH 06/17] Move autoloader file inclusion to top in CSV blueprint file The order of operations in the csv-blueprint.php file was modified to include the JBZOO_AUTOLOAD_FILE at the beginning of the iteration. Previously it was included right before setting the default timezone, potentially causing issues if certain elements needed in the iteration were not loaded properly. --- csv-blueprint.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/csv-blueprint.php b/csv-blueprint.php index 99219ee0..93267875 100644 --- a/csv-blueprint.php +++ b/csv-blueprint.php @@ -34,10 +34,11 @@ } } +require_once JBZOO_AUTOLOAD_FILE; + var_dump(Cli::getNumberOfColumns()); exit(1); -require_once JBZOO_AUTOLOAD_FILE; \date_default_timezone_set('UTC'); From aaf53ad4b24e9f446512302382a6dfea44197429 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 01:44:45 +0400 Subject: [PATCH 07/17] Update table rendering in ErrorSuite and related tests The table rendering in ErrorSuite.php has been refactored to dynamically set column sizes based on terminal width rather than using fixed widths. A new method getTableSize() has been added to calculate and return the sizes. The relevant tests and documentation in README.md, have been adjusted to reflect these changes. --- Makefile | 4 +- README.md | 45 ++++++++---------- csv-blueprint.php | 5 -- phpunit.xml.dist | 4 ++ src/Validators/ErrorSuite.php | 46 ++++++++++++++++-- tests/Blueprint/ReadmeTest.php | 1 - tests/Blueprint/ValidateCsvTest.php | 74 +++++++++++++---------------- 7 files changed, 101 insertions(+), 78 deletions(-) diff --git a/Makefile b/Makefile index b41f5a70..9fa10b8a 100644 --- a/Makefile +++ b/Makefile @@ -12,11 +12,13 @@ .PHONY: build +REPORT ?= table +COLUMNS ?= 150 + ifneq (, $(wildcard ./vendor/jbzoo/codestyle/src/init.Makefile)) include ./vendor/jbzoo/codestyle/src/init.Makefile endif -REPORT ?= table build: ##@Project Install all 3rd party dependencies $(call title,"Install/Update all 3rd party dependencies") diff --git a/README.md b/README.md index ffee22dd..f4d67e89 100644 --- a/README.md +++ b/README.md @@ -211,35 +211,30 @@ Schema: ./tests/schemas/demo_invalid.yml Found CSV files: 3 (1/3) Invalid file: ./tests/fixtures/batch/demo-1.csv -+------+------------------+--------------+ demo-1.csv ------------------------------------------+ -| Line | id:Column | Rule | Message | -+------+------------------+--------------+------------------------------------------------------+ -| 3 | 2:Float | max | Value "74605.944" is greater than "74605" | -| 3 | 4:Favorite color | allow_values | Value "blue" is not allowed. Allowed values: ["red", | -| | | | "green", "Blue"] | -+------+------------------+--------------+ demo-1.csv ------------------------------------------+ ++------+------------------+--------------+--------- demo-1.csv --------------------------------------------------+ +| Line | id:Column | Rule | Message | ++------+------------------+--------------+-----------------------------------------------------------------------+ +| 3 | 2:Float | max | Value "74605.944" is greater than "74605" | +| 3 | 4:Favorite color | allow_values | Value "blue" is not allowed. Allowed values: ["red", "green", "Blue"] | ++------+------------------+--------------+--------- demo-1.csv --------------------------------------------------+ (2/3) Invalid file: ./tests/fixtures/batch/demo-2.csv -+------+------------+------------+----- demo-2.csv ---------------------------------------+ -| Line | id:Column | Rule | Message | -+------+------------+------------+--------------------------------------------------------+ -| 2 | 0:Name | min_length | Value "Carl" (length: 4) is too short. Min length is 5 | -| 2 | 3:Birthday | min_date | Value "1955-05-14" is less than the minimum date | -| | | | "1955-05-15T00:00:00.000+00:00" | -| 4 | 3:Birthday | min_date | Value "1955-05-14" is less than the minimum date | -| | | | "1955-05-15T00:00:00.000+00:00" | -| 5 | 3:Birthday | max_date | Value "2010-07-20" is more than the maximum date | -| | | | "2009-01-01T00:00:00.000+00:00" | -| 7 | 0:Name | min_length | Value "Lois" (length: 4) is too short. Min length is 5 | -+------+------------+------------+----- demo-2.csv ---------------------------------------+ ++------+------------+------------+------------------ demo-2.csv ----------------------------------------------------+ +| Line | id:Column | Rule | Message | ++------+------------+------------+----------------------------------------------------------------------------------+ +| 2 | 0:Name | min_length | Value "Carl" (length: 4) is too short. Min length is 5 | +| 2 | 3:Birthday | min_date | Value "1955-05-14" is less than the minimum date "1955-05-15T00:00:00.000+00:00" | +| 4 | 3:Birthday | min_date | Value "1955-05-14" is less than the minimum date "1955-05-15T00:00:00.000+00:00" | +| 5 | 3:Birthday | max_date | Value "2010-07-20" is more than the maximum date "2009-01-01T00:00:00.000+00:00" | +| 7 | 0:Name | min_length | Value "Lois" (length: 4) is too short. Min length is 5 | ++------+------------+------------+------------------ demo-2.csv ----------------------------------------------------+ (3/3) Invalid file: ./tests/fixtures/batch/sub/demo-3.csv -+------+-----------+------------------+---- demo-3.csv -------------------------------------------+ -| Line | id:Column | Rule | Message | -+------+-----------+------------------+-----------------------------------------------------------+ -| 0 | | filename_pattern | Filename "./tests/fixtures/batch/sub/demo-3.csv" does not | -| | | | match pattern: "/demo-[12].csv$/i" | -+------+-----------+------------------+---- demo-3.csv -------------------------------------------+ ++------+-----------+------------------+---------------------- demo-3.csv ------------------------------------------------------------+ +| Line | id:Column | Rule | Message | ++------+-----------+------------------+----------------------------------------------------------------------------------------------+ +| 0 | | filename_pattern | Filename "./tests/fixtures/batch/sub/demo-3.csv" does not match pattern: "/demo-[12].csv$/i" | ++------+-----------+------------------+---------------------- demo-3.csv ------------------------------------------------------------+ Found 8 issues in 3 out of 3 CSV files. diff --git a/csv-blueprint.php b/csv-blueprint.php index 93267875..330bce06 100644 --- a/csv-blueprint.php +++ b/csv-blueprint.php @@ -17,7 +17,6 @@ namespace JBZoo\CsvBlueprint; use JBZoo\Cli\CliApplication; -use JBZoo\Utils\Cli; \define('PATH_ROOT', __DIR__); @@ -36,10 +35,6 @@ require_once JBZOO_AUTOLOAD_FILE; -var_dump(Cli::getNumberOfColumns()); -exit(1); - - \date_default_timezone_set('UTC'); (new CliApplication('CSV Blueprint', '@git-version@')) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9ca3124e..df68b170 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -50,4 +50,8 @@ + + + + diff --git a/src/Validators/ErrorSuite.php b/src/Validators/ErrorSuite.php index 1384688c..16137d31 100644 --- a/src/Validators/ErrorSuite.php +++ b/src/Validators/ErrorSuite.php @@ -21,6 +21,9 @@ use JBZoo\CIReportConverter\Converters\JUnitConverter; use JBZoo\CIReportConverter\Converters\TeamCityTestsConverter; use JBZoo\CIReportConverter\Formats\Source\SourceSuite; +use JBZoo\Utils\Cli; +use JBZoo\Utils\Env; +use JBZoo\Utils\Vars; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Output\BufferedOutput; @@ -139,15 +142,17 @@ private function renderPlainText(): string private function renderTable(): string { + $floatingSizes = self::getTableSize(); + $buffer = new BufferedOutput(); $table = (new Table($buffer)) ->setHeaderTitle($this->getTestcaseName()) ->setFooterTitle($this->getTestcaseName()) ->setHeaders(['Line', 'id:Column', 'Rule', 'Message']) - ->setColumnMaxWidth(0, 10) - ->setColumnMaxWidth(1, 20) - ->setColumnMaxWidth(2, 20) - ->setColumnMaxWidth(3, 60); + ->setColumnMaxWidth(0, $floatingSizes['line']) + ->setColumnMaxWidth(1, $floatingSizes['column']) + ->setColumnMaxWidth(2, $floatingSizes['rule']) + ->setColumnMaxWidth(3, $floatingSizes['message']); foreach ($this->errors as $error) { $table->addRow([ @@ -182,4 +187,37 @@ private function getTestcaseName(): string { return \pathinfo((string)\realpath((string)$this->csvFilename), \PATHINFO_BASENAME); } + + /** + * Retrieves the size configuration for a table. + * + * @return int[] + */ + private static function getTableSize(): array + { + $floatingSizes = [ + 'line' => 10, + 'column' => 20, + 'rule' => 20, + 'min' => 90, + 'max' => 150, + 'reserve' => 5, // So that the table does not rest on the very edge of the terminal. Just in case. + ]; + + // Fallback to 80 if the terminal width cannot be determined. + $maxAutoDetected = Env::int('COLUMNS', Cli::getNumberOfColumns()); + + $maxWindowWidth = Vars::limit( + $maxAutoDetected, + $floatingSizes['min'], + $floatingSizes['max'], + ) - $floatingSizes['reserve']; + + $floatingSizes['message'] = $maxWindowWidth + - $floatingSizes['line'] + - $floatingSizes['column'] + - $floatingSizes['rule']; + + return $floatingSizes; + } } diff --git a/tests/Blueprint/ReadmeTest.php b/tests/Blueprint/ReadmeTest.php index f1b10cf1..9373d3dd 100644 --- a/tests/Blueprint/ReadmeTest.php +++ b/tests/Blueprint/ReadmeTest.php @@ -19,7 +19,6 @@ use JBZoo\PHPUnit\PHPUnit; use JBZoo\PHPUnit\TestTools; use JBZoo\Utils\Cli; -use JBZoo\Utils\Sys; use Symfony\Component\Console\Input\StringInput; use function JBZoo\PHPUnit\isFileContains; diff --git a/tests/Blueprint/ValidateCsvTest.php b/tests/Blueprint/ValidateCsvTest.php index a7bc0ec8..780aad04 100644 --- a/tests/Blueprint/ValidateCsvTest.php +++ b/tests/Blueprint/ValidateCsvTest.php @@ -64,23 +64,18 @@ public function testValidateOneFileNegativeTable(): void Found CSV files: 1 (1/1) Invalid file: ./tests/fixtures/demo.csv - +------+------------------+------------------+--- demo.csv -------------------------------------------------+ - | Line | id:Column | Rule | Message | - +------+------------------+------------------+--------------------------------------------------------------+ - | 0 | | filename_pattern | Filename "./tests/fixtures/demo.csv" does not match pattern: | - | | | | "/demo-[12].csv$/i" | - | 5 | 2:Float | max | Value "74605.944" is greater than "74605" | - | 5 | 4:Favorite color | allow_values | Value "blue" is not allowed. Allowed values: ["red", | - | | | | "green", "Blue"] | - | 6 | 0:Name | min_length | Value "Carl" (length: 4) is too short. Min length is 5 | - | 6 | 3:Birthday | min_date | Value "1955-05-14" is less than the minimum date | - | | | | "1955-05-15T00:00:00.000+00:00" | - | 8 | 3:Birthday | min_date | Value "1955-05-14" is less than the minimum date | - | | | | "1955-05-15T00:00:00.000+00:00" | - | 9 | 3:Birthday | max_date | Value "2010-07-20" is more than the maximum date | - | | | | "2009-01-01T00:00:00.000+00:00" | - | 11 | 0:Name | min_length | Value "Lois" (length: 4) is too short. Min length is 5 | - +------+------------------+------------------+--- demo.csv -------------------------------------------------+ + +------+------------------+------------------+------------- demo.csv -----------------------------------------------------------+ + | Line | id:Column | Rule | Message | + +------+------------------+------------------+----------------------------------------------------------------------------------+ + | 0 | | filename_pattern | Filename "./tests/fixtures/demo.csv" does not match pattern: "/demo-[12].csv$/i" | + | 5 | 2:Float | max | Value "74605.944" is greater than "74605" | + | 5 | 4:Favorite color | allow_values | Value "blue" is not allowed. Allowed values: ["red", "green", "Blue"] | + | 6 | 0:Name | min_length | Value "Carl" (length: 4) is too short. Min length is 5 | + | 6 | 3:Birthday | min_date | Value "1955-05-14" is less than the minimum date "1955-05-15T00:00:00.000+00:00" | + | 8 | 3:Birthday | min_date | Value "1955-05-14" is less than the minimum date "1955-05-15T00:00:00.000+00:00" | + | 9 | 3:Birthday | max_date | Value "2010-07-20" is more than the maximum date "2009-01-01T00:00:00.000+00:00" | + | 11 | 0:Name | min_length | Value "Lois" (length: 4) is too short. Min length is 5 | + +------+------------------+------------------+------------- demo.csv -----------------------------------------------------------+ Found 8 issues in CSV file. @@ -107,35 +102,30 @@ public function testValidateManyFileNegativeTable(): void Found CSV files: 3 (1/3) Invalid file: ./tests/fixtures/batch/demo-1.csv - +------+------------------+--------------+ demo-1.csv ------------------------------------------+ - | Line | id:Column | Rule | Message | - +------+------------------+--------------+------------------------------------------------------+ - | 3 | 2:Float | max | Value "74605.944" is greater than "74605" | - | 3 | 4:Favorite color | allow_values | Value "blue" is not allowed. Allowed values: ["red", | - | | | | "green", "Blue"] | - +------+------------------+--------------+ demo-1.csv ------------------------------------------+ + +------+------------------+--------------+--------- demo-1.csv --------------------------------------------------+ + | Line | id:Column | Rule | Message | + +------+------------------+--------------+-----------------------------------------------------------------------+ + | 3 | 2:Float | max | Value "74605.944" is greater than "74605" | + | 3 | 4:Favorite color | allow_values | Value "blue" is not allowed. Allowed values: ["red", "green", "Blue"] | + +------+------------------+--------------+--------- demo-1.csv --------------------------------------------------+ (2/3) Invalid file: ./tests/fixtures/batch/demo-2.csv - +------+------------+------------+----- demo-2.csv ---------------------------------------+ - | Line | id:Column | Rule | Message | - +------+------------+------------+--------------------------------------------------------+ - | 2 | 0:Name | min_length | Value "Carl" (length: 4) is too short. Min length is 5 | - | 2 | 3:Birthday | min_date | Value "1955-05-14" is less than the minimum date | - | | | | "1955-05-15T00:00:00.000+00:00" | - | 4 | 3:Birthday | min_date | Value "1955-05-14" is less than the minimum date | - | | | | "1955-05-15T00:00:00.000+00:00" | - | 5 | 3:Birthday | max_date | Value "2010-07-20" is more than the maximum date | - | | | | "2009-01-01T00:00:00.000+00:00" | - | 7 | 0:Name | min_length | Value "Lois" (length: 4) is too short. Min length is 5 | - +------+------------+------------+----- demo-2.csv ---------------------------------------+ + +------+------------+------------+------------------ demo-2.csv ----------------------------------------------------+ + | Line | id:Column | Rule | Message | + +------+------------+------------+----------------------------------------------------------------------------------+ + | 2 | 0:Name | min_length | Value "Carl" (length: 4) is too short. Min length is 5 | + | 2 | 3:Birthday | min_date | Value "1955-05-14" is less than the minimum date "1955-05-15T00:00:00.000+00:00" | + | 4 | 3:Birthday | min_date | Value "1955-05-14" is less than the minimum date "1955-05-15T00:00:00.000+00:00" | + | 5 | 3:Birthday | max_date | Value "2010-07-20" is more than the maximum date "2009-01-01T00:00:00.000+00:00" | + | 7 | 0:Name | min_length | Value "Lois" (length: 4) is too short. Min length is 5 | + +------+------------+------------+------------------ demo-2.csv ----------------------------------------------------+ (3/3) Invalid file: ./tests/fixtures/batch/sub/demo-3.csv - +------+-----------+------------------+---- demo-3.csv -------------------------------------------+ - | Line | id:Column | Rule | Message | - +------+-----------+------------------+-----------------------------------------------------------+ - | 0 | | filename_pattern | Filename "./tests/fixtures/batch/sub/demo-3.csv" does not | - | | | | match pattern: "/demo-[12].csv$/i" | - +------+-----------+------------------+---- demo-3.csv -------------------------------------------+ + +------+-----------+------------------+---------------------- demo-3.csv ------------------------------------------------------------+ + | Line | id:Column | Rule | Message | + +------+-----------+------------------+----------------------------------------------------------------------------------------------+ + | 0 | | filename_pattern | Filename "./tests/fixtures/batch/sub/demo-3.csv" does not match pattern: "/demo-[12].csv$/i" | + +------+-----------+------------------+---------------------- demo-3.csv ------------------------------------------------------------+ Found 8 issues in 3 out of 3 CSV files. From 42a57786b237dbdc54640ecd83c6c5b0d71ec7f4 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 01:49:27 +0400 Subject: [PATCH 08/17] Modify column size handling in ErrorSuite and tests Adjusted the column size handling in ErrorSuite.php to use 'COLUMNS_TEST' instead of 'COLUMNS' for improved testing flexibility. Also applied changes to Makefile and phpunit.xml.dist files to align with the modification made in ErrorSuite. --- Makefile | 4 ++-- phpunit.xml.dist | 2 +- src/Validators/ErrorSuite.php | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 9fa10b8a..352a8c37 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,8 @@ .PHONY: build -REPORT ?= table -COLUMNS ?= 150 +REPORT ?= table +COLUMNS_TEST ?= 150 ifneq (, $(wildcard ./vendor/jbzoo/codestyle/src/init.Makefile)) include ./vendor/jbzoo/codestyle/src/init.Makefile diff --git a/phpunit.xml.dist b/phpunit.xml.dist index df68b170..bb58f1ed 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -52,6 +52,6 @@ - + diff --git a/src/Validators/ErrorSuite.php b/src/Validators/ErrorSuite.php index 16137d31..4a625448 100644 --- a/src/Validators/ErrorSuite.php +++ b/src/Validators/ErrorSuite.php @@ -205,7 +205,8 @@ private static function getTableSize(): array ]; // Fallback to 80 if the terminal width cannot be determined. - $maxAutoDetected = Env::int('COLUMNS', Cli::getNumberOfColumns()); + // env.COLUMNS_TEST usually not defined so we use it only for testing purposes. + $maxAutoDetected = Env::int('COLUMNS_TEST', Cli::getNumberOfColumns()); $maxWindowWidth = Vars::limit( $maxAutoDetected, From a5fb85f351a819f21027553ceb6dbd550e801e02 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 02:02:05 +0400 Subject: [PATCH 09/17] Refactor Validators and Rules structure Reorganized and refactored the architecture of Validators and Rules within the codebase. The Rules were moved under the base namespace rather than under Validators. Additionally, implemented the ColumnValidator and ScvValidator, and refactored the abstract base Validator class into AbstractValidator. Adjustments were also applied to unit tests receiving the impact of these changes, ensuring comprehensive test coverage over the refactored structure. --- src/Csv/Column.php | 4 +- src/Csv/CsvFile.php | 2 + src/{Validators => }/Rules/AbstarctRule.php | 2 +- src/{Validators => }/Rules/AllowValues.php | 2 +- .../Rules/CardinalDirection.php | 2 +- src/{Validators => }/Rules/DateFormat.php | 2 +- src/{Validators => }/Rules/ExactValue.php | 2 +- src/{Validators => }/Rules/IsBool.php | 2 +- src/{Validators => }/Rules/IsDomain.php | 2 +- src/{Validators => }/Rules/IsEmail.php | 2 +- src/{Validators => }/Rules/IsFloat.php | 2 +- src/{Validators => }/Rules/IsInt.php | 2 +- src/{Validators => }/Rules/IsIp.php | 2 +- src/{Validators => }/Rules/IsLatitude.php | 2 +- src/{Validators => }/Rules/IsLongitude.php | 2 +- src/{Validators => }/Rules/IsUrl.php | 2 +- src/{Validators => }/Rules/IsUuid4.php | 2 +- src/{Validators => }/Rules/Max.php | 2 +- src/{Validators => }/Rules/MaxDate.php | 2 +- src/{Validators => }/Rules/MaxLength.php | 2 +- src/{Validators => }/Rules/Min.php | 2 +- src/{Validators => }/Rules/MinDate.php | 2 +- src/{Validators => }/Rules/MinLength.php | 2 +- src/{Validators => }/Rules/NotEmpty.php | 2 +- src/{Validators => }/Rules/OnlyCapitalize.php | 2 +- src/{Validators => }/Rules/OnlyLowercase.php | 2 +- src/{Validators => }/Rules/OnlyTrimed.php | 2 +- src/{Validators => }/Rules/OnlyUppercase.php | 2 +- src/{Validators => }/Rules/Precision.php | 2 +- src/{Validators => }/Rules/Regex.php | 2 +- src/{Validators => }/Rules/RuleException.php | 2 +- src/{Validators => }/Rules/UsaMarketName.php | 2 +- src/Validators/AbstractValidator.php | 22 ++++++++ .../{Validator.php => ColumnValidator.php} | 2 +- src/Validators/Ruleset.php | 4 +- src/Validators/ScvValidator.php | 25 +++++++++ tests/Blueprint/MiscTest.php | 2 +- tests/Blueprint/RulesTest.php | 54 +++++++++---------- tests/Blueprint/ValidatorTest.php | 2 +- 39 files changed, 113 insertions(+), 64 deletions(-) rename src/{Validators => }/Rules/AbstarctRule.php (98%) rename src/{Validators => }/Rules/AllowValues.php (95%) rename src/{Validators => }/Rules/CardinalDirection.php (94%) rename src/{Validators => }/Rules/DateFormat.php (96%) rename src/{Validators => }/Rules/ExactValue.php (94%) rename src/{Validators => }/Rules/IsBool.php (94%) rename src/{Validators => }/Rules/IsDomain.php (94%) rename src/{Validators => }/Rules/IsEmail.php (94%) rename src/{Validators => }/Rules/IsFloat.php (94%) rename src/{Validators => }/Rules/IsInt.php (94%) rename src/{Validators => }/Rules/IsIp.php (94%) rename src/{Validators => }/Rules/IsLatitude.php (95%) rename src/{Validators => }/Rules/IsLongitude.php (95%) rename src/{Validators => }/Rules/IsUrl.php (94%) rename src/{Validators => }/Rules/IsUuid4.php (94%) rename src/{Validators => }/Rules/Max.php (94%) rename src/{Validators => }/Rules/MaxDate.php (94%) rename src/{Validators => }/Rules/MaxLength.php (94%) rename src/{Validators => }/Rules/Min.php (94%) rename src/{Validators => }/Rules/MinDate.php (94%) rename src/{Validators => }/Rules/MinLength.php (94%) rename src/{Validators => }/Rules/NotEmpty.php (93%) rename src/{Validators => }/Rules/OnlyCapitalize.php (94%) rename src/{Validators => }/Rules/OnlyLowercase.php (94%) rename src/{Validators => }/Rules/OnlyTrimed.php (94%) rename src/{Validators => }/Rules/OnlyUppercase.php (94%) rename src/{Validators => }/Rules/Precision.php (96%) rename src/{Validators => }/Rules/Regex.php (95%) rename src/{Validators => }/Rules/RuleException.php (90%) rename src/{Validators => }/Rules/UsaMarketName.php (94%) create mode 100644 src/Validators/AbstractValidator.php rename src/Validators/{Validator.php => ColumnValidator.php} (93%) create mode 100644 src/Validators/ScvValidator.php diff --git a/src/Csv/Column.php b/src/Csv/Column.php index 1cc88b66..d873d784 100644 --- a/src/Csv/Column.php +++ b/src/Csv/Column.php @@ -18,7 +18,7 @@ use JBZoo\CsvBlueprint\Utils; use JBZoo\CsvBlueprint\Validators\ErrorSuite; -use JBZoo\CsvBlueprint\Validators\Validator; +use JBZoo\CsvBlueprint\Validators\ColumnValidator; use JBZoo\Data\Data; final class Column @@ -111,7 +111,7 @@ public function getInherit(): string public function validate(string $cellValue, int $line): ErrorSuite { - return (new Validator($this))->validate($cellValue, $line); + return (new ColumnValidator($this))->validate($cellValue, $line); } private function prepareRuleSet(string $schemaKey): array diff --git a/src/Csv/CsvFile.php b/src/Csv/CsvFile.php index 65d52eb1..f7cdc64f 100644 --- a/src/Csv/CsvFile.php +++ b/src/Csv/CsvFile.php @@ -81,6 +81,8 @@ public function getRecordsChunk(int $offset = 0, int $limit = -1): TabularDataRe public function validate(bool $quickStop = false): ErrorSuite { + // $fileValidator + $errors = new ErrorSuite($this->getCsvFilename()); $errors diff --git a/src/Validators/Rules/AbstarctRule.php b/src/Rules/AbstarctRule.php similarity index 98% rename from src/Validators/Rules/AbstarctRule.php rename to src/Rules/AbstarctRule.php index d97bcb05..dd827158 100644 --- a/src/Validators/Rules/AbstarctRule.php +++ b/src/Rules/AbstarctRule.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; use JBZoo\CsvBlueprint\Utils; use JBZoo\CsvBlueprint\Validators\Error; diff --git a/src/Validators/Rules/AllowValues.php b/src/Rules/AllowValues.php similarity index 95% rename from src/Validators/Rules/AllowValues.php rename to src/Rules/AllowValues.php index 11dbcf21..6541d2e5 100644 --- a/src/Validators/Rules/AllowValues.php +++ b/src/Rules/AllowValues.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; class AllowValues extends AbstarctRule { diff --git a/src/Validators/Rules/CardinalDirection.php b/src/Rules/CardinalDirection.php similarity index 94% rename from src/Validators/Rules/CardinalDirection.php rename to src/Rules/CardinalDirection.php index f1cf9be9..d27d3d15 100644 --- a/src/Validators/Rules/CardinalDirection.php +++ b/src/Rules/CardinalDirection.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; class CardinalDirection extends AllowValues { diff --git a/src/Validators/Rules/DateFormat.php b/src/Rules/DateFormat.php similarity index 96% rename from src/Validators/Rules/DateFormat.php rename to src/Rules/DateFormat.php index ec3f80da..49bdfefe 100644 --- a/src/Validators/Rules/DateFormat.php +++ b/src/Rules/DateFormat.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class DateFormat extends AbstarctRule { diff --git a/src/Validators/Rules/ExactValue.php b/src/Rules/ExactValue.php similarity index 94% rename from src/Validators/Rules/ExactValue.php rename to src/Rules/ExactValue.php index 317aa0cb..1914320a 100644 --- a/src/Validators/Rules/ExactValue.php +++ b/src/Rules/ExactValue.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class ExactValue extends AbstarctRule { diff --git a/src/Validators/Rules/IsBool.php b/src/Rules/IsBool.php similarity index 94% rename from src/Validators/Rules/IsBool.php rename to src/Rules/IsBool.php index e4260f5b..51a59dc8 100644 --- a/src/Validators/Rules/IsBool.php +++ b/src/Rules/IsBool.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class IsBool extends AllowValues { diff --git a/src/Validators/Rules/IsDomain.php b/src/Rules/IsDomain.php similarity index 94% rename from src/Validators/Rules/IsDomain.php rename to src/Rules/IsDomain.php index e815db76..ac3628e4 100644 --- a/src/Validators/Rules/IsDomain.php +++ b/src/Rules/IsDomain.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class IsDomain extends AbstarctRule { diff --git a/src/Validators/Rules/IsEmail.php b/src/Rules/IsEmail.php similarity index 94% rename from src/Validators/Rules/IsEmail.php rename to src/Rules/IsEmail.php index f26cc9dc..3d812588 100644 --- a/src/Validators/Rules/IsEmail.php +++ b/src/Rules/IsEmail.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class IsEmail extends AbstarctRule { diff --git a/src/Validators/Rules/IsFloat.php b/src/Rules/IsFloat.php similarity index 94% rename from src/Validators/Rules/IsFloat.php rename to src/Rules/IsFloat.php index 644b11aa..a4bb73e7 100644 --- a/src/Validators/Rules/IsFloat.php +++ b/src/Rules/IsFloat.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; class IsFloat extends AbstarctRule { diff --git a/src/Validators/Rules/IsInt.php b/src/Rules/IsInt.php similarity index 94% rename from src/Validators/Rules/IsInt.php rename to src/Rules/IsInt.php index 4581f04a..792369d9 100644 --- a/src/Validators/Rules/IsInt.php +++ b/src/Rules/IsInt.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class IsInt extends AbstarctRule { diff --git a/src/Validators/Rules/IsIp.php b/src/Rules/IsIp.php similarity index 94% rename from src/Validators/Rules/IsIp.php rename to src/Rules/IsIp.php index ccc5fc98..437a000c 100644 --- a/src/Validators/Rules/IsIp.php +++ b/src/Rules/IsIp.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class IsIp extends AbstarctRule { diff --git a/src/Validators/Rules/IsLatitude.php b/src/Rules/IsLatitude.php similarity index 95% rename from src/Validators/Rules/IsLatitude.php rename to src/Rules/IsLatitude.php index 7a670fb6..67ba570e 100644 --- a/src/Validators/Rules/IsLatitude.php +++ b/src/Rules/IsLatitude.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class IsLatitude extends IsFloat { diff --git a/src/Validators/Rules/IsLongitude.php b/src/Rules/IsLongitude.php similarity index 95% rename from src/Validators/Rules/IsLongitude.php rename to src/Rules/IsLongitude.php index 31f2a53b..542d8503 100644 --- a/src/Validators/Rules/IsLongitude.php +++ b/src/Rules/IsLongitude.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class IsLongitude extends IsFloat { diff --git a/src/Validators/Rules/IsUrl.php b/src/Rules/IsUrl.php similarity index 94% rename from src/Validators/Rules/IsUrl.php rename to src/Rules/IsUrl.php index f2fba082..fc072f9d 100644 --- a/src/Validators/Rules/IsUrl.php +++ b/src/Rules/IsUrl.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class IsUrl extends AbstarctRule { diff --git a/src/Validators/Rules/IsUuid4.php b/src/Rules/IsUuid4.php similarity index 94% rename from src/Validators/Rules/IsUuid4.php rename to src/Rules/IsUuid4.php index 86a5b55b..f47fd5bd 100644 --- a/src/Validators/Rules/IsUuid4.php +++ b/src/Rules/IsUuid4.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class IsUuid4 extends AbstarctRule { diff --git a/src/Validators/Rules/Max.php b/src/Rules/Max.php similarity index 94% rename from src/Validators/Rules/Max.php rename to src/Rules/Max.php index 7a0cb9bc..eb96587a 100644 --- a/src/Validators/Rules/Max.php +++ b/src/Rules/Max.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class Max extends IsFloat { diff --git a/src/Validators/Rules/MaxDate.php b/src/Rules/MaxDate.php similarity index 94% rename from src/Validators/Rules/MaxDate.php rename to src/Rules/MaxDate.php index 3dc0612f..504df30f 100644 --- a/src/Validators/Rules/MaxDate.php +++ b/src/Rules/MaxDate.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class MaxDate extends AbstarctRule { diff --git a/src/Validators/Rules/MaxLength.php b/src/Rules/MaxLength.php similarity index 94% rename from src/Validators/Rules/MaxLength.php rename to src/Rules/MaxLength.php index 515cdb74..5d18a6d8 100644 --- a/src/Validators/Rules/MaxLength.php +++ b/src/Rules/MaxLength.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class MaxLength extends AbstarctRule { diff --git a/src/Validators/Rules/Min.php b/src/Rules/Min.php similarity index 94% rename from src/Validators/Rules/Min.php rename to src/Rules/Min.php index ee340fcb..c6c2cfda 100644 --- a/src/Validators/Rules/Min.php +++ b/src/Rules/Min.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class Min extends IsFloat { diff --git a/src/Validators/Rules/MinDate.php b/src/Rules/MinDate.php similarity index 94% rename from src/Validators/Rules/MinDate.php rename to src/Rules/MinDate.php index e9435a6b..3c4ae330 100644 --- a/src/Validators/Rules/MinDate.php +++ b/src/Rules/MinDate.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class MinDate extends AbstarctRule { diff --git a/src/Validators/Rules/MinLength.php b/src/Rules/MinLength.php similarity index 94% rename from src/Validators/Rules/MinLength.php rename to src/Rules/MinLength.php index 10120131..bb7b9957 100644 --- a/src/Validators/Rules/MinLength.php +++ b/src/Rules/MinLength.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class MinLength extends AbstarctRule { diff --git a/src/Validators/Rules/NotEmpty.php b/src/Rules/NotEmpty.php similarity index 93% rename from src/Validators/Rules/NotEmpty.php rename to src/Rules/NotEmpty.php index b99339d7..6a936c8d 100644 --- a/src/Validators/Rules/NotEmpty.php +++ b/src/Rules/NotEmpty.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class NotEmpty extends AbstarctRule { diff --git a/src/Validators/Rules/OnlyCapitalize.php b/src/Rules/OnlyCapitalize.php similarity index 94% rename from src/Validators/Rules/OnlyCapitalize.php rename to src/Rules/OnlyCapitalize.php index 99b011f2..f426f3fb 100644 --- a/src/Validators/Rules/OnlyCapitalize.php +++ b/src/Rules/OnlyCapitalize.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class OnlyCapitalize extends AbstarctRule { diff --git a/src/Validators/Rules/OnlyLowercase.php b/src/Rules/OnlyLowercase.php similarity index 94% rename from src/Validators/Rules/OnlyLowercase.php rename to src/Rules/OnlyLowercase.php index 54bd838e..a5fe11e6 100644 --- a/src/Validators/Rules/OnlyLowercase.php +++ b/src/Rules/OnlyLowercase.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class OnlyLowercase extends AbstarctRule { diff --git a/src/Validators/Rules/OnlyTrimed.php b/src/Rules/OnlyTrimed.php similarity index 94% rename from src/Validators/Rules/OnlyTrimed.php rename to src/Rules/OnlyTrimed.php index 60d3b0c8..f55053b9 100644 --- a/src/Validators/Rules/OnlyTrimed.php +++ b/src/Rules/OnlyTrimed.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class OnlyTrimed extends AbstarctRule { diff --git a/src/Validators/Rules/OnlyUppercase.php b/src/Rules/OnlyUppercase.php similarity index 94% rename from src/Validators/Rules/OnlyUppercase.php rename to src/Rules/OnlyUppercase.php index 6546547e..839fe5d4 100644 --- a/src/Validators/Rules/OnlyUppercase.php +++ b/src/Rules/OnlyUppercase.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class OnlyUppercase extends AbstarctRule { diff --git a/src/Validators/Rules/Precision.php b/src/Rules/Precision.php similarity index 96% rename from src/Validators/Rules/Precision.php rename to src/Rules/Precision.php index f6106e31..df2c51af 100644 --- a/src/Validators/Rules/Precision.php +++ b/src/Rules/Precision.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; final class Precision extends AbstarctRule { diff --git a/src/Validators/Rules/Regex.php b/src/Rules/Regex.php similarity index 95% rename from src/Validators/Rules/Regex.php rename to src/Rules/Regex.php index 356cbdb1..95e2be6e 100644 --- a/src/Validators/Rules/Regex.php +++ b/src/Rules/Regex.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; use JBZoo\CsvBlueprint\Utils; diff --git a/src/Validators/Rules/RuleException.php b/src/Rules/RuleException.php similarity index 90% rename from src/Validators/Rules/RuleException.php rename to src/Rules/RuleException.php index 48ed9cb6..d0e06b41 100644 --- a/src/Validators/Rules/RuleException.php +++ b/src/Rules/RuleException.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; class RuleException extends \JBZoo\CsvBlueprint\Exception { diff --git a/src/Validators/Rules/UsaMarketName.php b/src/Rules/UsaMarketName.php similarity index 94% rename from src/Validators/Rules/UsaMarketName.php rename to src/Rules/UsaMarketName.php index 0979b6ea..d381d25c 100644 --- a/src/Validators/Rules/UsaMarketName.php +++ b/src/Rules/UsaMarketName.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\CsvBlueprint\Validators\Rules; +namespace JBZoo\CsvBlueprint\Rules; class UsaMarketName extends AllowValues { diff --git a/src/Validators/AbstractValidator.php b/src/Validators/AbstractValidator.php new file mode 100644 index 00000000..de339246 --- /dev/null +++ b/src/Validators/AbstractValidator.php @@ -0,0 +1,22 @@ +columnNameId, $options); diff --git a/src/Validators/ScvValidator.php b/src/Validators/ScvValidator.php new file mode 100644 index 00000000..7bdc0011 --- /dev/null +++ b/src/Validators/ScvValidator.php @@ -0,0 +1,25 @@ +files() - ->in(PROJECT_ROOT . '/src/Validators/Rules') + ->in(PROJECT_ROOT . '/src/Rules') ->ignoreDotFiles(false) ->ignoreVCS(true) ->name('/\\.php$/'); diff --git a/tests/Blueprint/RulesTest.php b/tests/Blueprint/RulesTest.php index 69f97883..cad32907 100644 --- a/tests/Blueprint/RulesTest.php +++ b/tests/Blueprint/RulesTest.php @@ -16,33 +16,33 @@ namespace JBZoo\PHPUnit\Blueprint; -use JBZoo\CsvBlueprint\Validators\Rules\AllowValues; -use JBZoo\CsvBlueprint\Validators\Rules\CardinalDirection; -use JBZoo\CsvBlueprint\Validators\Rules\DateFormat; -use JBZoo\CsvBlueprint\Validators\Rules\ExactValue; -use JBZoo\CsvBlueprint\Validators\Rules\IsBool; -use JBZoo\CsvBlueprint\Validators\Rules\IsDomain; -use JBZoo\CsvBlueprint\Validators\Rules\IsEmail; -use JBZoo\CsvBlueprint\Validators\Rules\IsFloat; -use JBZoo\CsvBlueprint\Validators\Rules\IsInt; -use JBZoo\CsvBlueprint\Validators\Rules\IsIp; -use JBZoo\CsvBlueprint\Validators\Rules\IsLatitude; -use JBZoo\CsvBlueprint\Validators\Rules\IsLongitude; -use JBZoo\CsvBlueprint\Validators\Rules\IsUrl; -use JBZoo\CsvBlueprint\Validators\Rules\IsUuid4; -use JBZoo\CsvBlueprint\Validators\Rules\Max; -use JBZoo\CsvBlueprint\Validators\Rules\MaxDate; -use JBZoo\CsvBlueprint\Validators\Rules\MaxLength; -use JBZoo\CsvBlueprint\Validators\Rules\Min; -use JBZoo\CsvBlueprint\Validators\Rules\MinDate; -use JBZoo\CsvBlueprint\Validators\Rules\MinLength; -use JBZoo\CsvBlueprint\Validators\Rules\NotEmpty; -use JBZoo\CsvBlueprint\Validators\Rules\OnlyCapitalize; -use JBZoo\CsvBlueprint\Validators\Rules\OnlyLowercase; -use JBZoo\CsvBlueprint\Validators\Rules\OnlyUppercase; -use JBZoo\CsvBlueprint\Validators\Rules\Precision; -use JBZoo\CsvBlueprint\Validators\Rules\Regex; -use JBZoo\CsvBlueprint\Validators\Rules\UsaMarketName; +use JBZoo\CsvBlueprint\Rules\AllowValues; +use JBZoo\CsvBlueprint\Rules\CardinalDirection; +use JBZoo\CsvBlueprint\Rules\DateFormat; +use JBZoo\CsvBlueprint\Rules\ExactValue; +use JBZoo\CsvBlueprint\Rules\IsBool; +use JBZoo\CsvBlueprint\Rules\IsDomain; +use JBZoo\CsvBlueprint\Rules\IsEmail; +use JBZoo\CsvBlueprint\Rules\IsFloat; +use JBZoo\CsvBlueprint\Rules\IsInt; +use JBZoo\CsvBlueprint\Rules\IsIp; +use JBZoo\CsvBlueprint\Rules\IsLatitude; +use JBZoo\CsvBlueprint\Rules\IsLongitude; +use JBZoo\CsvBlueprint\Rules\IsUrl; +use JBZoo\CsvBlueprint\Rules\IsUuid4; +use JBZoo\CsvBlueprint\Rules\Max; +use JBZoo\CsvBlueprint\Rules\MaxDate; +use JBZoo\CsvBlueprint\Rules\MaxLength; +use JBZoo\CsvBlueprint\Rules\Min; +use JBZoo\CsvBlueprint\Rules\MinDate; +use JBZoo\CsvBlueprint\Rules\MinLength; +use JBZoo\CsvBlueprint\Rules\NotEmpty; +use JBZoo\CsvBlueprint\Rules\OnlyCapitalize; +use JBZoo\CsvBlueprint\Rules\OnlyLowercase; +use JBZoo\CsvBlueprint\Rules\OnlyUppercase; +use JBZoo\CsvBlueprint\Rules\Precision; +use JBZoo\CsvBlueprint\Rules\Regex; +use JBZoo\CsvBlueprint\Rules\UsaMarketName; use JBZoo\PHPUnit\PHPUnit; use JBZoo\Utils\Str; diff --git a/tests/Blueprint/ValidatorTest.php b/tests/Blueprint/ValidatorTest.php index 4c6a0ffa..e57f125e 100644 --- a/tests/Blueprint/ValidatorTest.php +++ b/tests/Blueprint/ValidatorTest.php @@ -43,7 +43,7 @@ protected function setUp(): void public function testUndefinedRule(): void { $this->expectExceptionMessage( - 'Rule "undefined_rule" not found. Expected class: "JBZoo\CsvBlueprint\Validators\Rules\UndefinedRule"', + 'Rule "undefined_rule" not found. Expected class: "\JBZoo\CsvBlueprint\Rules\UndefinedRule"', ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'undefined_rule', true)); $csv->validate(); From e35d8a529ca11dc973312315b1b57644e34c34a3 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 02:12:40 +0400 Subject: [PATCH 10/17] Refactor major parts of the validation and rules structure for easier maintainability and clarity. The validation code in `CsvFile` has been refactored to a new class, `CsvValidator`, for better encapsulation and readability. The use statements in `CsvFile` and `Column` classes have been updated to reflect these changes. The `AbstractValidator` has been removed for simplicity, hence, `ColumnValidator` and `CsvValidator` classes no longer extend it. Furthermore, a typo in the `CsvValidator` filename was corrected. The commit aims to make the validation process easier to maintain and understand. --- src/Csv/Column.php | 2 +- src/Csv/CsvFile.php | 104 +-------------------- src/Validators/AbstractValidator.php | 22 ----- src/Validators/ColumnValidator.php | 2 +- src/Validators/CsvValidator.php | 133 +++++++++++++++++++++++++++ src/Validators/ScvValidator.php | 25 ----- 6 files changed, 137 insertions(+), 151 deletions(-) delete mode 100644 src/Validators/AbstractValidator.php create mode 100644 src/Validators/CsvValidator.php delete mode 100644 src/Validators/ScvValidator.php diff --git a/src/Csv/Column.php b/src/Csv/Column.php index d873d784..3dc820a8 100644 --- a/src/Csv/Column.php +++ b/src/Csv/Column.php @@ -17,8 +17,8 @@ namespace JBZoo\CsvBlueprint\Csv; use JBZoo\CsvBlueprint\Utils; -use JBZoo\CsvBlueprint\Validators\ErrorSuite; use JBZoo\CsvBlueprint\Validators\ColumnValidator; +use JBZoo\CsvBlueprint\Validators\ErrorSuite; use JBZoo\Data\Data; final class Column diff --git a/src/Csv/CsvFile.php b/src/Csv/CsvFile.php index f7cdc64f..543ce522 100644 --- a/src/Csv/CsvFile.php +++ b/src/Csv/CsvFile.php @@ -17,8 +17,7 @@ namespace JBZoo\CsvBlueprint\Csv; use JBZoo\CsvBlueprint\Schema; -use JBZoo\CsvBlueprint\Utils; -use JBZoo\CsvBlueprint\Validators\Error; +use JBZoo\CsvBlueprint\Validators\CsvValidator; use JBZoo\CsvBlueprint\Validators\ErrorSuite; use League\Csv\Reader as LeagueReader; use League\Csv\Statement; @@ -81,17 +80,7 @@ public function getRecordsChunk(int $offset = 0, int $limit = -1): TabularDataRe public function validate(bool $quickStop = false): ErrorSuite { - // $fileValidator - - $errors = new ErrorSuite($this->getCsvFilename()); - - $errors - ->addErrorSuit($this->validateFile($quickStop)) - ->addErrorSuit($this->validateHeader($quickStop)) - ->addErrorSuit($this->validateEachCell($quickStop)) - ->addErrorSuit(self::validateAggregateRules($quickStop)); - - return $errors; + return (new CsvValidator($this, $this->schema))->validate($quickStop); } private function prepareReader(): LeagueReader @@ -110,93 +99,4 @@ private function prepareReader(): LeagueReader return $reader; } - - private function validateHeader(bool $quickStop = false): ErrorSuite - { - $errors = new ErrorSuite(); - - if (!$this->getCsvStructure()->isHeader()) { - return $errors; - } - - foreach ($this->schema->getColumns() as $column) { - if ($column->getName() === '') { - $error = new Error( - 'csv.header', - "Property \"name\" is not defined in schema: \"{$this->schema->getFilename()}\"", - $column->getHumanName(), - 1, - ); - - $errors->addError($error); - } - - if ($quickStop && $errors->count() > 0) { - return $errors; - } - } - - return $errors; - } - - private function validateEachCell(bool $quickStop = false): ErrorSuite - { - $errors = new ErrorSuite(); - - foreach ($this->getRecords() as $line => $record) { - $columns = $this->schema->getColumnsMappedByHeader($this->getHeader()); - - foreach ($columns as $column) { - if ($column === null) { - continue; - } - - $errors->addErrorSuit($column->validate($record[$column->getKey()], (int)$line + 1)); - if ($quickStop && $errors->count() > 0) { - return $errors; - } - } - } - - return $errors; - } - - private function validateFile(bool $quickStop = false): ErrorSuite - { - $errors = new ErrorSuite(); - - $filenamePattern = $this->schema->getFilenamePattern(); - if ( - $filenamePattern !== null - && $filenamePattern !== '' - && \preg_match($filenamePattern, $this->csvFilename) === 0 - ) { - $error = new Error( - 'filename_pattern', - 'Filename "' . Utils::cutPath($this->csvFilename) . - "\" does not match pattern: \"{$filenamePattern}\"", - '', - 0, - ); - - $errors->addError($error); - - if ($quickStop && $errors->count() > 0) { - return $errors; - } - } - - return $errors; - } - - private static function validateAggregateRules(bool $quickStop = false): ErrorSuite - { - $errors = new ErrorSuite(); - - if ($quickStop && $errors->count() > 0) { - return $errors; - } - - return new ErrorSuite(); - } } diff --git a/src/Validators/AbstractValidator.php b/src/Validators/AbstractValidator.php deleted file mode 100644 index de339246..00000000 --- a/src/Validators/AbstractValidator.php +++ /dev/null @@ -1,22 +0,0 @@ -csv = $csv; + $this->schema = $schema; + $this->errors = new ErrorSuite($this->csv->getCsvFilename()); + } + + public function validate(bool $quickStop = false): ErrorSuite + { + return $this->errors + ->addErrorSuit($this->validateFile($quickStop)) + ->addErrorSuit($this->validateHeader($quickStop)) + ->addErrorSuit($this->validateEachCell($quickStop)) + ->addErrorSuit(self::validateAggregateRules($quickStop)); + } + + private function validateHeader(bool $quickStop = false): ErrorSuite + { + $errors = new ErrorSuite(); + + if (!$this->schema->getCsvStructure()->isHeader()) { + return $errors; + } + + foreach ($this->schema->getColumns() as $column) { + if ($column->getName() === '') { + $error = new Error( + 'csv.header', + "Property \"name\" is not defined in schema: \"{$this->schema->getFilename()}\"", + $column->getHumanName(), + 1, + ); + + $errors->addError($error); + } + + if ($quickStop && $errors->count() > 0) { + return $errors; + } + } + + return $errors; + } + + private function validateEachCell(bool $quickStop = false): ErrorSuite + { + $errors = new ErrorSuite(); + + foreach ($this->csv->getRecords() as $line => $record) { + $columns = $this->schema->getColumnsMappedByHeader($this->csv->getHeader()); + + foreach ($columns as $column) { + if ($column === null) { + continue; + } + + $errors->addErrorSuit($column->validate($record[$column->getKey()], (int)$line + 1)); + if ($quickStop && $errors->count() > 0) { + return $errors; + } + } + } + + return $errors; + } + + private function validateFile(bool $quickStop = false): ErrorSuite + { + $errors = new ErrorSuite(); + + $filenamePattern = $this->schema->getFilenamePattern(); + if ( + $filenamePattern !== null + && $filenamePattern !== '' + && \preg_match($filenamePattern, $this->csv->getCsvFilename()) === 0 + ) { + $error = new Error( + 'filename_pattern', + 'Filename "' . Utils::cutPath($this->csv->getCsvFilename()) . + "\" does not match pattern: \"{$filenamePattern}\"", + '', + 0, + ); + + $errors->addError($error); + + if ($quickStop && $errors->count() > 0) { + return $errors; + } + } + + return $errors; + } + + private static function validateAggregateRules(bool $quickStop = false): ErrorSuite + { + $errors = new ErrorSuite(); + + if ($quickStop && $errors->count() > 0) { + return $errors; + } + + return new ErrorSuite(); + } +} diff --git a/src/Validators/ScvValidator.php b/src/Validators/ScvValidator.php deleted file mode 100644 index 7bdc0011..00000000 --- a/src/Validators/ScvValidator.php +++ /dev/null @@ -1,25 +0,0 @@ - Date: Thu, 14 Mar 2024 02:17:37 +0400 Subject: [PATCH 11/17] Fixes --- src/Rules/CardinalDirection.php | 2 +- src/Rules/UsaMarketName.php | 2 +- src/Validators/CsvValidator.php | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Rules/CardinalDirection.php b/src/Rules/CardinalDirection.php index d27d3d15..751dffe6 100644 --- a/src/Rules/CardinalDirection.php +++ b/src/Rules/CardinalDirection.php @@ -16,7 +16,7 @@ namespace JBZoo\CsvBlueprint\Rules; -class CardinalDirection extends AllowValues +final class CardinalDirection extends AllowValues { public function validateRule(?string $cellValue): ?string { diff --git a/src/Rules/UsaMarketName.php b/src/Rules/UsaMarketName.php index d381d25c..786f61c8 100644 --- a/src/Rules/UsaMarketName.php +++ b/src/Rules/UsaMarketName.php @@ -16,7 +16,7 @@ namespace JBZoo\CsvBlueprint\Rules; -class UsaMarketName extends AllowValues +final class UsaMarketName extends AllowValues { public function validateRule(?string $cellValue): ?string { diff --git a/src/Validators/CsvValidator.php b/src/Validators/CsvValidator.php index 7099ff6e..8169b3bc 100644 --- a/src/Validators/CsvValidator.php +++ b/src/Validators/CsvValidator.php @@ -54,7 +54,8 @@ private function validateHeader(bool $quickStop = false): ErrorSuite if ($column->getName() === '') { $error = new Error( 'csv.header', - "Property \"name\" is not defined in schema: \"{$this->schema->getFilename()}\"", + 'Property "name" is not defined in schema: ' . + "\"{$this->schema->getFilename()}\"", $column->getHumanName(), 1, ); From 436bb473edceb6eaed66a0e43f7d165417273aee Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 02:40:43 +0400 Subject: [PATCH 12/17] Implement min and max precision rules Added new rules for minimum and maximum precision in the schema files and created corresponding validation logic in the PHP files. Updated test files to include tests for these new rules. Enhanced the precision functionality in the schema files and reflected these changes in the README for clarity. --- README.md | 12 ++++-- schema-examples/full.json | 4 +- schema-examples/full.php | 4 +- schema-examples/full.yml | 4 +- src/Rules/MaxPrecision.php | 32 ++++++++++++++++ src/Rules/MinPrecision.php | 32 ++++++++++++++++ src/Rules/Precision.php | 6 +-- tests/Blueprint/RulesTest.php | 71 +++++++++++++++++++++++++++++++++++ 8 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 src/Rules/MaxPrecision.php create mode 100644 src/Rules/MinPrecision.php diff --git a/README.md b/README.md index f4d67e89..559c68da 100644 --- a/README.md +++ b/README.md @@ -327,7 +327,9 @@ columns: # Decimal and integer numbers min: 10 # Can be integer or float, negative and positive max: 100.50 # Can be integer or float, negative and positive - precision: 2 # Strict(!) number of digits after the decimal point + precision: 3 # Strict(!) number of digits after the decimal point + min_precision: 2 # Min number of digits after the decimal point + max_precision: 4 # Max number of digits after the decimal point # Dates date_format: Y-m-d # See: https://www.php.net/manual/en/datetime.format.php @@ -384,7 +386,9 @@ columns: "only_capitalize" : true, "min" : 10, "max" : 100.5, - "precision" : 2, + "precision" : 3, + "min_precision" : 2, + "max_precision" : 4, "date_format" : "Y-m-d", "min_date" : "2000-01-02", "max_date" : "+1 day", @@ -449,7 +453,9 @@ return [ 'only_capitalize' => true, 'min' => 10, 'max' => 100.5, - 'precision' => 2, + 'precision' => 3, + 'min_precision' => 2, + 'max_precision' => 4, 'date_format' => 'Y-m-d', 'min_date' => '2000-01-02', 'max_date' => '+1 day', diff --git a/schema-examples/full.json b/schema-examples/full.json index 17131304..6897256e 100644 --- a/schema-examples/full.json +++ b/schema-examples/full.json @@ -25,7 +25,9 @@ "only_capitalize" : true, "min" : 10, "max" : 100.5, - "precision" : 2, + "precision" : 3, + "min_precision" : 2, + "max_precision" : 4, "date_format" : "Y-m-d", "min_date" : "2000-01-02", "max_date" : "+1 day", diff --git a/schema-examples/full.php b/schema-examples/full.php index 66c7542a..e80e81ad 100644 --- a/schema-examples/full.php +++ b/schema-examples/full.php @@ -43,7 +43,9 @@ 'only_capitalize' => true, 'min' => 10, 'max' => 100.5, - 'precision' => 2, + 'precision' => 3, + 'min_precision' => 2, + 'max_precision' => 4, 'date_format' => 'Y-m-d', 'min_date' => '2000-01-02', 'max_date' => '+1 day', diff --git a/schema-examples/full.yml b/schema-examples/full.yml index 98036c1c..73ef9fe1 100644 --- a/schema-examples/full.yml +++ b/schema-examples/full.yml @@ -51,7 +51,9 @@ columns: # Decimal and integer numbers min: 10 # Can be integer or float, negative and positive max: 100.50 # Can be integer or float, negative and positive - precision: 2 # Strict(!) number of digits after the decimal point + precision: 3 # Strict(!) number of digits after the decimal point + min_precision: 2 # Min number of digits after the decimal point + max_precision: 4 # Max number of digits after the decimal point # Dates date_format: Y-m-d # See: https://www.php.net/manual/en/datetime.format.php diff --git a/src/Rules/MaxPrecision.php b/src/Rules/MaxPrecision.php new file mode 100644 index 00000000..35e8a65c --- /dev/null +++ b/src/Rules/MaxPrecision.php @@ -0,0 +1,32 @@ + $this->getOptionAsInt()) { + return "Value \"{$cellValue}\" has a precision of {$valuePrecision} " . + "but should have a max precision of {$this->getOptionAsInt()}"; + } + + return null; + } +} diff --git a/src/Rules/MinPrecision.php b/src/Rules/MinPrecision.php new file mode 100644 index 00000000..8af2d12d --- /dev/null +++ b/src/Rules/MinPrecision.php @@ -0,0 +1,32 @@ +getOptionAsInt()) { + return "Value \"{$cellValue}\" has a precision of {$valuePrecision} " . + "but should have a min precision of {$this->getOptionAsInt()}"; + } + + return null; + } +} diff --git a/src/Rules/Precision.php b/src/Rules/Precision.php index df2c51af..25b6b816 100644 --- a/src/Rules/Precision.php +++ b/src/Rules/Precision.php @@ -16,13 +16,13 @@ namespace JBZoo\CsvBlueprint\Rules; -final class Precision extends AbstarctRule +class Precision extends AbstarctRule { public function validateRule(?string $cellValue): ?string { $valuePrecision = self::getFloatPrecision($cellValue); - if ($this->getOptionAsInt() !== $valuePrecision) { + if ($valuePrecision !== $this->getOptionAsInt()) { return "Value \"{$cellValue}\" has a precision of {$valuePrecision} " . "but should have a precision of {$this->getOptionAsInt()}"; } @@ -30,7 +30,7 @@ public function validateRule(?string $cellValue): ?string return null; } - private static function getFloatPrecision(?string $cellValue): int + protected static function getFloatPrecision(?string $cellValue): int { $floatAsString = (string)$cellValue; $dotPosition = \strpos($floatAsString, '.'); diff --git a/tests/Blueprint/RulesTest.php b/tests/Blueprint/RulesTest.php index cad32907..bd3a12da 100644 --- a/tests/Blueprint/RulesTest.php +++ b/tests/Blueprint/RulesTest.php @@ -33,9 +33,11 @@ use JBZoo\CsvBlueprint\Rules\Max; use JBZoo\CsvBlueprint\Rules\MaxDate; use JBZoo\CsvBlueprint\Rules\MaxLength; +use JBZoo\CsvBlueprint\Rules\MaxPrecision; use JBZoo\CsvBlueprint\Rules\Min; use JBZoo\CsvBlueprint\Rules\MinDate; use JBZoo\CsvBlueprint\Rules\MinLength; +use JBZoo\CsvBlueprint\Rules\MinPrecision; use JBZoo\CsvBlueprint\Rules\NotEmpty; use JBZoo\CsvBlueprint\Rules\OnlyCapitalize; use JBZoo\CsvBlueprint\Rules\OnlyLowercase; @@ -575,6 +577,75 @@ public function testPrecision(): void ); } + public function testMinPrecision(): void + { + $rule = new MinPrecision('prop', 0); + isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('0.0')); + isSame(null, $rule->validate('0.1')); + isSame(null, $rule->validate('-1.0')); + isSame(null, $rule->validate('10.01')); + isSame(null, $rule->validate('-10.0001')); + + $rule = new MinPrecision('prop', 1); + isSame(null, $rule->validate('0.0')); + isSame(null, $rule->validate('10.0')); + isSame(null, $rule->validate('-10.0')); + + isSame( + '"min_precision" at line 0, column "prop". ' . + 'Value "2" has a precision of 0 but should have a min precision of 1.', + \strip_tags((string)$rule->validate('2')), + ); + + $rule = new MinPrecision('prop', 2); + isSame(null, $rule->validate('10.01')); + isSame(null, $rule->validate('-10.0001')); + + isSame( + '"min_precision" at line 0, column "prop". ' . + 'Value "2" has a precision of 0 but should have a min precision of 2.', + \strip_tags((string)$rule->validate('2')), + ); + + isSame( + '"min_precision" at line 0, column "prop". ' . + 'Value "2.0" has a precision of 1 but should have a min precision of 2.', + \strip_tags((string)$rule->validate('2.0')), + ); + } + + public function testMaxPrecision(): void + { + $rule = new MaxPrecision('prop', 0); + isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('10')); + isSame(null, $rule->validate('-10')); + + isSame( + '"max_precision" at line 0, column "prop". ' . + 'Value "2.0" has a precision of 1 but should have a max precision of 0.', + \strip_tags((string)$rule->validate('2.0')), + ); + + $rule = new MaxPrecision('prop', 1); + isSame(null, $rule->validate('0.0')); + isSame(null, $rule->validate('10.0')); + isSame(null, $rule->validate('-10.0')); + + isSame( + '"max_precision" at line 0, column "prop". ' . + 'Value "-2.003" has a precision of 3 but should have a max precision of 1.', + \strip_tags((string)$rule->validate('-2.003')), + ); + + isSame( + '"max_precision" at line 0, column "prop". ' . + 'Value "2.00000" has a precision of 5 but should have a max precision of 1.', + \strip_tags((string)$rule->validate('2.00000')), + ); + } + public function testRegex(): void { $rule = new Regex('prop', '/^a/'); From 1ec2368975ae208e7982a136f4cf5976d8cdeaed Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 02:42:28 +0400 Subject: [PATCH 13/17] Update precision description in schema and README Clarified precision rules in both full schema and README file by adding a note about inclusion of zeroes in minimum and maximum precision calculations. This will ensure the users understand this particular aspect when dealing with precision logic. --- README.md | 4 ++-- schema-examples/full.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 559c68da..4a984806 100644 --- a/README.md +++ b/README.md @@ -328,8 +328,8 @@ columns: min: 10 # Can be integer or float, negative and positive max: 100.50 # Can be integer or float, negative and positive precision: 3 # Strict(!) number of digits after the decimal point - min_precision: 2 # Min number of digits after the decimal point - max_precision: 4 # Max number of digits after the decimal point + min_precision: 2 # Min number of digits after the decimal point (with zeros) + max_precision: 4 # Max number of digits after the decimal point (with zeros) # Dates date_format: Y-m-d # See: https://www.php.net/manual/en/datetime.format.php diff --git a/schema-examples/full.yml b/schema-examples/full.yml index 73ef9fe1..cc69b868 100644 --- a/schema-examples/full.yml +++ b/schema-examples/full.yml @@ -52,8 +52,8 @@ columns: min: 10 # Can be integer or float, negative and positive max: 100.50 # Can be integer or float, negative and positive precision: 3 # Strict(!) number of digits after the decimal point - min_precision: 2 # Min number of digits after the decimal point - max_precision: 4 # Max number of digits after the decimal point + min_precision: 2 # Min number of digits after the decimal point (with zeros) + max_precision: 4 # Max number of digits after the decimal point (with zeros) # Dates date_format: Y-m-d # See: https://www.php.net/manual/en/datetime.format.php From 5f4c4ac9932cbea7ef3901d48aec72e92c4b3a18 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 03:36:10 +0400 Subject: [PATCH 14/17] Add new string and word count validation rules Introduced new validation rules related to string content and word count. The changes include rules for word counting, string beginning and ending content, and required content presence in strings. These modifications expand the ability of the library to perform more intricate validations, aiding in ensuring data consistency and accuracy in CSV files. --- README.md | 139 +++------------------------------- schema-examples/full.json | 7 ++ schema-examples/full.php | 7 ++ schema-examples/full.yml | 8 ++ src/Rules/AllMustContain.php | 37 +++++++++ src/Rules/AtLeastContains.php | 37 +++++++++ src/Rules/MaxWordCount.php | 33 ++++++++ src/Rules/MinWordCount.php | 33 ++++++++ src/Rules/StrEndsWith.php | 34 +++++++++ src/Rules/StrStartsWith.php | 34 +++++++++ src/Rules/WordCount.php | 33 ++++++++ tests/Blueprint/MiscTest.php | 30 +++++--- tests/Blueprint/RulesTest.php | 139 ++++++++++++++++++++++++++++++++++ 13 files changed, 431 insertions(+), 140 deletions(-) create mode 100644 src/Rules/AllMustContain.php create mode 100644 src/Rules/AtLeastContains.php create mode 100644 src/Rules/MaxWordCount.php create mode 100644 src/Rules/MinWordCount.php create mode 100644 src/Rules/StrEndsWith.php create mode 100644 src/Rules/StrStartsWith.php create mode 100644 src/Rules/WordCount.php diff --git a/README.md b/README.md index 4a984806..0ff73c7c 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,8 @@ This gives you great flexibility when validating CSV files. ### Schema file examples +Available formats: [YAML](schema-examples/full.yml), [JSON](schema-examples/full.json), [PHP](schema-examples/full.php). + ```yml # It's a full example of the CSV schema file in YAML format. @@ -323,6 +325,14 @@ columns: only_lowercase: true # String is only lower-case. Example: "hello world" only_uppercase: true # String is only upper-case. Example: "HELLO WORLD" only_capitalize: true # String is only capitalized. Example: "Hello World" + word_count: 10 # Integer only. Exact count of words in the string. Example: "Hello World, 123" - 2 words only (123 is not a word) + min_word_count: 1 # Integer only. Min count of words in the string. Example: "Hello World. 123" - 2 words only (123 is not a word) + max_word_count: 5 # Integer only. Max count of words in the string Example: "Hello World! 123" - 2 words only (123 is not a word) + at_least_contains: [ a, b ] # At least one of the string must be in the CSV value. Case-sensitive. + all_must_contain: [ a, b, c ] # All the strings must be part of a CSV value. Case-sensitive. + str_ends_with: " suffix" # Case-sensitive. Example: "Hello World suffix" + str_starts_with: "prefix " # Case-sensitive. Example: "prefix Hello World" + # Decimal and integer numbers min: 10 # Can be integer or float, negative and positive @@ -355,134 +365,6 @@ columns: ``` -
- Click to see: JSON Format - -```json -{ - "filename_pattern" : "/demo(-\\d+)?\\.csv$/i", - "csv" : { - "header" : true, - "delimiter" : ",", - "quote_char" : "\\", - "enclosure" : "\"", - "encoding" : "utf-8", - "bom" : false - }, - "columns" : [ - { - "name" : "csv_header_name", - "description" : "Lorem ipsum", - "rules" : { - "not_empty" : true, - "exact_value" : "Some string", - "allow_values" : ["y", "n", ""], - "regex" : "\/^[\\d]{2}$\/", - "min_length" : 1, - "max_length" : 10, - "only_trimed" : true, - "only_lowercase" : true, - "only_uppercase" : true, - "only_capitalize" : true, - "min" : 10, - "max" : 100.5, - "precision" : 3, - "min_precision" : 2, - "max_precision" : 4, - "date_format" : "Y-m-d", - "min_date" : "2000-01-02", - "max_date" : "+1 day", - "is_bool" : true, - "is_int" : true, - "is_float" : true, - "is_ip" : true, - "is_url" : true, - "is_email" : true, - "is_domain" : true, - "is_uuid4" : true, - "is_latitude" : true, - "is_longitude" : true, - "cardinal_direction" : true, - "usa_market_name" : true - } - }, - {"name" : "another_column"} - ] -} - -``` - -
- - - - -
- Click to see: PHP Format - -```php - '/demo(-\\d+)?\\.csv$/i', - - 'csv' => [ - 'header' => true, - 'delimiter' => ',', - 'quote_char' => '\\', - 'enclosure' => '"', - 'encoding' => 'utf-8', - 'bom' => false, - ], - - 'columns' => [ - [ - 'name' => 'csv_header_name', - 'description' => 'Lorem ipsum', - 'rules' => [ - 'not_empty' => true, - 'exact_value' => 'Some string', - 'allow_values' => ['y', 'n', ''], - 'regex' => '/^[\\d]{2}$/', - 'min_length' => 1, - 'max_length' => 10, - 'only_trimed' => true, - 'only_lowercase' => true, - 'only_uppercase' => true, - 'only_capitalize' => true, - 'min' => 10, - 'max' => 100.5, - 'precision' => 3, - 'min_precision' => 2, - 'max_precision' => 4, - 'date_format' => 'Y-m-d', - 'min_date' => '2000-01-02', - 'max_date' => '+1 day', - 'is_bool' => true, - 'is_int' => true, - 'is_float' => true, - 'is_ip' => true, - 'is_url' => true, - 'is_email' => true, - 'is_domain' => true, - 'is_uuid4' => true, - 'is_latitude' => true, - 'is_longitude' => true, - 'cardinal_direction' => true, - 'usa_market_name' => true, - ], - ], - ['name' => 'another_column'], - ], -]; - -``` - -
- - - ## Coming soon It's random ideas and plans. No orderings and deadlines. But batch processing is the priority #1. @@ -498,6 +380,7 @@ Batch processing Validation * [x] ~~`filename_pattern` validation with regex (like "all files in the folder should be in the format `/^[\d]{4}-[\d]{2}-[\d]{2}\.csv$/`").~~ * [ ] Flag to ignore file name pattern. It's useful when you have a lot of files and you don't want to validate the file name. +* [ ] Keyword for null value. Configurable. By default, it's an empty string. But you can use `null`, `nil`, `none`, `empty`, etc. * [ ] Agregate rules (like "at least one of the fields should be not empty" or "all values must be unique"). * [ ] Handle empty files and files with only a header row, or only with one line of data. One column wthout header is also possible. * [ ] Using multiple schemas for one csv file. diff --git a/schema-examples/full.json b/schema-examples/full.json index 6897256e..0f186c76 100644 --- a/schema-examples/full.json +++ b/schema-examples/full.json @@ -23,6 +23,13 @@ "only_lowercase" : true, "only_uppercase" : true, "only_capitalize" : true, + "word_count" : 10, + "min_word_count" : 1, + "max_word_count" : 5, + "at_least_contains" : ["a", "b"], + "all_must_contain" : ["a", "b", "c"], + "str_ends_with" : " suffix", + "str_starts_with" : "prefix ", "min" : 10, "max" : 100.5, "precision" : 3, diff --git a/schema-examples/full.php b/schema-examples/full.php index e80e81ad..2019eaea 100644 --- a/schema-examples/full.php +++ b/schema-examples/full.php @@ -41,6 +41,13 @@ 'only_lowercase' => true, 'only_uppercase' => true, 'only_capitalize' => true, + 'word_count' => 10, + 'min_word_count' => 1, + 'max_word_count' => 5, + 'at_least_contains' => ['a', 'b'], + 'all_must_contain' => ['a', 'b', 'c'], + 'str_ends_with' => ' suffix', + 'str_starts_with' => 'prefix ', 'min' => 10, 'max' => 100.5, 'precision' => 3, diff --git a/schema-examples/full.yml b/schema-examples/full.yml index cc69b868..d15adfb1 100644 --- a/schema-examples/full.yml +++ b/schema-examples/full.yml @@ -47,6 +47,14 @@ columns: only_lowercase: true # String is only lower-case. Example: "hello world" only_uppercase: true # String is only upper-case. Example: "HELLO WORLD" only_capitalize: true # String is only capitalized. Example: "Hello World" + word_count: 10 # Integer only. Exact count of words in the string. Example: "Hello World, 123" - 2 words only (123 is not a word) + min_word_count: 1 # Integer only. Min count of words in the string. Example: "Hello World. 123" - 2 words only (123 is not a word) + max_word_count: 5 # Integer only. Max count of words in the string Example: "Hello World! 123" - 2 words only (123 is not a word) + at_least_contains: [ a, b ] # At least one of the string must be in the CSV value. Case-sensitive. + all_must_contain: [ a, b, c ] # All the strings must be part of a CSV value. Case-sensitive. + str_ends_with: " suffix" # Case-sensitive. Example: "Hello World suffix" + str_starts_with: "prefix " # Case-sensitive. Example: "prefix Hello World" + # Decimal and integer numbers min: 10 # Can be integer or float, negative and positive diff --git a/src/Rules/AllMustContain.php b/src/Rules/AllMustContain.php new file mode 100644 index 00000000..507d97a3 --- /dev/null +++ b/src/Rules/AllMustContain.php @@ -0,0 +1,37 @@ +getOptionAsArray(); + if (\count($inclusions) === 0) { + return null; + } + + foreach ($inclusions as $inclusion) { + if (\strpos((string)$cellValue, (string)$inclusion) === false) { + return "Value \"{$cellValue}\" must contain all of the following:" . + ' "["' . \implode('", "', $inclusions) . '"]"'; + } + } + + return null; + } +} diff --git a/src/Rules/AtLeastContains.php b/src/Rules/AtLeastContains.php new file mode 100644 index 00000000..5fec1baa --- /dev/null +++ b/src/Rules/AtLeastContains.php @@ -0,0 +1,37 @@ +getOptionAsArray(); + if (\count($inclusions) === 0) { + return null; + } + + foreach ($inclusions as $inclusion) { + if (\strpos((string)$cellValue, (string)$inclusion) !== false) { + return null; + } + } + + return "Value \"{$cellValue}\" must contain one of the following:" . + ' "["' . \implode('", "', $inclusions) . '"]"'; + } +} diff --git a/src/Rules/MaxWordCount.php b/src/Rules/MaxWordCount.php new file mode 100644 index 00000000..d7ea0734 --- /dev/null +++ b/src/Rules/MaxWordCount.php @@ -0,0 +1,33 @@ +getOptionAsInt(); + $count = \str_word_count((string)$cellValue); + + if ($count > $wordCount) { + return "Value \"{$cellValue}\" has {$count} words, " . + "but must have no more than {$wordCount} words"; + } + + return null; + } +} diff --git a/src/Rules/MinWordCount.php b/src/Rules/MinWordCount.php new file mode 100644 index 00000000..1c80f4d2 --- /dev/null +++ b/src/Rules/MinWordCount.php @@ -0,0 +1,33 @@ +getOptionAsInt(); + $count = \str_word_count((string)$cellValue); + + if ($count < $wordCount) { + return "Value \"{$cellValue}\" has {$count} words, " . + "but must have at least {$wordCount} words"; + } + + return null; + } +} diff --git a/src/Rules/StrEndsWith.php b/src/Rules/StrEndsWith.php new file mode 100644 index 00000000..639e742a --- /dev/null +++ b/src/Rules/StrEndsWith.php @@ -0,0 +1,34 @@ +getOptionAsString(); + if ($prefix === '') { + return null; + } + + if (!\str_ends_with((string)$cellValue, $prefix)) { + return "Value \"{$cellValue}\" must end with \"{$prefix}\""; + } + + return null; + } +} diff --git a/src/Rules/StrStartsWith.php b/src/Rules/StrStartsWith.php new file mode 100644 index 00000000..50a22f09 --- /dev/null +++ b/src/Rules/StrStartsWith.php @@ -0,0 +1,34 @@ +getOptionAsString(); + if ($prefix === '') { + return null; + } + + if (!\str_starts_with((string)$cellValue, $prefix)) { + return "Value \"{$cellValue}\" must start with \"{$prefix}\""; + } + + return null; + } +} diff --git a/src/Rules/WordCount.php b/src/Rules/WordCount.php new file mode 100644 index 00000000..2dbc66c8 --- /dev/null +++ b/src/Rules/WordCount.php @@ -0,0 +1,33 @@ +getOptionAsInt(); + $count = \str_word_count((string)$cellValue); + + if ($count !== $wordCount) { + return "Value \"{$cellValue}\" has {$count} words, " . + "but must have exactly {$wordCount} words"; + } + + return null; + } +} diff --git a/tests/Blueprint/MiscTest.php b/tests/Blueprint/MiscTest.php index 84e48744..2ac6f844 100644 --- a/tests/Blueprint/MiscTest.php +++ b/tests/Blueprint/MiscTest.php @@ -82,7 +82,13 @@ public function testFullListOfRules(): void } \sort($rulesInCode); - isSame($rulesInCode, $rulesInConfig); + $diffAsErrMessage = \array_reduce( + \array_diff($rulesInCode, $rulesInConfig), + static fn (string $carry, string $item) => $carry . "{$item}: FIXME\n", + '', + ); + + isSame($rulesInCode, $rulesInConfig, $diffAsErrMessage); } public function testCsvStrutureDefaultValues(): void @@ -105,15 +111,15 @@ public function testCheckYmlSchemaExampleInReadme(): void ); } - public function testCheckPhpSchemaExampleInReadme(): void - { - $this->testCheckExampleInReadme(PROJECT_ROOT . '/schema-examples/full.php', 'php', 'PHP Format', 14); - } - - public function testCheckJsonSchemaExampleInReadme(): void - { - $this->testCheckExampleInReadme(PROJECT_ROOT . '/schema-examples/full.json', 'json', 'JSON Format', 0); - } + // public function testCheckPhpSchemaExampleInReadme(): void + // { + // $this->testCheckExampleInReadme(PROJECT_ROOT . '/schema-examples/full.php', 'php', 'PHP Format', 14); + // } + // + // public function testCheckJsonSchemaExampleInReadme(): void + // { + // $this->testCheckExampleInReadme(PROJECT_ROOT . '/schema-examples/full.json', 'json', 'JSON Format', 0); + // } public function testCompareExamplesWithOrig(): void { @@ -125,8 +131,8 @@ public function testCompareExamplesWithOrig(): void // file_put_contents("{$basepath}.php", (string)phpArray($origYml)); // file_put_contents("{$basepath}.json", (string)json($origYml)); - isSame($origYml, phpArray("{$basepath}.php")->getArrayCopy(), 'PHP config is invalid'); - isSame($origYml, json("{$basepath}.json")->getArrayCopy(), 'JSON config is invalid'); + isSame((string)phpArray($origYml), (string)phpArray("{$basepath}.php"), 'PHP config is invalid'); + isSame((string)json($origYml), (string)json("{$basepath}.json"), 'JSON config is invalid'); } public function testFindFiles(): void diff --git a/tests/Blueprint/RulesTest.php b/tests/Blueprint/RulesTest.php index bd3a12da..1074fb18 100644 --- a/tests/Blueprint/RulesTest.php +++ b/tests/Blueprint/RulesTest.php @@ -16,7 +16,9 @@ namespace JBZoo\PHPUnit\Blueprint; +use JBZoo\CsvBlueprint\Rules\AllMustContain; use JBZoo\CsvBlueprint\Rules\AllowValues; +use JBZoo\CsvBlueprint\Rules\AtLeastContains; use JBZoo\CsvBlueprint\Rules\CardinalDirection; use JBZoo\CsvBlueprint\Rules\DateFormat; use JBZoo\CsvBlueprint\Rules\ExactValue; @@ -34,17 +36,22 @@ use JBZoo\CsvBlueprint\Rules\MaxDate; use JBZoo\CsvBlueprint\Rules\MaxLength; use JBZoo\CsvBlueprint\Rules\MaxPrecision; +use JBZoo\CsvBlueprint\Rules\MaxWordCount; use JBZoo\CsvBlueprint\Rules\Min; use JBZoo\CsvBlueprint\Rules\MinDate; use JBZoo\CsvBlueprint\Rules\MinLength; use JBZoo\CsvBlueprint\Rules\MinPrecision; +use JBZoo\CsvBlueprint\Rules\MinWordCount; use JBZoo\CsvBlueprint\Rules\NotEmpty; use JBZoo\CsvBlueprint\Rules\OnlyCapitalize; use JBZoo\CsvBlueprint\Rules\OnlyLowercase; use JBZoo\CsvBlueprint\Rules\OnlyUppercase; use JBZoo\CsvBlueprint\Rules\Precision; use JBZoo\CsvBlueprint\Rules\Regex; +use JBZoo\CsvBlueprint\Rules\StrEndsWith; +use JBZoo\CsvBlueprint\Rules\StrStartsWith; use JBZoo\CsvBlueprint\Rules\UsaMarketName; +use JBZoo\CsvBlueprint\Rules\WordCount; use JBZoo\PHPUnit\PHPUnit; use JBZoo\Utils\Str; @@ -714,4 +721,136 @@ public function testIsUuid4(): void $rule = new IsUuid4('prop', false); isSame(null, $rule->validate('123')); } + + public function testMustContain(): void + { + $rule = new AtLeastContains('prop', ['a', 'b', 'c']); + isSame(null, $rule->validate('a')); + isSame(null, $rule->validate('abc')); + isSame(null, $rule->validate('adasdasdasdc')); + + isSame( + '"at_least_contains" at line 0, column "prop". ' . + 'Value "123" must contain one of the following: "["a", "b", "c"]".', + \strip_tags((string)$rule->validate('123')), + ); + } + + public function testAllMustContain(): void + { + $rule = new AllMustContain('prop', ['a', 'b', 'c']); + isSame(null, $rule->validate('abc')); + isSame(null, $rule->validate('abdasadasdasdc')); + + isSame( + '"all_must_contain" at line 0, column "prop". ' . + 'Value "ab" must contain all of the following: "["a", "b", "c"]".', + \strip_tags((string)$rule->validate('ab')), + ); + isSame( + '"all_must_contain" at line 0, column "prop". ' . + 'Value "ac" must contain all of the following: "["a", "b", "c"]".', + \strip_tags((string)$rule->validate('ac')), + ); + } + + public function testStrStartsWith(): void + { + $rule = new StrStartsWith('prop', 'a'); + isSame(null, $rule->validate('a')); + isSame(null, $rule->validate('abc')); + + isSame( + '"str_starts_with" at line 0, column "prop". Value "" must start with "a".', + \strip_tags((string)$rule->validate('')), + ); + + isSame( + '"str_starts_with" at line 0, column "prop". Value " a" must start with "a".', + \strip_tags((string)$rule->validate(' a')), + ); + } + + public function testStrEndsWith(): void + { + $rule = new StrEndsWith('prop', 'a'); + isSame(null, $rule->validate('a')); + isSame(null, $rule->validate('cba')); + + isSame( + '"str_ends_with" at line 0, column "prop". Value "" must end with "a".', + \strip_tags((string)$rule->validate('')), + ); + + isSame( + '"str_ends_with" at line 0, column "prop". Value "a " must end with "a".', + \strip_tags((string)$rule->validate('a ')), + ); + } + + public function testStrWordCount(): void + { + $rule = new WordCount('prop', 0); + isSame(null, $rule->validate('')); + isSame( + '"word_count" at line 0, column "prop". ' . + 'Value "cba" has 1 words, but must have exactly 0 words.', + \strip_tags((string)$rule->validate('cba')), + ); + + $rule = new WordCount('prop', 2); + isSame(null, $rule->validate('asd, asdasd')); + isSame( + '"word_count" at line 0, column "prop". ' . + 'Value "cba" has 1 words, but must have exactly 2 words.', + \strip_tags((string)$rule->validate('cba')), + ); + isSame( + '"word_count" at line 0, column "prop". ' . + 'Value "cba 123, 123123" has 1 words, but must have exactly 2 words.', + \strip_tags((string)$rule->validate('cba 123, 123123')), + ); + + isSame( + '"word_count" at line 0, column "prop". Value "a b c" has 3 words, but must have exactly 2 words.', + \strip_tags((string)$rule->validate('a b c')), + ); + } + + public function testMinWordCount(): void + { + $rule = new MinWordCount('prop', 0); + isSame(null, $rule->validate('cba')); + + $rule = new MinWordCount('prop', 2); + isSame(null, $rule->validate('asd, asdasd')); + isSame(null, $rule->validate('asd, asdasd asd')); + isSame(null, $rule->validate('asd, asdasd 1232 asdas')); + isSame( + '"min_word_count" at line 0, column "prop". ' . + 'Value "cba" has 1 words, but must have at least 2 words.', + \strip_tags((string)$rule->validate('cba')), + ); + isSame( + '"min_word_count" at line 0, column "prop". ' . + 'Value "cba 123, 123123" has 1 words, but must have at least 2 words.', + \strip_tags((string)$rule->validate('cba 123, 123123')), + ); + } + + public function testMaxWordCount(): void + { + $rule = new MaxWordCount('prop', 0); + isSame(null, $rule->validate('')); + + $rule = new MaxWordCount('prop', 2); + isSame(null, $rule->validate('asd, asdasd')); + isSame(null, $rule->validate('asd, 1232')); + isSame(null, $rule->validate('asd, 1232 113234324 342 . ..')); + isSame( + '"max_word_count" at line 0, column "prop". ' . + 'Value "asd, asdasd asd 1232 asdas" has 4 words, but must have no more than 2 words.', + \strip_tags((string)$rule->validate('asd, asdasd asd 1232 asdas')), + ); + } } From 2757bb1012c5d0c2aba516fe7a3bbdd68335c46f Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 03:44:38 +0400 Subject: [PATCH 15/17] Refactor code to remove potential nullability issues Updated method signatures across codebase to remove nullable string types for validation input. These changes ensure consistency in handling input values from CSV files. The prior handling could potentially lead to null value related errors, thus this update improves the overall stability of the CSV validation process. --- src/Rules/AbstarctRule.php | 4 ++-- src/Rules/AllMustContain.php | 4 ++-- src/Rules/AllowValues.php | 2 +- src/Rules/AtLeastContains.php | 4 ++-- src/Rules/CardinalDirection.php | 4 ++-- src/Rules/DateFormat.php | 4 ++-- src/Rules/ExactValue.php | 4 ++-- src/Rules/IsBool.php | 4 ++-- src/Rules/IsDomain.php | 4 ++-- src/Rules/IsEmail.php | 2 +- src/Rules/IsFloat.php | 4 ++-- src/Rules/IsInt.php | 4 ++-- src/Rules/IsIp.php | 2 +- src/Rules/IsLatitude.php | 2 +- src/Rules/IsLongitude.php | 2 +- src/Rules/IsUrl.php | 2 +- src/Rules/IsUuid4.php | 4 ++-- src/Rules/Max.php | 2 +- src/Rules/MaxDate.php | 4 ++-- src/Rules/MaxLength.php | 4 ++-- src/Rules/MaxPrecision.php | 2 +- src/Rules/MaxWordCount.php | 4 ++-- src/Rules/Min.php | 2 +- src/Rules/MinDate.php | 4 ++-- src/Rules/MinLength.php | 4 ++-- src/Rules/MinPrecision.php | 2 +- src/Rules/MinWordCount.php | 4 ++-- src/Rules/NotEmpty.php | 4 ++-- src/Rules/OnlyCapitalize.php | 4 ++-- src/Rules/OnlyLowercase.php | 4 ++-- src/Rules/OnlyTrimed.php | 4 ++-- src/Rules/OnlyUppercase.php | 4 ++-- src/Rules/Precision.php | 10 ++++------ src/Rules/Regex.php | 4 ++-- src/Rules/StrEndsWith.php | 4 ++-- src/Rules/StrStartsWith.php | 4 ++-- src/Rules/UsaMarketName.php | 4 ++-- src/Rules/WordCount.php | 4 ++-- src/Validators/ColumnValidator.php | 2 +- src/Validators/Ruleset.php | 2 +- tests/Blueprint/RulesTest.php | 6 +----- 41 files changed, 71 insertions(+), 77 deletions(-) diff --git a/src/Rules/AbstarctRule.php b/src/Rules/AbstarctRule.php index dd827158..8da530e0 100644 --- a/src/Rules/AbstarctRule.php +++ b/src/Rules/AbstarctRule.php @@ -33,7 +33,7 @@ abstract class AbstarctRule private string $columnNameId; private string $ruleCode; - abstract public function validateRule(?string $cellValue): ?string; + abstract public function validateRule(string $cellValue): ?string; public function __construct(string $columnNameId, null|array|bool|float|int|string $options = null) { @@ -42,7 +42,7 @@ public function __construct(string $columnNameId, null|array|bool|float|int|stri $this->ruleCode = $this->getRuleCode(); } - public function validate(?string $cellValue, int $line = 0): ?Error + public function validate(string $cellValue, int $line = 0): ?Error { $error = $this->validateRule($cellValue); if ($error !== null) { diff --git a/src/Rules/AllMustContain.php b/src/Rules/AllMustContain.php index 507d97a3..e400241f 100644 --- a/src/Rules/AllMustContain.php +++ b/src/Rules/AllMustContain.php @@ -18,7 +18,7 @@ final class AllMustContain extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $inclusions = $this->getOptionAsArray(); if (\count($inclusions) === 0) { @@ -26,7 +26,7 @@ public function validateRule(?string $cellValue): ?string } foreach ($inclusions as $inclusion) { - if (\strpos((string)$cellValue, (string)$inclusion) === false) { + if (\strpos($cellValue, (string)$inclusion) === false) { return "Value \"{$cellValue}\" must contain all of the following:" . ' "["' . \implode('", "', $inclusions) . '"]"'; } diff --git a/src/Rules/AllowValues.php b/src/Rules/AllowValues.php index 6541d2e5..c2f602c5 100644 --- a/src/Rules/AllowValues.php +++ b/src/Rules/AllowValues.php @@ -18,7 +18,7 @@ class AllowValues extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $allowedValues = $this->getOptionAsArray(); diff --git a/src/Rules/AtLeastContains.php b/src/Rules/AtLeastContains.php index 5fec1baa..694d325d 100644 --- a/src/Rules/AtLeastContains.php +++ b/src/Rules/AtLeastContains.php @@ -18,7 +18,7 @@ final class AtLeastContains extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $inclusions = $this->getOptionAsArray(); if (\count($inclusions) === 0) { @@ -26,7 +26,7 @@ public function validateRule(?string $cellValue): ?string } foreach ($inclusions as $inclusion) { - if (\strpos((string)$cellValue, (string)$inclusion) !== false) { + if (\strpos($cellValue, (string)$inclusion) !== false) { return null; } } diff --git a/src/Rules/CardinalDirection.php b/src/Rules/CardinalDirection.php index 751dffe6..60ca6d92 100644 --- a/src/Rules/CardinalDirection.php +++ b/src/Rules/CardinalDirection.php @@ -18,13 +18,13 @@ final class CardinalDirection extends AllowValues { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; } - return parent::validateRule((string)$cellValue); + return parent::validateRule($cellValue); } public function getOptionAsArray(): array diff --git a/src/Rules/DateFormat.php b/src/Rules/DateFormat.php index 49bdfefe..232e1046 100644 --- a/src/Rules/DateFormat.php +++ b/src/Rules/DateFormat.php @@ -18,14 +18,14 @@ final class DateFormat extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $expectedDateFormat = $this->getOptionAsString(); if ($expectedDateFormat === '') { return 'Date format is not defined'; } - if ($cellValue === null || $cellValue === '') { + if ($cellValue === '') { return 'Date format of value "" is not valid. Expected format: "' . $expectedDateFormat . '"'; } diff --git a/src/Rules/ExactValue.php b/src/Rules/ExactValue.php index 1914320a..2c3aa3d9 100644 --- a/src/Rules/ExactValue.php +++ b/src/Rules/ExactValue.php @@ -18,9 +18,9 @@ final class ExactValue extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { - if ($this->getOptionAsString() !== (string)$cellValue) { + if ($this->getOptionAsString() !== $cellValue) { return "Value \"{$cellValue}\" is not strict equal to " . "\"{$this->getOptionAsString()}\""; } diff --git a/src/Rules/IsBool.php b/src/Rules/IsBool.php index 51a59dc8..f1c08a79 100644 --- a/src/Rules/IsBool.php +++ b/src/Rules/IsBool.php @@ -18,13 +18,13 @@ final class IsBool extends AllowValues { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; } - return parent::validateRule(\strtolower((string)$cellValue)); + return parent::validateRule(\strtolower($cellValue)); } public function getOptionAsArray(): array diff --git a/src/Rules/IsDomain.php b/src/Rules/IsDomain.php index ac3628e4..1234350e 100644 --- a/src/Rules/IsDomain.php +++ b/src/Rules/IsDomain.php @@ -18,7 +18,7 @@ final class IsDomain extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; @@ -26,7 +26,7 @@ public function validateRule(?string $cellValue): ?string $domainPattern = '/^(?!-)[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(\.[A-Za-z]{2,})$/'; - if (\preg_match($domainPattern, (string)$cellValue) === 0) { + if (\preg_match($domainPattern, $cellValue) === 0) { return "Value \"{$cellValue}\" is not a valid domain"; } diff --git a/src/Rules/IsEmail.php b/src/Rules/IsEmail.php index 3d812588..d59fbb95 100644 --- a/src/Rules/IsEmail.php +++ b/src/Rules/IsEmail.php @@ -18,7 +18,7 @@ final class IsEmail extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; diff --git a/src/Rules/IsFloat.php b/src/Rules/IsFloat.php index a4bb73e7..ab2f8402 100644 --- a/src/Rules/IsFloat.php +++ b/src/Rules/IsFloat.php @@ -18,13 +18,13 @@ class IsFloat extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; } - if (\preg_match('/^-?\d+(\.\d+)?$/', (string)$cellValue) === 0) { + if (\preg_match('/^-?\d+(\.\d+)?$/', $cellValue) === 0) { return "Value \"{$cellValue}\" is not a float number"; } diff --git a/src/Rules/IsInt.php b/src/Rules/IsInt.php index 792369d9..478376b8 100644 --- a/src/Rules/IsInt.php +++ b/src/Rules/IsInt.php @@ -18,13 +18,13 @@ final class IsInt extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; } - if (\preg_match('/^-?\d+$/', (string)$cellValue) === 0) { + if (\preg_match('/^-?\d+$/', $cellValue) === 0) { return "Value \"{$cellValue}\" is not an integer"; } diff --git a/src/Rules/IsIp.php b/src/Rules/IsIp.php index 437a000c..6ed5cf1e 100644 --- a/src/Rules/IsIp.php +++ b/src/Rules/IsIp.php @@ -18,7 +18,7 @@ final class IsIp extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; diff --git a/src/Rules/IsLatitude.php b/src/Rules/IsLatitude.php index 67ba570e..27b19b53 100644 --- a/src/Rules/IsLatitude.php +++ b/src/Rules/IsLatitude.php @@ -21,7 +21,7 @@ final class IsLatitude extends IsFloat private float $min = -90.0; private float $max = 90.0; - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; diff --git a/src/Rules/IsLongitude.php b/src/Rules/IsLongitude.php index 542d8503..0188bf5f 100644 --- a/src/Rules/IsLongitude.php +++ b/src/Rules/IsLongitude.php @@ -21,7 +21,7 @@ final class IsLongitude extends IsFloat private float $min = -180.0; private float $max = 180.0; - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; diff --git a/src/Rules/IsUrl.php b/src/Rules/IsUrl.php index fc072f9d..d57580c9 100644 --- a/src/Rules/IsUrl.php +++ b/src/Rules/IsUrl.php @@ -18,7 +18,7 @@ final class IsUrl extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; diff --git a/src/Rules/IsUuid4.php b/src/Rules/IsUuid4.php index f47fd5bd..37c35fab 100644 --- a/src/Rules/IsUuid4.php +++ b/src/Rules/IsUuid4.php @@ -18,7 +18,7 @@ final class IsUuid4 extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; @@ -26,7 +26,7 @@ public function validateRule(?string $cellValue): ?string $uuid4 = '/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89ABab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/'; - if (\preg_match($uuid4, (string)$cellValue) === 0) { + if (\preg_match($uuid4, $cellValue) === 0) { return 'Value is not a valid UUID v4'; } diff --git a/src/Rules/Max.php b/src/Rules/Max.php index eb96587a..b99d1f72 100644 --- a/src/Rules/Max.php +++ b/src/Rules/Max.php @@ -18,7 +18,7 @@ final class Max extends IsFloat { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $result = parent::validateRule($cellValue); if ($result !== null) { diff --git a/src/Rules/MaxDate.php b/src/Rules/MaxDate.php index 504df30f..834c7471 100644 --- a/src/Rules/MaxDate.php +++ b/src/Rules/MaxDate.php @@ -18,10 +18,10 @@ final class MaxDate extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $minDate = $this->getOptionAsDate(); - $cellDate = new \DateTimeImmutable((string)$cellValue); + $cellDate = new \DateTimeImmutable($cellValue); if ($cellDate->getTimestamp() > $minDate->getTimestamp()) { return "Value \"{$cellValue}\" is more than the maximum " . diff --git a/src/Rules/MaxLength.php b/src/Rules/MaxLength.php index 5d18a6d8..e34ff02b 100644 --- a/src/Rules/MaxLength.php +++ b/src/Rules/MaxLength.php @@ -18,10 +18,10 @@ final class MaxLength extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $minLength = $this->getOptionAsInt(); - $length = \mb_strlen((string)$cellValue); + $length = \mb_strlen($cellValue); if ($length > $minLength) { return "Value \"{$cellValue}\" (length: {$length}) is too long. " . diff --git a/src/Rules/MaxPrecision.php b/src/Rules/MaxPrecision.php index 35e8a65c..1dea055f 100644 --- a/src/Rules/MaxPrecision.php +++ b/src/Rules/MaxPrecision.php @@ -18,7 +18,7 @@ final class MaxPrecision extends Precision { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $valuePrecision = self::getFloatPrecision($cellValue); diff --git a/src/Rules/MaxWordCount.php b/src/Rules/MaxWordCount.php index d7ea0734..99925603 100644 --- a/src/Rules/MaxWordCount.php +++ b/src/Rules/MaxWordCount.php @@ -18,10 +18,10 @@ final class MaxWordCount extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $wordCount = $this->getOptionAsInt(); - $count = \str_word_count((string)$cellValue); + $count = \str_word_count($cellValue); if ($count > $wordCount) { return "Value \"{$cellValue}\" has {$count} words, " . diff --git a/src/Rules/Min.php b/src/Rules/Min.php index c6c2cfda..33e7271c 100644 --- a/src/Rules/Min.php +++ b/src/Rules/Min.php @@ -18,7 +18,7 @@ final class Min extends IsFloat { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $result = parent::validateRule($cellValue); if ($result !== null) { diff --git a/src/Rules/MinDate.php b/src/Rules/MinDate.php index 3c4ae330..174f73d9 100644 --- a/src/Rules/MinDate.php +++ b/src/Rules/MinDate.php @@ -18,10 +18,10 @@ final class MinDate extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $minDate = $this->getOptionAsDate(); - $cellDate = new \DateTimeImmutable((string)$cellValue); + $cellDate = new \DateTimeImmutable($cellValue); if ($cellDate->getTimestamp() < $minDate->getTimestamp()) { return "Value \"{$cellValue}\" is less than the minimum " . diff --git a/src/Rules/MinLength.php b/src/Rules/MinLength.php index bb7b9957..0d6b6fe0 100644 --- a/src/Rules/MinLength.php +++ b/src/Rules/MinLength.php @@ -18,10 +18,10 @@ final class MinLength extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $minLength = $this->getOptionAsInt(); - $length = \mb_strlen((string)$cellValue); + $length = \mb_strlen($cellValue); if ($length < $minLength) { return "Value \"{$cellValue}\" (length: {$length}) is too short. " . diff --git a/src/Rules/MinPrecision.php b/src/Rules/MinPrecision.php index 8af2d12d..90ceda3e 100644 --- a/src/Rules/MinPrecision.php +++ b/src/Rules/MinPrecision.php @@ -18,7 +18,7 @@ final class MinPrecision extends Precision { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $valuePrecision = self::getFloatPrecision($cellValue); diff --git a/src/Rules/MinWordCount.php b/src/Rules/MinWordCount.php index 1c80f4d2..a7fe08a8 100644 --- a/src/Rules/MinWordCount.php +++ b/src/Rules/MinWordCount.php @@ -18,10 +18,10 @@ final class MinWordCount extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $wordCount = $this->getOptionAsInt(); - $count = \str_word_count((string)$cellValue); + $count = \str_word_count($cellValue); if ($count < $wordCount) { return "Value \"{$cellValue}\" has {$count} words, " . diff --git a/src/Rules/NotEmpty.php b/src/Rules/NotEmpty.php index 6a936c8d..cfa623ca 100644 --- a/src/Rules/NotEmpty.php +++ b/src/Rules/NotEmpty.php @@ -18,13 +18,13 @@ final class NotEmpty extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; } - if ($cellValue === null || $cellValue === '') { + if ($cellValue === '') { return 'Value is empty'; } diff --git a/src/Rules/OnlyCapitalize.php b/src/Rules/OnlyCapitalize.php index f426f3fb..e25f747d 100644 --- a/src/Rules/OnlyCapitalize.php +++ b/src/Rules/OnlyCapitalize.php @@ -18,13 +18,13 @@ final class OnlyCapitalize extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; } - if ($cellValue !== null && $cellValue !== \ucfirst($cellValue)) { + if ($cellValue !== \ucfirst($cellValue)) { return "Value \"{$cellValue}\" should be in capitalize"; } diff --git a/src/Rules/OnlyLowercase.php b/src/Rules/OnlyLowercase.php index a5fe11e6..cf36c7f7 100644 --- a/src/Rules/OnlyLowercase.php +++ b/src/Rules/OnlyLowercase.php @@ -18,13 +18,13 @@ final class OnlyLowercase extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; } - if ($cellValue !== null && $cellValue !== \mb_strtolower($cellValue)) { + if ($cellValue !== \mb_strtolower($cellValue)) { return "Value \"{$cellValue}\" should be in lowercase"; } diff --git a/src/Rules/OnlyTrimed.php b/src/Rules/OnlyTrimed.php index f55053b9..d8621755 100644 --- a/src/Rules/OnlyTrimed.php +++ b/src/Rules/OnlyTrimed.php @@ -18,13 +18,13 @@ final class OnlyTrimed extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; } - if (\trim((string)$cellValue) !== (string)$cellValue) { + if (\trim($cellValue) !== $cellValue) { return "Value \"{$cellValue}\" is not trimmed"; } diff --git a/src/Rules/OnlyUppercase.php b/src/Rules/OnlyUppercase.php index 839fe5d4..a1034e5e 100644 --- a/src/Rules/OnlyUppercase.php +++ b/src/Rules/OnlyUppercase.php @@ -18,13 +18,13 @@ final class OnlyUppercase extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; } - if ($cellValue !== null && \mb_strtoupper($cellValue, 'UTF-8') !== $cellValue) { + if (\mb_strtoupper($cellValue, 'UTF-8') !== $cellValue) { return "Value \"{$cellValue}\" is not uppercase"; } diff --git a/src/Rules/Precision.php b/src/Rules/Precision.php index 25b6b816..697a5b80 100644 --- a/src/Rules/Precision.php +++ b/src/Rules/Precision.php @@ -18,7 +18,7 @@ class Precision extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $valuePrecision = self::getFloatPrecision($cellValue); @@ -30,15 +30,13 @@ public function validateRule(?string $cellValue): ?string return null; } - protected static function getFloatPrecision(?string $cellValue): int + protected static function getFloatPrecision(string $cellValue): int { - $floatAsString = (string)$cellValue; - $dotPosition = \strpos($floatAsString, '.'); - + $dotPosition = \strpos($cellValue, '.'); if ($dotPosition === false) { return 0; } - return \strlen($floatAsString) - $dotPosition - 1; + return \strlen($cellValue) - $dotPosition - 1; } } diff --git a/src/Rules/Regex.php b/src/Rules/Regex.php index 95e2be6e..f07ca4d8 100644 --- a/src/Rules/Regex.php +++ b/src/Rules/Regex.php @@ -20,7 +20,7 @@ final class Regex extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $regex = Utils::prepareRegex($this->getOptionAsString()); @@ -28,7 +28,7 @@ public function validateRule(?string $cellValue): ?string return 'Regex pattern is not defined'; } - if (\preg_match($regex, (string)$cellValue) === 0) { + if (\preg_match($regex, $cellValue) === 0) { return "Value \"{$cellValue}\" does not match the pattern \"{$regex}\""; } diff --git a/src/Rules/StrEndsWith.php b/src/Rules/StrEndsWith.php index 639e742a..a8cbb129 100644 --- a/src/Rules/StrEndsWith.php +++ b/src/Rules/StrEndsWith.php @@ -18,14 +18,14 @@ final class StrEndsWith extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $prefix = $this->getOptionAsString(); if ($prefix === '') { return null; } - if (!\str_ends_with((string)$cellValue, $prefix)) { + if (!\str_ends_with($cellValue, $prefix)) { return "Value \"{$cellValue}\" must end with \"{$prefix}\""; } diff --git a/src/Rules/StrStartsWith.php b/src/Rules/StrStartsWith.php index 50a22f09..5295bf1d 100644 --- a/src/Rules/StrStartsWith.php +++ b/src/Rules/StrStartsWith.php @@ -18,14 +18,14 @@ final class StrStartsWith extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $prefix = $this->getOptionAsString(); if ($prefix === '') { return null; } - if (!\str_starts_with((string)$cellValue, $prefix)) { + if (!\str_starts_with($cellValue, $prefix)) { return "Value \"{$cellValue}\" must start with \"{$prefix}\""; } diff --git a/src/Rules/UsaMarketName.php b/src/Rules/UsaMarketName.php index 786f61c8..0626d44e 100644 --- a/src/Rules/UsaMarketName.php +++ b/src/Rules/UsaMarketName.php @@ -18,13 +18,13 @@ final class UsaMarketName extends AllowValues { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { if (!$this->getOptionAsBool()) { return null; } - if (\preg_match('/^[A-Za-z0-9\s-]+, [A-Z]{2}$/u', (string)$cellValue) === 0) { + if (\preg_match('/^[A-Za-z0-9\s-]+, [A-Z]{2}$/u', $cellValue) === 0) { return "Invalid market name format for value \"{$cellValue}\". " . 'Market name must have format "New York, NY"'; } diff --git a/src/Rules/WordCount.php b/src/Rules/WordCount.php index 2dbc66c8..dc020c6f 100644 --- a/src/Rules/WordCount.php +++ b/src/Rules/WordCount.php @@ -18,10 +18,10 @@ final class WordCount extends AbstarctRule { - public function validateRule(?string $cellValue): ?string + public function validateRule(string $cellValue): ?string { $wordCount = $this->getOptionAsInt(); - $count = \str_word_count((string)$cellValue); + $count = \str_word_count($cellValue); if ($count !== $wordCount) { return "Value \"{$cellValue}\" has {$count} words, " . diff --git a/src/Validators/ColumnValidator.php b/src/Validators/ColumnValidator.php index 929f7b47..be91b832 100644 --- a/src/Validators/ColumnValidator.php +++ b/src/Validators/ColumnValidator.php @@ -27,7 +27,7 @@ public function __construct(Column $column) $this->ruleset = new Ruleset($column->getRules(), $column->getHumanName()); } - public function validate(?string $cellValue, int $line): ErrorSuite + public function validate(string $cellValue, int $line): ErrorSuite { return $this->ruleset->validate($cellValue, $line); } diff --git a/src/Validators/Ruleset.php b/src/Validators/Ruleset.php index 4953f84f..9bccd8c0 100644 --- a/src/Validators/Ruleset.php +++ b/src/Validators/Ruleset.php @@ -50,7 +50,7 @@ public function createRule(string $ruleName, null|array|bool|float|int|string $o throw new Exception("Rule \"{$ruleName}\" not found. Expected class: \"{$classname}\""); } - public function validate(?string $cellValue, int $line): ErrorSuite + public function validate(string $cellValue, int $line): ErrorSuite { $errors = new ErrorSuite(); diff --git a/tests/Blueprint/RulesTest.php b/tests/Blueprint/RulesTest.php index 1074fb18..cdee6fc3 100644 --- a/tests/Blueprint/RulesTest.php +++ b/tests/Blueprint/RulesTest.php @@ -465,13 +465,9 @@ public function testNotEmpty(): void '"not_empty" at line 0, column "prop". Value is empty.', \strip_tags((string)$rule->validate('')), ); - isSame( - '"not_empty" at line 0, column "prop". Value is empty.', - \strip_tags((string)$rule->validate(null)), - ); $rule = new NotEmpty('prop', false); - isSame(null, $rule->validate(null)); + isSame(null, $rule->validate('')); } public function testOnlyCapitalize(): void From 861cafa979c68d23e7aa2caa2075d93a4a403032 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 03:57:04 +0400 Subject: [PATCH 16/17] Add is_alias validation rule to schema examples A new validation rule is_alias has been added to the schema examples in the full.json, full.php and full.yml files. Due to this addition, a new IsAlias.php file is created under src/Rules, which will validate the aliases present in these files. This function is also tested in Blueprint/RulesTest.php. --- README.md | 2 +- schema-examples/full.json | 1 + schema-examples/full.php | 1 + schema-examples/full.yml | 2 +- src/Rules/IsAlias.php | 36 +++++++++++++++++++++++++++++++++++ tests/Blueprint/RulesTest.php | 15 +++++++++++++++ 6 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/Rules/IsAlias.php diff --git a/README.md b/README.md index 0ff73c7c..cbd1252c 100644 --- a/README.md +++ b/README.md @@ -333,7 +333,6 @@ columns: str_ends_with: " suffix" # Case-sensitive. Example: "Hello World suffix" str_starts_with: "prefix " # Case-sensitive. Example: "prefix Hello World" - # Decimal and integer numbers min: 10 # Can be integer or float, negative and positive max: 100.50 # Can be integer or float, negative and positive @@ -357,6 +356,7 @@ columns: is_uuid4: true # Only UUID4 format. Example: "550e8400-e29b-41d4-a716-446655440000" is_latitude: true # Can be integer or float. Example: 50.123456 is_longitude: true # Can be integer or float. Example: -89.123456 + is_alias: true # Only alias format. Example: "my-alias-123" cardinal_direction: true # Valid cardinal direction. Examples: "N", "S", "NE", "SE", "none", "" usa_market_name: true # Check if the value is a valid USA market name. Example: "New York, NY" diff --git a/schema-examples/full.json b/schema-examples/full.json index 0f186c76..fe841bf1 100644 --- a/schema-examples/full.json +++ b/schema-examples/full.json @@ -48,6 +48,7 @@ "is_uuid4" : true, "is_latitude" : true, "is_longitude" : true, + "is_alias" : true, "cardinal_direction" : true, "usa_market_name" : true } diff --git a/schema-examples/full.php b/schema-examples/full.php index 2019eaea..f336b00f 100644 --- a/schema-examples/full.php +++ b/schema-examples/full.php @@ -66,6 +66,7 @@ 'is_uuid4' => true, 'is_latitude' => true, 'is_longitude' => true, + 'is_alias' => true, 'cardinal_direction' => true, 'usa_market_name' => true, ], diff --git a/schema-examples/full.yml b/schema-examples/full.yml index d15adfb1..136e73cb 100644 --- a/schema-examples/full.yml +++ b/schema-examples/full.yml @@ -55,7 +55,6 @@ columns: str_ends_with: " suffix" # Case-sensitive. Example: "Hello World suffix" str_starts_with: "prefix " # Case-sensitive. Example: "prefix Hello World" - # Decimal and integer numbers min: 10 # Can be integer or float, negative and positive max: 100.50 # Can be integer or float, negative and positive @@ -79,6 +78,7 @@ columns: is_uuid4: true # Only UUID4 format. Example: "550e8400-e29b-41d4-a716-446655440000" is_latitude: true # Can be integer or float. Example: 50.123456 is_longitude: true # Can be integer or float. Example: -89.123456 + is_alias: true # Only alias format. Example: "my-alias-123" cardinal_direction: true # Valid cardinal direction. Examples: "N", "S", "NE", "SE", "none", "" usa_market_name: true # Check if the value is a valid USA market name. Example: "New York, NY" diff --git a/src/Rules/IsAlias.php b/src/Rules/IsAlias.php new file mode 100644 index 00000000..885c0c7b --- /dev/null +++ b/src/Rules/IsAlias.php @@ -0,0 +1,36 @@ +getOptionAsBool()) { + return null; + } + + $alias = Filter::alias($cellValue); + if ($cellValue !== $alias) { + return "Value \"{$cellValue}\" is not a valid alias. Expected \"{$alias}\"."; + } + + return null; + } +} diff --git a/tests/Blueprint/RulesTest.php b/tests/Blueprint/RulesTest.php index cdee6fc3..4fa6d9a1 100644 --- a/tests/Blueprint/RulesTest.php +++ b/tests/Blueprint/RulesTest.php @@ -22,6 +22,7 @@ use JBZoo\CsvBlueprint\Rules\CardinalDirection; use JBZoo\CsvBlueprint\Rules\DateFormat; use JBZoo\CsvBlueprint\Rules\ExactValue; +use JBZoo\CsvBlueprint\Rules\IsAlias; use JBZoo\CsvBlueprint\Rules\IsBool; use JBZoo\CsvBlueprint\Rules\IsDomain; use JBZoo\CsvBlueprint\Rules\IsEmail; @@ -849,4 +850,18 @@ public function testMaxWordCount(): void \strip_tags((string)$rule->validate('asd, asdasd asd 1232 asdas')), ); } + + public function testIsAlias(): void + { + $rule = new IsAlias('prop', true); + isSame(null, $rule->validate('')); + isSame(null, $rule->validate('123')); + + $rule = new IsAlias('prop', true); + isSame( + '"is_alias" at line 0, column "prop". ' . + 'Value "Qwerty, asd 123" is not a valid alias. Expected "qwerty-asd-123".', + \strip_tags((string)$rule->validate('Qwerty, asd 123')), + ); + } } From d0696b0e81537421671ba13bdefc58173fc48dea Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Thu, 14 Mar 2024 04:05:09 +0400 Subject: [PATCH 17/17] Improve validation of rule options in schema files Error messages have been added to return statements in the AllMustContain, AtLeastContains, StrEndsWith, and StrStartsWith rules when no value or an empty value is provided in the schema files. This results in more informative feedback. Additionally, corresponding tests have been instituted in the RulesTest.php file to verify the functioning of these updates. --- src/Rules/AllMustContain.php | 2 +- src/Rules/AtLeastContains.php | 2 +- src/Rules/StrEndsWith.php | 2 +- src/Rules/StrStartsWith.php | 2 +- tests/Blueprint/RulesTest.php | 29 +++++++++++++++++++++++++++++ 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/Rules/AllMustContain.php b/src/Rules/AllMustContain.php index e400241f..9a50a1aa 100644 --- a/src/Rules/AllMustContain.php +++ b/src/Rules/AllMustContain.php @@ -22,7 +22,7 @@ public function validateRule(string $cellValue): ?string { $inclusions = $this->getOptionAsArray(); if (\count($inclusions) === 0) { - return null; + return 'Rule must contain at least one inclusion value in schema file.'; } foreach ($inclusions as $inclusion) { diff --git a/src/Rules/AtLeastContains.php b/src/Rules/AtLeastContains.php index 694d325d..e93161e6 100644 --- a/src/Rules/AtLeastContains.php +++ b/src/Rules/AtLeastContains.php @@ -22,7 +22,7 @@ public function validateRule(string $cellValue): ?string { $inclusions = $this->getOptionAsArray(); if (\count($inclusions) === 0) { - return null; + return 'Rule must contain at least one inclusion value in schema file.'; } foreach ($inclusions as $inclusion) { diff --git a/src/Rules/StrEndsWith.php b/src/Rules/StrEndsWith.php index a8cbb129..aa5dd3ad 100644 --- a/src/Rules/StrEndsWith.php +++ b/src/Rules/StrEndsWith.php @@ -22,7 +22,7 @@ public function validateRule(string $cellValue): ?string { $prefix = $this->getOptionAsString(); if ($prefix === '') { - return null; + return 'Rule must contain a suffix value in schema file.'; } if (!\str_ends_with($cellValue, $prefix)) { diff --git a/src/Rules/StrStartsWith.php b/src/Rules/StrStartsWith.php index 5295bf1d..a16e8f32 100644 --- a/src/Rules/StrStartsWith.php +++ b/src/Rules/StrStartsWith.php @@ -22,7 +22,7 @@ public function validateRule(string $cellValue): ?string { $prefix = $this->getOptionAsString(); if ($prefix === '') { - return null; + return 'Rule must contain a prefix value in schema file.'; } if (!\str_starts_with($cellValue, $prefix)) { diff --git a/tests/Blueprint/RulesTest.php b/tests/Blueprint/RulesTest.php index 4fa6d9a1..d7f741d3 100644 --- a/tests/Blueprint/RulesTest.php +++ b/tests/Blueprint/RulesTest.php @@ -721,6 +721,13 @@ public function testIsUuid4(): void public function testMustContain(): void { + $rule = new AtLeastContains('prop', []); + isSame( + '"at_least_contains" at line 0, column "prop". ' . + 'Rule must contain at least one inclusion value in schema file.', + \strip_tags((string)$rule->validate('123')), + ); + $rule = new AtLeastContains('prop', ['a', 'b', 'c']); isSame(null, $rule->validate('a')); isSame(null, $rule->validate('abc')); @@ -735,6 +742,13 @@ public function testMustContain(): void public function testAllMustContain(): void { + $rule = new AllMustContain('prop', []); + isSame( + '"all_must_contain" at line 0, column "prop". ' . + 'Rule must contain at least one inclusion value in schema file.', + \strip_tags((string)$rule->validate('ac')), + ); + $rule = new AllMustContain('prop', ['a', 'b', 'c']); isSame(null, $rule->validate('abc')); isSame(null, $rule->validate('abdasadasdasdc')); @@ -766,6 +780,12 @@ public function testStrStartsWith(): void '"str_starts_with" at line 0, column "prop". Value " a" must start with "a".', \strip_tags((string)$rule->validate(' a')), ); + + $rule = new StrStartsWith('prop', ''); + isSame( + '"str_starts_with" at line 0, column "prop". Rule must contain a prefix value in schema file.', + \strip_tags((string)$rule->validate('a ')), + ); } public function testStrEndsWith(): void @@ -783,6 +803,12 @@ public function testStrEndsWith(): void '"str_ends_with" at line 0, column "prop". Value "a " must end with "a".', \strip_tags((string)$rule->validate('a ')), ); + + $rule = new StrEndsWith('prop', ''); + isSame( + '"str_ends_with" at line 0, column "prop". Rule must contain a suffix value in schema file.', + \strip_tags((string)$rule->validate('a ')), + ); } public function testStrWordCount(): void @@ -863,5 +889,8 @@ public function testIsAlias(): void 'Value "Qwerty, asd 123" is not a valid alias. Expected "qwerty-asd-123".', \strip_tags((string)$rule->validate('Qwerty, asd 123')), ); + + $rule = new IsAlias('prop', false); + isSame(null, $rule->validate('Qwerty, asd 123')); } }