Completed
Push — master ( 87b25f...b603aa )
by Viacheslav
10s
created

Format::dateTimeError()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 8.0231

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 22
ccs 13
cts 14
cp 0.9286
rs 8.4444
c 0
b 0
f 0
cc 8
nc 7
nop 1
crap 8.0231
1
<?php
2
3
namespace Swaggest\JsonSchema\Constraint;
4
5
use Swaggest\JsonSchema\Constraint\Format\IdnHostname;
6
use Swaggest\JsonSchema\Constraint\Format\Iri;
7
use Swaggest\JsonSchema\Constraint\Format\Uri;
8
9
class Format
10
{
11
    const DATE_TIME = 'date-time';
12
    const DATE = 'date';
13
    const TIME = 'time';
14
    const URI = 'uri';
15
    const IRI = 'iri';
16
    const EMAIL = 'email';
17
    const IDN_EMAIL = 'idn-email';
18
    const IPV4 = 'ipv4';
19
    const IPV6 = 'ipv6';
20
    const HOSTNAME = 'hostname';
21
    const IDN_HOSTNAME = 'idn-hostname';
22
    const REGEX = 'regex';
23
    const JSON_POINTER = 'json-pointer';
24
    const RELATIVE_JSON_POINTER = 'relative-json-pointer';
25
    const URI_REFERENCE = 'uri-reference';
26
    const IRI_REFERENCE = 'iri-reference';
27
    const URI_TEMPLATE = 'uri-template';
28
29
    public static $strictDateTimeValidation = false;
30
31
    private static $dateRegexPart = '(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])';
32
    private static $timeRegexPart = '([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))?';
33
    private static $jsonPointerRegex = '_^(?:/|(?:/[^/#]*)*)$_';
34
    private static $jsonPointerRelativeRegex = '~^(0|[1-9][0-9]*)((?:/[^/#]*)*)(#?)$~';
35
    private static $jsonPointerUnescapedTilde = '/~([^01]|$)/';
36
37 436
    public static function validationError($format, $data)
38
    {
39
        switch ($format) {
40 436
            case self::DATE_TIME:
41 36
                return self::dateTimeError($data);
42 400
            case self::DATE:
43 6
                return preg_match('/^' . self::$dateRegexPart . '$/i', $data) ? null : 'Invalid date';
44 394
            case self::TIME:
45 9
                return preg_match('/^' . self::$timeRegexPart . '$/i', $data) ? null : 'Invalid time';
46 385
            case self::URI:
47 164
                return Uri::validationError($data, Uri::IS_SCHEME_REQUIRED);
48 234
            case self::IRI:
49 8
                return Iri::validationError($data);
50 226
            case self::EMAIL:
51 9
                return filter_var($data, FILTER_VALIDATE_EMAIL) ? null : 'Invalid email';
52 218
            case self::IDN_EMAIL:
53 2
                return count(explode('@', $data, 3)) === 2 ? null : 'Invalid email';
54 216
            case self::IPV4:
55 16
                return filter_var($data, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? null : 'Invalid ipv4';
56 200
            case self::IPV6:
57 12
                return filter_var($data, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? null : 'Invalid ipv6';
58 188
            case self::HOSTNAME:
59 16
                return preg_match(Uri::HOSTNAME_REGEX, $data) ? null : 'Invalid hostname';
60 173
            case self::IDN_HOSTNAME:
61 4
                return IdnHostname::validationError($data);
62 169
            case self::REGEX:
63 53
                return self::regexError($data);
64 116
            case self::JSON_POINTER:
65 70
                return self::jsonPointerError($data);
66 46
            case self::RELATIVE_JSON_POINTER:
67 14
                return self::jsonPointerError($data, true);
68 32
            case self::URI_REFERENCE:
69 14
                return Uri::validationError($data, Uri::IS_URI_REFERENCE);
70 18
            case self::IRI_REFERENCE:
71 7
                return Iri::validationError($data, Uri::IS_URI_REFERENCE);
72 11
            case self::URI_TEMPLATE:
73 10
                return Uri::validationError($data, Uri::IS_URI_TEMPLATE);
74
        }
75 1
        return null;
76
    }
77
78 36
    public static function dateTimeError($data)
79
    {
80 36
        if (!preg_match('/^' . self::$dateRegexPart . 'T' . self::$timeRegexPart . '$/i', $data)) {
81 14
            return 'Invalid date-time format: ' . $data;
82
        }
83
84 22
        if (self::$strictDateTimeValidation) {
85 20
            $dt = date_create($data);
86 20
            if ($dt === false) {
87
                return 'Failed to parse date-time: ' . $data;
88
            }
89 20
            $isLeapSecond = '6' === $data[17] && (
90 4
                    0 === strpos(substr($data, 5, 5), '12-31') ||
91 20
                    0 === strpos(substr($data, 5, 5), '06-30')
92
                );
93 20
            if (!$isLeapSecond &&
94 20
                0 !== stripos($dt->format(DATE_RFC3339), substr($data, 0, 19))) {
95 3
                return 'Invalid date-time value: ' . $data;
96
            }
97
        }
98
99 19
        return null;
100
    }
101
102 53
    public static function regexError($data)
103
    {
104 53
        if (substr($data, -2) === '\Z') {
105 3
            return 'Invalid regex: \Z is not supported';
106
        }
107 50
        if (substr($data, 0, 2) === '\A') {
108
            return 'Invalid regex: \A is not supported';
109
        }
110
111
112 50
        $d = null;
113 50
        foreach (array('/', '_', '~', '#', '!', '%', '`', '=') as $delimiter) {
114 50
            if (strpos($data, $delimiter) === false) {
115 50
                $d = $delimiter;
116 50
                break;
117
            }
118
        }
119 50
        return @preg_match($d . $data . $d, '') === false ? 'Invalid regex: ' . $data : null;
120
    }
121
122 84
    public static function jsonPointerError($data, $isRelative = false)
123
    {
124 84
        if (preg_match(self::$jsonPointerUnescapedTilde, $data)) {
125 14
            return 'Invalid json-pointer: unescaped ~';
126
        }
127 70
        if ($isRelative) {
128 13
            return preg_match(self::$jsonPointerRelativeRegex, $data) ? null : 'Invalid relative json-pointer';
129
        } else {
130 57
            return preg_match(self::$jsonPointerRegex, $data) ? null : 'Invalid json-pointer';
131
        }
132
    }
133
}