From 6f92cb444fbbfaed0c8e638bcf62ba06f786e857 Mon Sep 17 00:00:00 2001 From: Sorin Sarca Date: Mon, 30 Dec 2024 00:29:45 +0200 Subject: [PATCH] Added stopAtFirstError --- src/CompliantValidator.php | 8 ++++++-- src/Schemas/ObjectSchema.php | 34 ++++++++++++++++++++++++++++--- src/ValidationContext.php | 39 ++++++++++++++++++++++++++++++------ src/Validator.php | 38 +++++++++++++++++++++++++++++++++-- 4 files changed, 106 insertions(+), 13 deletions(-) diff --git a/src/CompliantValidator.php b/src/CompliantValidator.php index e7273e6..a979b1b 100644 --- a/src/CompliantValidator.php +++ b/src/CompliantValidator.php @@ -38,9 +38,13 @@ class CompliantValidator extends Validator 'keepAdditionalItemsKeyword' => false, ]; - public function __construct(?SchemaLoader $loader = null, int $max_errors = 1) + public function __construct( + ?SchemaLoader $loader = null, + int $max_errors = 1, + bool $stop_at_first_error = true + ) { - parent::__construct($loader, $max_errors); + parent::__construct($loader, $max_errors, $stop_at_first_error); // Set parser options $parser = $this->parser(); diff --git a/src/Schemas/ObjectSchema.php b/src/Schemas/ObjectSchema.php index 1cc11be..1b5da2c 100644 --- a/src/Schemas/ObjectSchema.php +++ b/src/Schemas/ObjectSchema.php @@ -18,7 +18,7 @@ namespace Opis\JsonSchema\Schemas; use Opis\JsonSchema\{Helper, Keyword, ValidationContext, KeywordValidator}; -use Opis\JsonSchema\Info\SchemaInfo; +use Opis\JsonSchema\Info\{DataInfo, SchemaInfo}; use Opis\JsonSchema\Errors\ValidationError; use Opis\JsonSchema\KeywordValidators\CallbackKeywordValidator; @@ -109,12 +109,40 @@ public function doValidate(ValidationContext $context): ?ValidationError */ protected function applyKeywords(array $keywords, ValidationContext $context): ?ValidationError { + if ($context->stopAtFirstError()) { + foreach ($keywords as $keyword) { + if ($error = $keyword->validate($context, $this)) { + return $error; + } + } + return null; + } + + /** @var null|ValidationError[] $error_list */ + $error_list = null; + foreach ($keywords as $keyword) { if ($error = $keyword->validate($context, $this)) { - return $error; + $error_list ??= []; + $error_list[] = $error; } } - return null; + if (!$error_list) { + return null; + } + + if (count($error_list) === 1) { + return $error_list[0]; + } + + return new ValidationError( + '', + $this, + DataInfo::fromContext($context), + 'Data must match schema', + [], + $error_list + ); } } \ No newline at end of file diff --git a/src/ValidationContext.php b/src/ValidationContext.php index e55470e..9e22b3e 100644 --- a/src/ValidationContext.php +++ b/src/ValidationContext.php @@ -54,6 +54,8 @@ class ValidationContext protected int $maxErrors = 1; + protected bool $stopAtFirstError = true; + /** * @param $data * @param SchemaLoader $loader @@ -70,7 +72,8 @@ public function __construct( ?Schema $sender = null, array $globals = [], ?array $slots = null, - int $max_errors = 1 + int $max_errors = 1, + bool $stop_at_first_error = true ) { $this->sender = $sender; $this->rootData = $data; @@ -79,6 +82,7 @@ public function __construct( $this->globals = $globals; $this->slots = null; $this->maxErrors = $max_errors; + $this->stopAtFirstError = $stop_at_first_error; $this->currentData = [ [$data, false], ]; @@ -101,10 +105,19 @@ public function newInstance( ?Schema $sender, ?array $globals = null, ?array $slots = null, - ?int $max_errors = null + ?int $max_errors = null, + ?bool $stop_at_first_error = null ): self { - return new self($data, $this->loader, $this, $sender, $globals ?? $this->globals, $slots ?? $this->slots, - $max_errors ?? $this->maxErrors); + return new self( + $data, + $this->loader, + $this, + $sender, + $globals ?? $this->globals, + $slots ?? $this->slots, + $max_errors ?? $this->maxErrors, + $stop_at_first_error ?? $this->stopAtFirstError + ); } public function create( @@ -112,7 +125,8 @@ public function create( ?Variables $mapper = null, ?Variables $globals = null, ?array $slots = null, - ?int $maxErrors = null + ?int $maxErrors = null, + ?bool $stop_at_first_error = null ): self { if ($globals) { $globals = $globals->resolve($this->rootData(), $this->currentDataPath()); @@ -131,7 +145,7 @@ public function create( } return new self($data, $this->loader, $this, $sender, $globals, $slots ?? $this->slots, - $maxErrors ?? $this->maxErrors); + $maxErrors ?? $this->maxErrors, $stop_at_first_error ?? $this->stopAtFirstError); } public function sender(): ?Schema @@ -359,6 +373,19 @@ public function setMaxErrors(int $max): self return $this; } + + public function stopAtFirstError(): bool + { + return $this->stopAtFirstError; + } + + public function setStopAtFirstError(bool $stop): self + { + $this->stopAtFirstError = $stop; + + return $this; + } + /* --------------------- */ /** diff --git a/src/Validator.php b/src/Validator.php index 73ae717..f80a70b 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -26,15 +26,22 @@ class Validator { protected SchemaLoader $loader; protected int $maxErrors = 1; + protected bool $stopAtFirstError = true; /** * @param SchemaLoader|null $loader * @param int $max_errors + * @param bool $stop_at_first_error */ - public function __construct(?SchemaLoader $loader = null, int $max_errors = 1) + public function __construct( + ?SchemaLoader $loader = null, + int $max_errors = 1, + bool $stop_at_first_error = true + ) { $this->loader = $loader ?? new SchemaLoader(new SchemaParser(), new SchemaResolver(), true); $this->maxErrors = $max_errors; + $this->stopAtFirstError = $stop_at_first_error; } /** @@ -170,7 +177,16 @@ public function createContext($data, ?array $globals = null, ?array $slots = nul $slots = $this->parseSlots($slots); } - return new ValidationContext($data, $this->loader, null, null, $globals ?? [], $slots, $this->maxErrors); + return new ValidationContext( + $data, + $this->loader, + null, + null, + $globals ?? [], + $slots, + $this->maxErrors, + $this->stopAtFirstError, + ); } /** @@ -249,6 +265,24 @@ public function setMaxErrors(int $max_errors): self return $this; } + /** + * @return bool + */ + public function getStopAtFirstError(): bool + { + return $this->stopAtFirstError; + } + + /** + * @param bool $stop + * @return $this + */ + public function setStopAtFirstError(bool $stop): self + { + $this->stopAtFirstError = $stop; + return $this; + } + /** * @param array $slots * @return array