Permalink
Browse files

Added new validator for UUIDs

  • Loading branch information...
1 parent 790ba4c commit 19931c984cee492297eb16f5ef325b936fb05d11 @colinodell colinodell committed with fabpot Feb 18, 2014
@@ -0,0 +1,60 @@
+<?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;
+
+/**
+ * @Annotation
+ *
+ * @author Colin O'Dell <colinodell@gmail.com>
+ */
+class Uuid extends Constraint
+{
+ // Possible versions defined by RFC 4122
+ const V1_MAC = 1;
+ const V2_DCE = 2;
+ const V3_MD5 = 3;
+ const V4_RANDOM = 4;
+ const V5_SHA1 = 5;
+
+ /**
+ * Message to display when validation fails
+ *
+ * @var string
+ */
+ public $message = 'This is not a valid UUID.';
+
+ /**
+ * Strict mode only allows UUIDs that meet the formal definition and formatting per RFC 4122
+ *
+ * Set this to `false` to allow legacy formats with different dash positioning or wrapping characters
+ *
+ * @var bool
+ */
+ public $strict = true;
+
+ /**
+ * Array of allowed versions (see version constants above)
+ *
+ * All UUID versions are allowed by default
+ *
+ * @var int[]
+ */
+ public $versions = array(
+ self::V1_MAC,
+ self::V2_DCE,
+ self::V3_MD5,
+ self::V4_RANDOM,
+ self::V5_SHA1
+ );
+}
@@ -0,0 +1,82 @@
+<?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;
+
+/**
+ * Validates whether the value is a valid UUID per RFC 4122.
+ *
+ * @author Colin O'Dell <colinodell@gmail.com>
+ *
+ * @see http://tools.ietf.org/html/rfc4122
+ * @see https://en.wikipedia.org/wiki/Universally_unique_identifier
+ */
+class UuidValidator extends ConstraintValidator
+{
+ /**
+ * Regular expression which verifies allowed characters and the proper format.
+ *
+ * The strict pattern matches UUIDs like this: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
+ * Roughly speaking: x = any hexadecimal character, M = any allowed version, N = any allowed variant.
+ */
+ const STRICT_PATTERN = '/^[a-f0-9]{8}-[a-f0-9]{4}-[%s][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i';
+
+ /**
+ * The loose pattern validates similar yet non-compliant UUIDs.
+ *
+ * Dashes are completely optional. If present, they should only appear between every fourth character.
+ * The value can also be wrapped with characters like []{} for backwards-compatibility with other systems.
+ * Neither the version nor the variant is validated by this pattern.
+ */
+ const LOOSE_PATTERN = '/^[a-f0-9]{4}(?:-?[a-f0-9]{4}){7}$/i';
+
+ /**
+ * Properly-formatted UUIDs contain 32 hex digits, separated by 4 dashes.
+ * We can use this fact to avoid performing a preg_match on strings of other sizes.
+ */
+ const STRICT_UUID_LENGTH = 36;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function validate($value, Constraint $constraint)
+ {
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
+ throw new UnexpectedTypeException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if ($constraint->strict) {
+ // Insert the allowed versions into the regular expression
+ $pattern = sprintf(static::STRICT_PATTERN, implode('', $constraint->versions));
+
+ if (strlen($value) !== static::STRICT_UUID_LENGTH || !preg_match($pattern, $value)) {
+ $this->context->addViolation($constraint->message, array('{{ value }}' => $value));
+ }
+ } else {
+ // Trim any wrapping characters like [] or {} used by some legacy systems
+ $value = trim($value, '[]{}');
+
+ if (!preg_match(static::LOOSE_PATTERN, $value)) {
+ $this->context->addViolation($constraint->message, array('{{ value }}' => $value));
+ }
+ }
+ }
+}
@@ -0,0 +1,202 @@
+<?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\Uuid;
+use Symfony\Component\Validator\Constraints\UuidValidator;
+
+/**
+ * @author Colin O'Dell <colinodell@gmail.com>
+ */
+class UuidValidatorTest extends \PHPUnit_Framework_TestCase
+{
+ protected $context;
+ protected $validator;
+
+ protected function setUp()
+ {
+ $this->context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false);
+ $this->validator = new UuidValidator();
+ $this->validator->initialize($this->context);
+ }
+
+ protected function tearDown()
+ {
+ $this->context = null;
+ $this->validator = null;
+ }
+
+ public function testNullIsValid()
+ {
+ $this->context->expects($this->never())
+ ->method('addViolation');
+
+ $this->validator->validate(null, new Uuid());
+ }
+
+ public function testEmptyStringIsValid()
+ {
+ $this->context->expects($this->never())
+ ->method('addViolation');
+
+ $this->validator->validate('', new Uuid());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException
+ */
+ public function testExpectsStringCompatibleType()
+ {
+ $this->validator->validate(new \stdClass(), new Uuid());
+ }
+
+ /**
+ * @dataProvider getValidStrictUuids
+ */
+ public function testValidStrictUuids($uuid)
+ {
+ $this->context->expects($this->never())
+ ->method('addViolation');
+
+ $this->validator->validate($uuid, new Uuid());
+ }
+
+ public function getValidStrictUuids()
+ {
+ return array(
+ array('216fff40-98d9-11e3-a5e2-0800200c9a66'), // Version 1 UUID in lowercase
+ array('216FFF40-98D9-11E3-A5E2-0800200C9A66'), // Version 1 UUID in UPPERCASE
+ array('456daefb-5aa6-41b5-8dbc-068b05a8b201'), // Version 4 UUID in lowercase
+ array('456DAEFb-5AA6-41B5-8DBC-068B05A8B201'), // Version 4 UUID in UPPERCASE
+ );
+ }
+
+ /**
+ * @dataProvider getInvalidStrictUuids
+ */
+ public function testInvalidStrictUuids($uuid)
+ {
+ $constraint = new Uuid(array(
+ 'message' => 'testMessage'
+ ));
+
+ $this->context->expects($this->once())
+ ->method('addViolation')
+ ->with('testMessage', array(
+ '{{ value }}' => $uuid,
+ ));
+
+ $this->validator->validate($uuid, $constraint);
+ }
+
+ public function getInvalidStrictUuids()
+ {
+ return array(
+ array('216fff40-98d9-11e3-a5e2-0800200c9a6'), // Too few characters
+ array('216fff40-98d9-11e3-a5e2-0800200c9a666'), // Too many characters
+ array('V16fff40-98d9-11e3-a5e2-0800200c9a66'), // Invalid character 'V'
+ array('2-16fff-4098d-911e3a5e20-800-200c9-a66'), // Non-standard dash positions (randomly placed)
+
+ // Non-standard UUIDs allowed by some other systems
+ array('216f-ff40-98d9-11e3-a5e2-0800-200c-9a66'), // Non-standard dash positions (every 4 chars)
+ array('216fff40-98d911e3-a5e20800-200c9a66'), // Non-standard dash positions (every 8 chars)
+ array('216fff4098d911e3a5e20800200c9a66'), // No dashes at all
+ array('{216fff40-98d9-11e3-a5e2-0800200c9a66}'), // Wrapped with curly braces
+ );
+ }
+
+ /**
+ * @dataProvider getValidStrictUuids
+ */
+ public function testVersionConstraintIsValid($uuid)
+ {
+ $this->context->expects($this->never())
+ ->method('addViolation');
+
+ $constraint = new Uuid(array(
+ 'versions' => array(Uuid::V1_MAC, Uuid::V4_RANDOM)
+ ));
+
+ $this->validator->validate($uuid, $constraint);
+ }
+
+ /**
+ * @dataProvider getValidStrictUuids
+ */
+ public function testVersionConstraintIsInvalid($uuid)
+ {
+ $constraint = new Uuid(array(
+ 'versions' => array(Uuid::V2_DCE, Uuid::V3_MD5)
+ ));
+
+ $this->context->expects($this->once())
+ ->method('addViolation');
+
+ $this->validator->validate($uuid, $constraint);
+ }
+
+ /**
+ * @dataProvider getValidNonStrictUuids
+ */
+ public function testValidNonStrictUuids($uuid)
+ {
+ $constraint = new Uuid(array(
+ 'strict' => false
+ ));
+
+ $this->context->expects($this->never())
+ ->method('addViolation');
+
+ $this->validator->validate($uuid, $constraint);
+ }
+
+ public function getValidNonStrictUuids()
+ {
+ return array(
+ array('216fff40-98d9-11e3-a5e2-0800200c9a66'), // Version 1 UUID in lowercase
+ array('216FFF40-98D9-11E3-A5E2-0800200C9A66'), // Version 1 UUID in UPPERCASE
+ array('456daefb-5aa6-41b5-8dbc-068b05a8b201'), // Version 4 UUID in lowercase
+ array('456DAEFb-5AA6-41B5-8DBC-068B05A8B201'), // Version 4 UUID in UPPERCASE
+
+ // Non-standard UUIDs allowed by some other systems
+ array('216f-ff40-98d9-11e3-a5e2-0800-200c-9a66'), // Non-standard dash positions (every 4 chars)
+ array('216fff40-98d911e3-a5e20800-200c9a66'), // Non-standard dash positions (every 8 chars)
+ array('216fff4098d911e3a5e20800200c9a66'), // No dashes at all
+ array('{216fff40-98d9-11e3-a5e2-0800200c9a66}'), // Wrapped with curly braces
+ );
+ }
+
+ /**
+ * @dataProvider getInvalidNonStrictUuids
+ */
+ public function testInvalidNonStrictUuids($uuid)
+ {
+ $constraint = new Uuid(array(
+ 'strict' => false
+ ));
+
+ $this->context->expects($this->once())
+ ->method('addViolation');
+
+ $this->validator->validate($uuid, $constraint);
+ }
+
+ public function getInvalidNonStrictUuids()
+ {
+ return array(
+ array('216fff40-98d9-11e3-a5e2-0800200c9a6'), // Too few characters
+ array('216fff40-98d9-11e3-a5e2-0800200c9a666'), // Too many characters
+ array('V16fff40-98d9-11e3-a5e2-0800200c9a66'), // Invalid character 'V'
+ array('2-16fff-4098d-911e3a5e20-800-200c9-a66'), // Non-standard dash positions (randomly placed)
+ );
+ }
+}

0 comments on commit 19931c9

Please sign in to comment.