Loading core/lib/Drupal/Core/Theme/Component/ComponentValidator.php +6 −5 Original line number Diff line number Diff line Loading @@ -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'] Loading Loading @@ -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; Loading core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php +55 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. * Loading Loading @@ -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); } } Loading
core/lib/Drupal/Core/Theme/Component/ComponentValidator.php +6 −5 Original line number Diff line number Diff line Loading @@ -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'] Loading Loading @@ -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; Loading
core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php +55 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. * Loading Loading @@ -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); } }