diff --git a/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php b/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php index 6664b2d3ec7ee81aa760bb00423d2e39f81d1c17..246f143d4e25d0e9bc6b732b8253e7ced2ff351c 100644 --- a/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php +++ b/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php @@ -123,6 +123,7 @@ public function validateDefinition(array $definition, bool $enforce_schemas): bo ); $definition_object = Validator::arrayToObjectRecursive($definition); + $this->validator->reset(); $this->validator->validate( $definition_object, (object) ['$ref' => 'file://' . dirname(__DIR__, 5) . '/assets/schemas/v1/metadata-full.schema.json'] @@ -188,15 +189,15 @@ public function validateProps(array $context, Component $component): bool { ] = $this->validateClassProps($schema, $props_raw, $component_id); $schema = Validator::arrayToObjectRecursive($schema); $props = Validator::arrayToObjectRecursive($props_raw); - $validator = new Validator(); - $validator->validate($props, $schema, Constraint::CHECK_MODE_TYPE_CAST); - $validator->getErrors(); - if ($validator->isValid()) { + $this->validator->reset(); + $this->validator->validate($props, $schema, Constraint::CHECK_MODE_TYPE_CAST); + $this->validator->getErrors(); + if ($this->validator->isValid()) { return TRUE; } // Dismiss type errors if the prop received a render array. $errors = array_filter( - $validator->getErrors(), + $this->validator->getErrors(), function (array $error) use ($context): bool { if (($error['constraint'] ?? '') !== 'type') { return TRUE; diff --git a/core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php b/core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php index ff79612b494e20dbbff7b70f603532fec903ce23..df03332cf4b1918b84e06b81b32ed2a8d05f99d2 100644 --- a/core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php +++ b/core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php @@ -4,10 +4,15 @@ namespace Drupal\Tests\Core\Theme\Component; +use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Template\Attribute; use Drupal\Core\Theme\Component\ComponentValidator; use Drupal\Core\Render\Component\Exception\InvalidComponentException; use Drupal\Core\Plugin\Component; +use JsonSchema\Constraints\Factory; +use JsonSchema\Constraints\FormatConstraint; +use JsonSchema\Entity\JsonPointer; +use JsonSchema\Validator; use PHPUnit\Framework\TestCase; use Symfony\Component\Yaml\Yaml; @@ -168,6 +173,33 @@ public static function dataProviderValidatePropsValid(): array { ]; } + /** + * Tests we can use a custom validator to validate props. + */ + public function testCustomValidator(): void { + $component = new Component( + ['app_root' => '/fake/path/root'], + 'sdc_test:my-cta', + static::loadComponentDefinitionFromFs('my-cta'), + ); + $component_validator = new ComponentValidator(); + // A validator with a constraint factory that uses a custom constraint for + // checking format. + $component_validator->setValidator(new Validator((new Factory())->setConstraintClass('format', UrlHelperFormatConstraint::class))); + self::assertTrue( + $component_validator->validateProps([ + 'text' => 'Can Pica', + // This is a valid URI but for v5.2 of justinrainbow/json-schema it + // does not pass validation without a custom constraint for format. + // We pass a custom factory and it should be used. + 'href' => 'entity:node/1', + 'target' => '_blank', + 'attributes' => new Attribute(['key' => 'value']), + ], $component), + 'The valid component props threw an error.' + ); + } + /** * Tests that invalid props are handled properly. * @@ -255,3 +287,26 @@ private static function loadComponentDefinitionFromFs(string $component_name): a } } + +/** + * Defines a custom format constraint for json-schema. + */ +class UrlHelperFormatConstraint extends FormatConstraint { + + /** + * {@inheritdoc} + */ + public function check(&$element, $schema = NULL, ?JsonPointer $path = NULL, $i = NULL): void { + if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) { + return; + } + if ($schema->format === 'uri') { + if (\is_string($element) && !UrlHelper::isValid($element)) { + $this->addError($path, 'Invalid URL format', 'format', ['format' => $schema->format]); + } + return; + } + parent::check($element, $schema, $path, $i); + } + +}