From 9718cbdbc6fb8550b70d4b718d0eca87a031d15c Mon Sep 17 00:00:00 2001 From: German Bisurgi Date: Wed, 23 Oct 2024 10:37:39 +0200 Subject: [PATCH 1/9] require an updated version of json-editor --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 99e1784..cd7f884 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ ], "require": { "yiisoft/yii2": "~2.0.0", - "npm-asset/json-editor--json-editor": "^2.5.0" + "npm-asset/json-editor--json-editor": "^2.15.2" }, "suggest": { "2amigos/yii2-selectize-widget": "Recommended assets for selectize plugin in json-editor", From 6681e72309b6bb233cec72af72870dbb26243512 Mon Sep 17 00:00:00 2001 From: German Bisurgi Date: Wed, 23 Oct 2024 10:39:13 +0200 Subject: [PATCH 2/9] Validation helper that uses the modern opis json validator. Returns errors in a format tailored for json-editor and allow for custom error messages (in schema) --- src/helpers/JsonEditorValidatorHelper.php | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/helpers/JsonEditorValidatorHelper.php diff --git a/src/helpers/JsonEditorValidatorHelper.php b/src/helpers/JsonEditorValidatorHelper.php new file mode 100644 index 0000000..e1406c8 --- /dev/null +++ b/src/helpers/JsonEditorValidatorHelper.php @@ -0,0 +1,52 @@ +setMaxErrors(9999); + $filterResolver = $validator->parser()->getFilterResolver(); + + foreach ($filters as $filterClass) { + if (class_exists($filterClass)) { + $filter = new $filterClass(); + $filterResolver->registerCallable($filter->getType(), $filter->getName(), $filter->getCallable()); + } else { + throw new \InvalidArgumentException("Filter class $filterClass does not exist."); + } + } + + $result = $validator->validate(Helper::toJSON($json), Helper::toJSON($jsonSchema)); + + if ($result->isValid()) { + return []; + } else { + $formatter = new ErrorFormatter(); + $error = $result->error(); + $errors = $formatter->formatFlat( $error, function ($error) use ($formatter) { + return [ + 'keyword' => $error->keyword(), + 'path' => $formatter->formatErrorKey($error), + 'message' => implode(', ', $formatter->format($error, false)) + ]; + }); + + return $errors; + } + } +} \ No newline at end of file From ff885fd4632d90c66d222f5d8eb4cd2f7221dc26 Mon Sep 17 00:00:00 2001 From: German Bisurgi Date: Wed, 23 Oct 2024 10:39:57 +0200 Subject: [PATCH 3/9] Added validation action class that uses the validator helper to return errors as JSON data --- src/validators/JsonEditorValidator.php | 101 +++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/validators/JsonEditorValidator.php diff --git a/src/validators/JsonEditorValidator.php b/src/validators/JsonEditorValidator.php new file mode 100644 index 0000000..c03533f --- /dev/null +++ b/src/validators/JsonEditorValidator.php @@ -0,0 +1,101 @@ +validationAction)) { + return ''; + } + + $csrfToken = Yii::$app->request->getCsrfToken(); + $widgetRefName = $model->formName() . $attribute; + $schema = json_encode($this->schema); + + return <<validationAction}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': '{$csrfToken}' + }, + body: JSON.stringify({ + json: json, + jsonSchema: jsonSchema + }) + }) + .then(response => response.json()) + .then(data => { + if (data.status === 'error') { + const root = editor.root.formname + + const mappedErrors = data.errors.map((error) => { + error.path = error.path.replace(/^\//, root + '.').replace(/\//g, '.'); + + if (error.path === root + '.') { + error.path = root + } + + messages.push(error.message); + + return error + }); + + for (let key in editor.editors) { + const childEditor = editor.editors[key] + + if (childEditor) { + childEditor.is_dirty = true + childEditor.showValidationErrors(mappedErrors) + } + } + } + def.resolve(); + }) + .catch((error) => { + console.error('Error:', error); + def.resolve(); + }); + + deferred.push(def); + JS; + } + + public function validateAttribute($model, $attribute) + { + $jsonValidatorHelper = new JsonEditorValidatorHelper(); + $value = json_decode($model->$attribute, true); + $jsonValidatorErrors = $jsonValidatorHelper->validateJson($value, $this->schema, $this->filters); + + if (!empty($jsonValidatorErrors)) { + if (!empty($this->message)) { + $this->addError($model, $attribute, $this->message); + } else { + $message = ''; + + foreach ($jsonValidatorErrors as $error) { + $message .= $error['message'] . ', '; + } + + if (!empty($message)) { + $this->addError($model, $attribute, $message); + } + } + } + } +} From 8488c30d35563665548df7be7661640ae2d87bba Mon Sep 17 00:00:00 2001 From: German Bisurgi Date: Wed, 23 Oct 2024 10:40:17 +0200 Subject: [PATCH 4/9] Added validation action class that uses the validator helper to return errors as JSON data --- src/actions/JsonEditorValidationAction.php | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/actions/JsonEditorValidationAction.php diff --git a/src/actions/JsonEditorValidationAction.php b/src/actions/JsonEditorValidationAction.php new file mode 100644 index 0000000..e0465af --- /dev/null +++ b/src/actions/JsonEditorValidationAction.php @@ -0,0 +1,58 @@ +request->getRawBody(); + $postData = json_decode($rawBody, true); + $json = isset($postData['json']) ? $postData['json'] : null; + $jsonSchema = isset($postData['jsonSchema']) ? $postData['jsonSchema'] : null; + + if (!$json || !$jsonSchema) { + return $this->controller->asJson([ + 'status' => 'error', + 'message' => 'Invalid request. Both value and schema are required.' + ]); + } + + $jsonValidatorHelper = new JsonEditorValidatorHelper(); + $filters = static::getFilters(); + $validationErrors = $jsonValidatorHelper->validateJson($json, $jsonSchema, $filters); + + if (!empty($validationErrors)) { + return $this->controller->asJson([ + 'status' => 'error', + 'errors' => $validationErrors + ]); + } + + return $this->controller->asJson([ + 'status' => 'success', + 'message' => 'Validation passed.' + ]); + } +} \ No newline at end of file From 40f2a1c3f3914a3bcf2fff92764afbb88e7dfb45 Mon Sep 17 00:00:00 2001 From: German Bisurgi Date: Wed, 23 Oct 2024 10:41:46 +0200 Subject: [PATCH 5/9] Added json-editor-widget internal client validation in case an validationAction is given to it. This complement the custom JsonEditorValidator --- src/JsonEditorWidget.php | 88 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/src/JsonEditorWidget.php b/src/JsonEditorWidget.php index 7933da5..868c2fa 100755 --- a/src/JsonEditorWidget.php +++ b/src/JsonEditorWidget.php @@ -32,6 +32,8 @@ class JsonEditorWidget extends BaseWidget */ public $options = []; + public $validationAction = null; + /** * An array that contains the schema to build the form from. * Required. Json::encode will be used. @@ -274,15 +276,99 @@ public function run() $clientOptions = Json::encode($clientOptions); // Prepare element IDs + $widgetRefName = $this->model->formName() . $this->attribute; $widgetId = $this->id; $inputId = $this->inputId; $containerId = $this->containerOptions['id']; // Add the "JSONEditor" instance to the global window object, otherwise the instance is only available in "ready()" function scope - $widgetJs = "window.{$widgetId} = new JSONEditor(document.getElementById('{$containerId}'), {$clientOptions});\n"; + $widgetJs = ""; + + $widgetJs .= "window.{$widgetId} = new JSONEditor(document.getElementById('{$containerId}'), {$clientOptions});\n"; // Add the "JSONEditor" instance to the global window.jsonEditors array. $widgetJs .= "if (!window.jsonEditors) { window.jsonEditors = []; } window.jsonEditors.push(window.{$widgetId});"; + $widgetJs .= "window.{$widgetRefName} = window.{$widgetId};"; + + + + + + + $widgetJs .= "window.{$widgetRefName} = {};"; + $widgetJs .= "window.{$widgetRefName}.editor = window.{$widgetId};"; + + if (!empty($this->validationAction)) { + $csrfToken = Yii::$app->request->getCsrfToken(); + $schema = json_encode($this->schema); + + $widgetJs .= "window.{$widgetRefName}.validate = (forceDirty = false) => { + const json = window.{$widgetRefName}.editor.getValue() + const jsonSchema = JSON.parse('{$schema}') + + fetch('{$this->validationAction}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': '{$csrfToken}' + }, + body: JSON.stringify({ + json: json, + jsonSchema: jsonSchema + }) + }) + .then(response => response.json()) + .then(data => { + if (data.status === 'error') { + const root = window.{$widgetRefName}.editor.root.formname + + const mappedErrors = data.errors.map((error) => { + error.path = error.path.replace(/^\//, root + '.').replace(/\//g, '.'); + + if (error.path === root + '.') { + error.path = root + } + + return error + }); + + for (let key in window.{$widgetRefName}.editor.editors) { + const childEditor = window.{$widgetRefName}.editor.editors[key] + + if (childEditor) { + if (forceDirty) { + childEditor.is_dirty = true + } + + if (childEditor.is_dirty) { + childEditor.showValidationErrors(mappedErrors) + childEditor.is_dirty = false + } + } + } + } + }) + .catch((error) => { + console.error('Error:', error); + }); + }; + + setTimeout(() => { + window.{$widgetRefName}.editor.on('change', function () { + window.{$widgetRefName}.validate(); + }) + }); + "; + + if ($this->model->hasErrors()) { + $widgetJs .= " + setTimeout(() => { + console.log('hey') + window.{$widgetRefName}.validate(true) + });"; + } + } + $readyFunction = ''; $readyFunction .= "{$widgetId}.on('change', function() { document.getElementById('{$inputId}').value = JSON.stringify({$widgetId}.getValue()); });\n"; if ($this->disabled) { From 2e2385c4c4ce1635822220c1539e075b707b1698 Mon Sep 17 00:00:00 2001 From: German Bisurgi Date: Wed, 23 Oct 2024 11:33:44 +0200 Subject: [PATCH 6/9] Added CHANGELOG.md file and Updated README.md --- CHANGELOG.md | 14 ++ README.md | 395 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 400 insertions(+), 9 deletions(-) create mode 100755 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100755 index 0000000..bc3e62e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +## Changelog + +### 1.4 + +- Added features to display backend validation errors + +### 1.3 + +- updated `json-editor` to `^2.3.5` (affects custom editor `extends` usage, [see commit](https://github.com/dmstr/yii2-json-editor/commit/731dd3dce28887fabd536f5c5ba37218ba243c73)) + +### 1.2 + +See `git log` + diff --git a/README.md b/README.md index 276eead..0b1b3d8 100755 --- a/README.md +++ b/README.md @@ -12,15 +12,7 @@ JsonEditorPluginsAsset::register($this); See the `suggest` section of [`composer.json`](https://github.com/dmstr/yii2-json-editor/blob/master/composer.json) for information about recommended composer packages. -## Changelog -### 1.3 - -- updated `json-editor` to `^2.3.5` (affects custom editor `extends` usage, [see commit](https://github.com/dmstr/yii2-json-editor/commit/731dd3dce28887fabd536f5c5ba37218ba243c73)) - -### 1.2 - -See `git log` ## Usage @@ -77,4 +69,389 @@ $form->field($model, 'example_field')->widget(JsonEditorWidget::className(), [ 'theme' => 'bootstrap3', ] ]); -``` \ No newline at end of file +``` + +## Validation + +Add `JsonEditorValidationAction` to your controller. In the example a new class extending the base class +is used to feature the use of filters (custom validator). + +```php +amount)) { + $actualSum += $item->amount; + } + } + + if ($actualSum === $expected) { + return true; + } + + throw new CustomError("The sum of the 'amount' properties must equal {expected}, but found {actualSum}", [ + "actualSum" => $actualSum, + "expected" => $expected + ]); + }; + } +} +``` + +A filter to check if a number is a prime number + +```php + $value]); + } + + if ($value == 2) { + throw new CustomError("This value is not a prime number: {value}", ["value" => $value]); + } + + if ($value % 2 == 0) { + throw new CustomError("This value is not a prime number: {value}", ["value" => $value]); + } + + $max = floor(sqrt($value)); + + for ($i = 3; $i <= $max; $i += 2) { + if ($value % $i == 0) { + throw new CustomError("This value is not a prime number: {value}", ["value" => $value]); + } + } + + return true; + }; + } +} + +``` + +Add `MyJsonEditorValidationAction` to a controller + +```php +public function actions() +{ + $actions = parent::actions(); + $actions[MyJsonEditorValidationAction::getActionName()] = [ + 'class' => MyJsonEditorValidationAction::class + ]; + return $actions; +} +``` + +Now that the action is added we can activate the `JsonEditorWidget` feature that retrieves and +shows backend validation errors by setting the property `validationAction`. A satic helper +method is used for the value but can be changed extending the `JsonEditorValidationAction` class. + +In the example `show_errors` is set to `never` because we want to display only the +errors produced in the backen. This Improves consistency. + +The active fiels option `tag` is set to `false` to remove the generated container around the `JsonEditorWidget`. +This is needed to prevent a know issue that adds the css class ".has-error" to the container making all +fields red. + +```php +field($model, 'json', [ + 'options' => [ + 'tag' => false + ] +])->widget(JsonEditorWidget::class, [ + 'schema' => $model->getJsonSchema(), + 'validationAction' => MyJsonEditorValidationAction::getValidationAction(), + 'clientOptions' => [ + 'disable_collapse' => true, + 'disable_properties' => true, + 'disable_edit_json' => true, + 'theme' => 'bootstrap3', + 'show_errors' => 'never' +], +])->label(false) ?> +``` + +To ensure that the validation is performend on the backend too the `JsonEditorValidator` can be used in the rules. +It takes some parameter that can be accessed trhough the `JsonEditorValidationAction` class helper static methods. +When `validationAction` is set the validator will performe client side validation too. This work together with +the `JsonEditorWidget` to display errors in differen user input scenarions (submit, change, ready, etc). + +```php + $this->getJsonSchema(), + 'validationAction' => MyJsonEditorValidationAction::getValidationAction(), + 'filters' => MyJsonEditorValidationAction::getFilters() + ], + [ + 'another_json', + JsonEditorValidator::class, + 'schema' => $this->getAnotherJsonSchema(), + 'validationAction' => MyJsonEditorValidationAction::getValidationAction(), + 'filters' => MyJsonEditorValidationAction::getFilters() + ] + ]; + } + + public function getJsonSchema() + { + return json_decode('{ + "title": "A JSON Editor", + "type": "object", + "properties": { + "custom_filter": { + "type": "number", + "format": "number", + "$filters": { + "$func": "prime" + } + }, + "id": { + "type": "string", + "description": "Example: ABC-123", + "pattern": "[A-Z]{3}-[0-9]{3,5}" + }, + "name": { + "type": "string", + "minLength": 3, + "maxLength": 20 + }, + "age": { + "format": "number", + "type": "integer", + "minimum": 18, + "maximum": 99 + }, + "email": { + "type": "string", + "format": "email" + }, + "orders": { + "title": "Orders", + "format": "table", + "type": "array", + "uniqueItems": true, + "minItems": 2, + "$filters": { + "$func": "amountSum" + }, + "items": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "format": "number", + "minimum": 10 + }, + "currency": { + "type": "string", + "enum": ["USD", "EUR", "GBP"] + } + } + } + } + } + }', true); + } + + public function getAnotherJsonSchema() + { + return [ + 'title' => 'Another JSON Editor', + 'type' => 'object', + 'required' => [ + 'name', + 'age', + 'date', + 'favorite_color', + 'gender', + 'location', + 'pets' + ], + 'properties' => [ + 'name' => [ + 'type' => 'string', + 'description' => 'First and Last name', + 'minLength' => 4, + '$error' => [ + 'minLength' => Yii::t('schema', 'JUP, MINDESTEN 4 BUCHSTABEN PLEASE') + ], + ], + 'age' => [ + 'format' => 'number', + 'type' => 'integer', + 'minimum' => 18, + 'maximum' => 99 + ], + 'favorite_color' => [ + 'type' => 'string', + 'format' => 'color', + 'title' => 'favorite color' + ], + 'gender' => [ + 'type' => 'string', + 'enum' => [ + 'male', + 'female', + 'other' + ] + ], + 'date' => [ + 'type' => 'string', + 'format' => 'date', + 'options' => [ + 'flatpickr' => [] + ] + ], + 'location' => [ + 'type' => 'object', + 'title' => 'Location', + 'properties' => [ + 'city' => [ + 'type' => 'string' + ], + 'state' => [ + 'type' => 'string' + ], + 'citystate' => [ + 'type' => 'string', + 'description' => 'This is generated automatically from the previous two fields', + 'template' => '{{city}}, {{state}}', + 'watch' => [ + 'city' => 'location.city', + 'state' => 'location.state' + ] + ] + ] + ], + 'pets' => [ + 'type' => 'array', + 'format' => 'table', + 'title' => 'Pets', + 'uniqueItems' => true, + 'items' => [ + 'type' => 'object', + 'title' => 'Pet', + 'properties' => [ + 'type' => [ + 'type' => 'string', + 'enum' => [ + 'cat', + 'dog', + 'bird', + 'reptile', + 'other' + ] + ], + 'name' => [ + 'type' => 'string' + ] + ] + ] + ] + ] + ]; + } +} +``` + +- The keyword `$error` is used to add custom error messages. https://github.com/opis/json-schema/issues/80#issuecomment-832098482 +- The keyword `$filters` is used to add custom validators https://opis.io/json-schema/2.x/php-filter.html + +WARNING: +- Using `$filters` in schemas without creating/registering the relative filter class will result in a fatal error +- Using active field `'tag' => false` when `'enableAjaxValidation' => true` will not display the backend error message produced by the `JsonEditorValidator` \ No newline at end of file From 5e7cd37cf54083d8aab9395a56a410d5b18ef152 Mon Sep 17 00:00:00 2001 From: German Bisurgi Date: Wed, 23 Oct 2024 13:28:18 +0200 Subject: [PATCH 7/9] removed not needed console log --- src/JsonEditorWidget.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/JsonEditorWidget.php b/src/JsonEditorWidget.php index 868c2fa..27ffb20 100755 --- a/src/JsonEditorWidget.php +++ b/src/JsonEditorWidget.php @@ -363,7 +363,6 @@ public function run() if ($this->model->hasErrors()) { $widgetJs .= " setTimeout(() => { - console.log('hey') window.{$widgetRefName}.validate(true) });"; } From 938e12550d80d656c7e8f67bc41cb6e2470a8c81 Mon Sep 17 00:00:00 2001 From: German Bisurgi Date: Wed, 23 Oct 2024 14:04:31 +0200 Subject: [PATCH 8/9] Refactored JS with Heredoc --- src/JsonEditorWidget.php | 113 +++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 57 deletions(-) diff --git a/src/JsonEditorWidget.php b/src/JsonEditorWidget.php index 27ffb20..bb20d1e 100755 --- a/src/JsonEditorWidget.php +++ b/src/JsonEditorWidget.php @@ -302,72 +302,71 @@ public function run() $csrfToken = Yii::$app->request->getCsrfToken(); $schema = json_encode($this->schema); - $widgetJs .= "window.{$widgetRefName}.validate = (forceDirty = false) => { - const json = window.{$widgetRefName}.editor.getValue() - const jsonSchema = JSON.parse('{$schema}') - - fetch('{$this->validationAction}', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-Token': '{$csrfToken}' - }, - body: JSON.stringify({ - json: json, - jsonSchema: jsonSchema - }) - }) - .then(response => response.json()) - .then(data => { - if (data.status === 'error') { - const root = window.{$widgetRefName}.editor.root.formname - - const mappedErrors = data.errors.map((error) => { - error.path = error.path.replace(/^\//, root + '.').replace(/\//g, '.'); - - if (error.path === root + '.') { - error.path = root - } - - return error - }); + $widgetJs .= << { + const json = window.{$widgetRefName}.editor.getValue(); + const jsonSchema = JSON.parse('{$schema}'); - for (let key in window.{$widgetRefName}.editor.editors) { - const childEditor = window.{$widgetRefName}.editor.editors[key] - - if (childEditor) { - if (forceDirty) { - childEditor.is_dirty = true - } + fetch('{$this->validationAction}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': '{$csrfToken}' + }, + body: JSON.stringify({ + json: json, + jsonSchema: jsonSchema + }) + }) + .then(response => response.json()) + .then(data => { + if (data.status === 'error') { + const root = window.{$widgetRefName}.editor.root.formname; + + const mappedErrors = data.errors.map((error) => { + error.path = error.path.replace(/^\//, root + '.').replace(/\//g, '.'); + + if (error.path === root + '.') { + error.path = root; + } + + return error; + }); - if (childEditor.is_dirty) { - childEditor.showValidationErrors(mappedErrors) - childEditor.is_dirty = false + for (let key in window.{$widgetRefName}.editor.editors) { + const childEditor = window.{$widgetRefName}.editor.editors[key]; + + if (childEditor) { + if (forceDirty) { + childEditor.is_dirty = true; + } + + if (childEditor.is_dirty) { + childEditor.showValidationErrors(mappedErrors); + childEditor.is_dirty = false; + } + } } } - } - } - }) - .catch((error) => { - console.error('Error:', error); - }); - }; - - setTimeout(() => { - window.{$widgetRefName}.editor.on('change', function () { - window.{$widgetRefName}.validate(); - }) - }); - "; + }) + .catch((error) => { + console.error('Error:', error); + }); + }; + + setTimeout(() => { + window.{$widgetRefName}.editor.on('change', function () { + window.{$widgetRefName}.validate(); + }); + }); + JS; if ($this->model->hasErrors()) { - $widgetJs .= " - setTimeout(() => { - window.{$widgetRefName}.validate(true) - });"; + $widgetJs .= "setTimeout(() => { window.{$widgetRefName}.validate(true); });"; } } + $readyFunction = ''; $readyFunction .= "{$widgetId}.on('change', function() { document.getElementById('{$inputId}').value = JSON.stringify({$widgetId}.getValue()); });\n"; if ($this->disabled) { From 806fd02d1b1289239c5a56159c68079a4b6c9bed Mon Sep 17 00:00:00 2001 From: German Bisurgi Date: Tue, 29 Oct 2024 09:11:46 +0100 Subject: [PATCH 9/9] Add try catch to validation phase. Display a root message in case of exceptions for example missing filter --- src/helpers/JsonEditorValidatorHelper.php | 53 +++++++++++++++-------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/src/helpers/JsonEditorValidatorHelper.php b/src/helpers/JsonEditorValidatorHelper.php index e1406c8..62c4fa0 100644 --- a/src/helpers/JsonEditorValidatorHelper.php +++ b/src/helpers/JsonEditorValidatorHelper.php @@ -31,22 +31,41 @@ public function validateJson($json, $jsonSchema, $filters = []) } } - $result = $validator->validate(Helper::toJSON($json), Helper::toJSON($jsonSchema)); - - if ($result->isValid()) { - return []; - } else { - $formatter = new ErrorFormatter(); - $error = $result->error(); - $errors = $formatter->formatFlat( $error, function ($error) use ($formatter) { - return [ - 'keyword' => $error->keyword(), - 'path' => $formatter->formatErrorKey($error), - 'message' => implode(', ', $formatter->format($error, false)) - ]; - }); - - return $errors; + try { + $result = $validator->validate(Helper::toJSON($json), Helper::toJSON($jsonSchema)); + + if ($result->isValid()) { + return []; + } else { + return $this->formatErrors($result->error()); + } + } catch (\Exception $e) { + return [ + [ + 'keyword' => 'exception', + 'path' => '/', + 'message' => 'Validation could not be performed correctly due to: ' . $e->getMessage() + ] + ]; } } -} \ No newline at end of file + + /** + * Format validation errors into a structured array. + * + * @param \Opis\JsonSchema\Errors\ValidationError $error + * @return array The array of formatted validation errors. + */ + protected function formatErrors($error) + { + $formatter = new ErrorFormatter(); + + return $formatter->formatFlat($error, function ($error) use ($formatter) { + return [ + 'keyword' => $error->keyword(), + 'path' => $formatter->formatErrorKey($error), + 'message' => implode(', ', $formatter->format($error, false)) + ]; + }); + } +}