From 046cefe4de0cd081de19ad4aac8e8d45905ce716 Mon Sep 17 00:00:00 2001
From: Lauri Eskola <lauri.eskola@acquia.com>
Date: Tue, 13 Jun 2023 19:49:57 +0300
Subject: [PATCH] Issue #3365480 by ctrlADel, e0ipso, sharkbaitdc, smustgrave,
 sarahjean, carolpettirossi: [SDC] Improve error handling during prop
 validation errors

(cherry picked from commit 80174a6c80758b449272b59579cd14b336b6a0f4)
---
 .../sdc/src/Component/ComponentValidator.php  | 15 ++++++++++++--
 .../array-to-object.component.yml             |  8 ++++++++
 .../array-to-object/array-to-object.twig      |  3 +++
 .../tests/src/Kernel/ComponentRenderTest.php  | 20 +++++++++++++++++++
 4 files changed, 44 insertions(+), 2 deletions(-)
 create mode 100644 core/modules/sdc/tests/modules/sdc_test/components/array-to-object/array-to-object.component.yml
 create mode 100644 core/modules/sdc/tests/modules/sdc_test/components/array-to-object/array-to-object.twig

diff --git a/core/modules/sdc/src/Component/ComponentValidator.php b/core/modules/sdc/src/Component/ComponentValidator.php
index 3e849798ebf2..57872670a320 100644
--- a/core/modules/sdc/src/Component/ComponentValidator.php
+++ b/core/modules/sdc/src/Component/ComponentValidator.php
@@ -5,6 +5,7 @@
 use Drupal\sdc\Exception\InvalidComponentException;
 use Drupal\sdc\Plugin\Component;
 use Drupal\sdc\Utilities;
+use JsonSchema\Constraints\Constraint;
 use JsonSchema\Validator;
 
 /**
@@ -164,7 +165,7 @@ public function validateProps(array $context, Component $component): bool {
     $schema = Validator::arrayToObjectRecursive($schema);
     $props = Validator::arrayToObjectRecursive($props_raw);
     $validator = new Validator();
-    $validator->validate($props, $schema);
+    $validator->validate($props, $schema, Constraint::CHECK_MODE_TYPE_CAST);
     $validator->getErrors();
     if ($validator->isValid()) {
       return TRUE;
@@ -183,7 +184,17 @@ function (array $error) use ($context): bool {
       return TRUE;
     }
     $message_parts = array_map(
-      static fn(array $error): string => sprintf("[%s] %s", $error['property'], $error['message']),
+      static function (array $error): string {
+        // We check the error message instead of values and definitions here
+        // because it's hard to access both given the possible complexity of a
+        // schema. Since this is a small non critical DX improvement error
+        // message checking should be sufficient.
+        if (str_contains($error['message'], 'NULL value found, but a ')) {
+          $error['message'] .= '. This may be because the property is empty instead of having data present. If possible fix the source data, use the |default() twig filter, or update the schema to allow multiple types.';
+        }
+
+        return sprintf("[%s] %s", $error['property'], $error['message']);
+      },
       $errors
     );
     $message = implode("/n", $message_parts);
diff --git a/core/modules/sdc/tests/modules/sdc_test/components/array-to-object/array-to-object.component.yml b/core/modules/sdc/tests/modules/sdc_test/components/array-to-object/array-to-object.component.yml
new file mode 100644
index 000000000000..1d70540cde52
--- /dev/null
+++ b/core/modules/sdc/tests/modules/sdc_test/components/array-to-object/array-to-object.component.yml
@@ -0,0 +1,8 @@
+$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json
+name: Array to Object
+props:
+  type: object
+  properties:
+    testProp:
+      title: 'Needs object'
+      type: object
diff --git a/core/modules/sdc/tests/modules/sdc_test/components/array-to-object/array-to-object.twig b/core/modules/sdc/tests/modules/sdc_test/components/array-to-object/array-to-object.twig
new file mode 100644
index 000000000000..695c56f89abf
--- /dev/null
+++ b/core/modules/sdc/tests/modules/sdc_test/components/array-to-object/array-to-object.twig
@@ -0,0 +1,3 @@
+<div>
+  {{ testProp }}
+</div>
diff --git a/core/modules/sdc/tests/src/Kernel/ComponentRenderTest.php b/core/modules/sdc/tests/src/Kernel/ComponentRenderTest.php
index 747ff27ea551..174a2dcbbe4d 100644
--- a/core/modules/sdc/tests/src/Kernel/ComponentRenderTest.php
+++ b/core/modules/sdc/tests/src/Kernel/ComponentRenderTest.php
@@ -37,6 +37,7 @@ public function testRender(): void {
     $this->checkIncludeDataMapping();
     $this->checkEmbedWithNested();
     $this->checkPropValidation();
+    $this->checkArrayObjectTypeCast();
     $this->checkNonExistingComponent();
     $this->checkLibraryOverrides();
     $this->checkAttributeMerging();
@@ -171,6 +172,25 @@ protected function checkPropValidation(): void {
     }
   }
 
+  /**
+   * Ensure fuzzy coercing of arrays and objects works properly.
+   */
+  protected function checkArrayObjectTypeCast(): void {
+    $content = ['test' => []];
+    $build = [
+      '#type' => 'inline_template',
+      '#context' => ['content' => $content],
+      '#template' => "{{ include('sdc_test:array-to-object', { testProp: content.test }, with_context = false) }}",
+    ];
+    try {
+      $this->renderComponentRenderArray($build);
+      $this->addToAssertionCount(1);
+    }
+    catch (\Throwable $e) {
+      $this->fail('Empty array was not converted to object');
+    }
+  }
+
   /**
    * Ensures that including an invalid component creates an error.
    */
-- 
GitLab