-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #53749 [Validator] Add
Yaml
constraint for validating YAML …
…content (symfonyaml) This PR was squashed before being merged into the 7.2 branch. Discussion ---------- [Validator] Add `Yaml` constraint for validating YAML content | Q | A | ------------- | --- | Branch? | 7.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Ticket? | no | License | MIT ## Purpose Inspired by the [Json constraint](https://symfony.com/doc/current/reference/constraints/Json.html), I've added a new feature to the Validator component for validating YAML content with a dedicated constraint. **Real world use case**: Having configuration settings stored in YAML format within a database. With this new feature, you can validate the integrity of these configurations, ensuring the YAML syntax is OK. ## Options I've added a `flags` option to this constraint, aligning with the [Yaml parser flags](https://symfony.com/doc/current/components/yaml.html#advanced-usage-flags). ## Exemple ```php namespace App\Entity; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Yaml\Yaml; class Configuration { #[Assert\Yaml(flags: Yaml::PARSE_DATETIME)] private string $content; } ``` Commits ------- 023d48c [Validator] Add `Yaml` constraint for validating YAML content
- Loading branch information
Showing
5 changed files
with
262 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Validator\Constraints; | ||
|
||
use Symfony\Component\Validator\Attribute\HasNamedArguments; | ||
use Symfony\Component\Validator\Constraint; | ||
use Symfony\Component\Validator\Exception\LogicException; | ||
use Symfony\Component\Yaml\Parser; | ||
|
||
/** | ||
* @author Kev <https://github.com/symfonyaml> | ||
*/ | ||
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] | ||
class Yaml extends Constraint | ||
{ | ||
public const INVALID_YAML_ERROR = '63313a31-837c-42bb-99eb-542c76aacc48'; | ||
|
||
protected const ERROR_NAMES = [ | ||
self::INVALID_YAML_ERROR => 'INVALID_YAML_ERROR', | ||
]; | ||
|
||
#[HasNamedArguments] | ||
public function __construct( | ||
public string $message = 'This value is not valid YAML.', | ||
public int $flags = 0, | ||
?array $groups = null, | ||
mixed $payload = null, | ||
) { | ||
if (!class_exists(Parser::class)) { | ||
throw new LogicException('The Yaml component is required to use the Yaml constraint. Try running "composer require symfony/yaml".'); | ||
} | ||
|
||
parent::__construct(null, $groups, $payload); | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
src/Symfony/Component/Validator/Constraints/YamlValidator.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Validator\Constraints; | ||
|
||
use Symfony\Component\Validator\Constraint; | ||
use Symfony\Component\Validator\ConstraintValidator; | ||
use Symfony\Component\Validator\Exception\UnexpectedTypeException; | ||
use Symfony\Component\Validator\Exception\UnexpectedValueException; | ||
use Symfony\Component\Yaml\Exception\ParseException; | ||
use Symfony\Component\Yaml\Parser; | ||
|
||
/** | ||
* @author Kev <https://github.com/symfonyaml> | ||
*/ | ||
class YamlValidator extends ConstraintValidator | ||
{ | ||
public function validate(mixed $value, Constraint $constraint): void | ||
{ | ||
if (!$constraint instanceof Yaml) { | ||
throw new UnexpectedTypeException($constraint, Yaml::class); | ||
} | ||
|
||
if (null === $value || '' === $value) { | ||
return; | ||
} | ||
|
||
if (!\is_scalar($value) && !$value instanceof \Stringable) { | ||
throw new UnexpectedValueException($value, 'string'); | ||
} | ||
|
||
$value = (string) $value; | ||
|
||
/** @see \Symfony\Component\Yaml\Command\LintCommand::validate() */ | ||
$prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { | ||
if (\E_USER_DEPRECATED === $level) { | ||
throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1); | ||
} | ||
|
||
return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false; | ||
}); | ||
|
||
try { | ||
(new Parser())->parse($value, $constraint->flags); | ||
} catch (ParseException $e) { | ||
$this->context->buildViolation($constraint->message) | ||
->setParameter('{{ error }}', $e->getMessage()) | ||
->setParameter('{{ line }}', $e->getParsedLine()) | ||
->setCode(Yaml::INVALID_YAML_ERROR) | ||
->addViolation(); | ||
} finally { | ||
restore_error_handler(); | ||
} | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
src/Symfony/Component/Validator/Tests/Constraints/YamlTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Validator\Tests\Constraints; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\Validator\Constraints\Yaml; | ||
use Symfony\Component\Validator\Mapping\ClassMetadata; | ||
use Symfony\Component\Validator\Mapping\Loader\AttributeLoader; | ||
use Symfony\Component\Yaml\Yaml as YamlParser; | ||
|
||
/** | ||
* @author Kev <https://github.com/symfonyaml> | ||
*/ | ||
class YamlTest extends TestCase | ||
{ | ||
public function testAttributes() | ||
{ | ||
$metadata = new ClassMetadata(YamlDummy::class); | ||
$loader = new AttributeLoader(); | ||
self::assertTrue($loader->loadClassMetadata($metadata)); | ||
|
||
[$bConstraint] = $metadata->properties['b']->getConstraints(); | ||
self::assertSame('myMessage', $bConstraint->message); | ||
self::assertSame(['Default', 'YamlDummy'], $bConstraint->groups); | ||
|
||
[$cConstraint] = $metadata->properties['c']->getConstraints(); | ||
self::assertSame(['my_group'], $cConstraint->groups); | ||
self::assertSame('some attached data', $cConstraint->payload); | ||
|
||
[$cConstraint] = $metadata->properties['d']->getConstraints(); | ||
self::assertSame(YamlParser::PARSE_CONSTANT | YamlParser::PARSE_CUSTOM_TAGS, $cConstraint->flags); | ||
} | ||
} | ||
|
||
class YamlDummy | ||
{ | ||
#[Yaml] | ||
private $a; | ||
|
||
#[Yaml(message: 'myMessage')] | ||
private $b; | ||
|
||
#[Yaml(groups: ['my_group'], payload: 'some attached data')] | ||
private $c; | ||
|
||
#[Yaml(flags: YamlParser::PARSE_CONSTANT | YamlParser::PARSE_CUSTOM_TAGS)] | ||
private $d; | ||
} |
97 changes: 97 additions & 0 deletions
97
src/Symfony/Component/Validator/Tests/Constraints/YamlValidatorTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Validator\Tests\Constraints; | ||
|
||
use Symfony\Component\Validator\Constraints\Yaml; | ||
use Symfony\Component\Validator\Constraints\YamlValidator; | ||
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; | ||
use Symfony\Component\Yaml\Yaml as YamlParser; | ||
|
||
/** | ||
* @author Kev <https://github.com/symfonyaml> | ||
*/ | ||
class YamlValidatorTest extends ConstraintValidatorTestCase | ||
{ | ||
protected function createValidator(): YamlValidator | ||
{ | ||
return new YamlValidator(); | ||
} | ||
|
||
/** | ||
* @dataProvider getValidValues | ||
*/ | ||
public function testYamlIsValid($value) | ||
{ | ||
$this->validator->validate($value, new Yaml()); | ||
|
||
$this->assertNoViolation(); | ||
} | ||
|
||
public function testYamlWithFlags() | ||
{ | ||
$this->validator->validate('date: 2023-01-01', new Yaml(flags: YamlParser::PARSE_DATETIME)); | ||
$this->assertNoViolation(); | ||
} | ||
|
||
/** | ||
* @dataProvider getInvalidValues | ||
*/ | ||
public function testInvalidValues($value, $message, $line) | ||
{ | ||
$constraint = new Yaml( | ||
message: 'myMessageTest', | ||
); | ||
|
||
$this->validator->validate($value, $constraint); | ||
|
||
$this->buildViolation('myMessageTest') | ||
->setParameter('{{ error }}', $message) | ||
->setParameter('{{ line }}', $line) | ||
->setCode(Yaml::INVALID_YAML_ERROR) | ||
->assertRaised(); | ||
} | ||
|
||
public function testInvalidFlags() | ||
{ | ||
$value = 'tags: [!tagged app.myclass]'; | ||
$this->validator->validate($value, new Yaml()); | ||
$this->buildViolation('This value is not valid YAML.') | ||
->setParameter('{{ error }}', 'Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!tagged" at line 1 (near "tags: [!tagged app.myclass]").') | ||
->setParameter('{{ line }}', 1) | ||
->setCode(Yaml::INVALID_YAML_ERROR) | ||
->assertRaised(); | ||
} | ||
|
||
public static function getValidValues() | ||
{ | ||
return [ | ||
['planet_diameters: {earth: 12742, mars: 6779, saturn: 116460, mercury: 4879}'], | ||
["key:\n value"], | ||
[null], | ||
[''], | ||
['"null"'], | ||
['null'], | ||
['"string"'], | ||
['1'], | ||
['true'], | ||
[1], | ||
]; | ||
} | ||
|
||
public static function getInvalidValues(): array | ||
{ | ||
return [ | ||
['{:INVALID]', 'Malformed unquoted YAML string at line 1 (near "{:INVALID]").', 1], | ||
["key:\nvalue", 'Unable to parse at line 2 (near "value").', 2], | ||
]; | ||
} | ||
} |