8000 Added new validator for UUIDs · symfony/symfony@19931c9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 19931c9

Browse files
colinodellfabpot
authored andcommitted
Added new validator for UUIDs
1 parent 790ba4c commit 19931c9

File tree

3 files changed

+344
-0
lines changed

3 files changed

+344
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
16+
/**
17+
* @Annotation
18+
*
19+
* @author Colin O'Dell <colinodell@gmail.com>
20+
*/
21+
class Uuid extends Constraint
22+
{
23+
// Possible versions defined by RFC 4122
24+
const V1_MAC = 1;
25+
const V2_DCE = 2;
26+
const V3_MD5 = 3;
27+
const V4_RANDOM = 4;
28+
const V5_SHA1 = 5;
29+
30+
/**
31+
* Message to display when validation fails
32+
*
33+
* @var string
34+
*/
35+
public $message = 'This is not a valid UUID.';
36+
37+
/**
38+
* Strict mode only allows UUIDs that meet the formal definition and formatting per RFC 4122
39+
*
40+
* Set this to `false` to allow legacy formats with different dash positioning or wrapping characters
41+
*
42+
* @var bool
43+
*/
44+
public $strict = true;
45+
46+
/**
47+
* Array of allowed versions (see version constants above)
48+
*
49+
* All UUID versions are allowed by default
50+
*
51+
* @var int[]
52+
*/
53+
public $versions = array(
54+
self::V1_MAC,
55+
self::V2_DCE,
56+
self::V3_MD5,
57+
self::V4_RANDOM,
58+
self::V5_SHA1
59+
);
60+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\ConstraintValidator;
16+
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
17+
18+
/**
19+
* Validates whether the value is a valid UUID per RFC 4122.
20+
*
21+
* @author Colin O'Dell <colinodell@gmail.com>
22+
*
23+
* @see http://tools.ietf.org/html/rfc4122
24+
* @see https://en.wikipedia.org/wiki/Universally_unique_identifier
25+
*/
26+
class UuidValidator extends ConstraintValidator
27+
{
28+
/**
29+
* Regular expression which verifies allowed characters and the proper format.
30+
*
31+
* The strict pattern matches UUIDs like this: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
32+
* Roughly speaking: x = any hexadecimal character, M = any allowed version, N = any allowed variant.
33+
*/
34+
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';
35+
36+
/**
37+
* The loose pattern validates similar yet non-compliant UUIDs.
38+
*
39+
* Dashes are completely optional. If present, they should only appear between every fourth character.
40+
* The value can also be wrapped with characters like []{} for backwards-compatibility with other systems.
41+
* Neither the version nor the variant is validated by this pattern.
42+
*/
43+
const LOOSE_PATTERN = '/^[a-f0-9]{4}(?:-?[a-f0-9]{4}){7}$/i';
44+
45+
/**
46+
* Properly-formatted UUIDs contain 32 hex digits, separated by 4 dashes.
47+
* We can use this fact to avoid performing a preg_match on strings of other sizes.
48+
*/
49+
const STRICT_UUID_LENGTH = 36;
50+
51+
/**
52+
* {@inheritDoc}
53+
*/
54+
public function validate($value, Constraint $constraint)
55+
{
56+
if (null === $value || '' === $value) {
57+
return;
58+
}
59+
60+
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
61+
throw new UnexpectedTypeException($value, 'string');
62+
}
63+
64+
$value = (string) $value;
65+
66+
if ($constraint->strict) {
67+
// Insert the allowed versions into the regular expression
68+
$pattern = sprintf(static::STRICT_PATTERN, implode('', $constraint->versions));
69+
70+
if (strlen($value) !== static::STRICT_UUID_LENGTH || !preg_match($pattern, $value)) {
71+
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
72+
}
73+
} else {
74+
// Trim any wrapping characters like [] or {} used by some legacy systems
75+
$value = trim($value, '[]{}');
76+
77+
if (!preg_match(static::LOOSE_PATTERN, $value)) {
78+
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
79+
}
80+
}
81+
}
82+
}
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Constraints;
13+
14+
use Symfony\Component\Validator\Constraints\Uuid;
15+
use Symfony\Component\Validator\Constraints\UuidValidator;
16+
17+
/**
18+
* @author Colin O'Dell <colinodell@gmail.com>
19+
*/
20+
class UuidValidatorTest extends \PHPUnit_Framework_TestCase
21+
{
22+
protected $context;
23+
protected $validator;
24+
25+
protected function setUp()
26+
{
27+
$this->context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false);
28+
$this->validator = new UuidValidator();
29+
$this->validator->initialize($this->context);
30+
}
31+
32+
protected function tearDown()
33+
{
34+
$this->context = null;
35+
$this->validator = null;
36+
}
37+
38+
public function testNullIsValid()
39+
{
40+
$this->context->expects($this->never())
41+
->method('addViolation');
42+
43+
$this->validator->validate(null, new Uuid());
44+
}
45+
46+
public function testEmptyStringIsValid()
47+
{
48+
$this->context->expects($this->never())
49+
->method('addViolation');
50+
51+
$this->validator->validate('', new Uuid());
52+
}
53+
54+
/**
55+
* @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException
56+
*/
57+
public function testExpectsStringCompatibleType()
58+
{
59+
$this->validator->validate(new \stdClass(), new Uuid());
60+
}
61+
62+
/**
63+
* @dataProvider getValidStrictUuids
64+
*/
65+
public function testValidStrictUuids($uuid)
66+
{
67+
$this->context->expects($this->never())
68+
->method('addViolation');
69+
70+
$this->validator->validate($uuid, new Uuid());
71+
}
72+
73+
public function getValidStrictUuids()
74+
{
75+
return array(
76+
array('216fff40-98d9-11e3-a5e2-0800200c9a66'), // Version 1 UUID in lowercase
77+
array('216FFF40-98D9-11E3-A5E2-0800200C9A66'), // Version 1 UUID in UPPERCASE
78+
array('456daefb-5aa6-41b5-8dbc-068b05a8b201'), // Version 4 UUID in lowercase
79+
array('456DAEFb-5AA6-41B5-8DBC-068B05A8B201'), // Version 4 UUID in UPPERCASE
80+
);
81+
}
82+
83+
/**
84+
* @dataProvider getInvalidStrictUuids
85+
*/
86+
public function testInvalidStrictUuids($uuid)
87+
{
88+
$constraint = new Uuid(array(
89+
'message' => 'testMessage'
90+
));
91+
92+
$this->context->expects($this->once())
93+
->method('addViolation')
94+
->with('testMessage', array(
95+
'{{ value }}' => $uuid,
96+
));
97+
98+
$this->validator->validate($uuid, $constraint);
99+
}
100+
101+
public function getInvalidStrictUuids()
102+
{
103+
return array(
104+
array('216fff40-98d9-11e3-a5e2-0800200c9a6'), // Too few characters
105+
array('216fff40-98d9-11e3-a5e2-0800200c9a666'), // Too many characters
106+
array('V16fff40-98d9-11e3-a5e2-0800200c9a66'), // Invalid character 'V'
107+
array('2-16fff-4098d-911e3a5e20-800-200c9-a66'), // Non-standard dash positions (randomly placed)
108+
109+
// Non-standard UUIDs allowed by some other systems
110+
array('216f-ff40-98d9-11e3-a5e2-0800-200c-9a66'), // Non-standard dash positions (every 4 chars)
111+
array('216fff40-98d911e3-a5e20800-200c9a66'), // Non-standard dash positions (every 8 chars)
112+
array('216fff4098d911e3a5e20800200c9a66'), // No dashes at all
113+
array('{216fff40-98d9-11e3-a5e2-0800200c9a66}'), // Wrapped with curly braces
114+
);
115+
}
116+
117+
/**
118+
* @dataProvider getValidStrict 10000 Uuids
119+
*/
120+
public function testVersionConstraintIsValid($uuid)
121+
{
122+
$this->context->expects($this->never())
123+
->method('addViolation');
124+
125+
$constraint = new Uuid(array(
126+
'versions' => array(Uuid::V1_MAC, Uuid::V4_RANDOM)
127+
));
128+
129+
$this->validator->validate($uuid, $constraint);
130+
}
131+
132+
/**
133+
* @dataProvider getValidStrictUuids
134+
*/
135+
public function testVersionConstraintIsInvalid($uuid)
136+
{
137+
$constraint = new Uuid(array(
138+
'versions' => array(Uuid::V2_DCE, Uuid::V3_MD5)
139+
));
140+
141+
$this->context->expects($this->once())
142+
->method('addViolation');
143+
144+
$this->validator->validate($uuid, $constraint);
145+
}
146+
147+
/**
148+
* @dataProvider getValidNonStrictUuids
149+
*/
150+
public function testValidNonStrictUuids($uuid)
151+
{
152+
$constraint = new Uuid(array(
153+
'strict' => false
154+
));
155+
156+
$this->context->expects($this->never())
157+
->method('addViolation');
158+
159+
$this->validator->validate($uuid, $constraint);
160+
}
161+
162+
public function getValidNonStrictUuids()
163+
{
164+
return array(
165+
array('216fff40-98d9-11e3-a5e2-0800200c9a66'), // Version 1 UUID in lowercase
166+
array('216FFF40-98D9-11E3-A5E2-0800200C9A66'), // Version 1 UUID in UPPERCASE
167+
array('456daefb-5aa6-41b5-8dbc-068b05a8b201'), // Version 4 UUID in lowercase
168+
array('456DAEFb-5AA6-41B5-8DBC-068B05A8B201'), // Version 4 UUID in UPPERCASE
169+
170+
// Non-standard UUIDs allowed by some other systems
171+
array('216f-ff40-98d9-11e3-a5e2-0800-200c-9a66'), // Non-standard dash positions (every 4 chars)
172+
array('216fff40-98d911e3-a5e20800-200c9a66'), // Non-standard dash positions (every 8 chars)
173+
array('216fff4098d911e3a5e20800200c9a66'), // No dashes at all
174+
array('{216fff40-98d9-11e3-a5e2-0800200c9a66}'), // Wrapped with curly braces
175+
);
176+
}
177+
178+
/**
179+
* @dataProvider getInvalidNonStrictUuids
180+
*/
181+
public function testInvalidNonStrictUuids($uuid)
182+
{
183+
$constraint = new Uuid(array(
184+
'strict' => false
185+
));
186+
187+
$this->context->expects($this->once())
188+
->method('addViolation');
189+
190+
$this->validator->validate($uuid, $constraint);
191+
}
192+
193+
public function getInvalidNonStrictUuids()
194+
{
195+
return array(
196+
array('216fff40-98d9-11e3-a5e2-0800200c9a6'), // Too few characters
197+
array('216fff40-98d9-11e3-a5e2-0800200c9a666'), // Too many characters
198+
array('V16fff40-98d9-11e3-a5e2-0800200c9a66'), // Invalid character 'V'
199+
array('2-16fff-4098d-911e3a5e20-800-200c9-a66'), // Non-standard dash positions (randomly placed)
200+
);
201+
}
202+
}

0 commit comments

Comments
 (0)
0