Passed
Push — master ( 8e672b...9eb68a )
by Thomas
02:40
created

DataValidator::checkArray()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 9
nc 7
nop 3
dl 0
loc 14
ccs 10
cts 10
cp 1
crap 6
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
namespace MadWizard\WebAuthn\Format;
4
5
use MadWizard\WebAuthn\Exception\DataValidationException;
6
use function array_key_exists;
7
use function get_class;
8
use function gettype;
9
10
final class DataValidator
11
{
12
    /**
13
     * @codeCoverageIgnore
14
     */
15
    private function __construct()
16
    {
17
    }
18
19
    /**
20
     * @param CborMap $data     CBOR map to valdiate
21
     * @param array   $types    Expected types in the CBOR map. Keys match with the keys from the map, the values
22
     *                          of this array are the expected types as strings. In case of objects this is the fully qualified classname, for
23
     *                          other types this can be any of the return values of PHP's gettype() function. When a type is prefixed with `?`
24
     *                          it is optional and not required to be in the data array.
25
     * @param bool    $complete Indicates whether the $types parameter completely covers the data. If any additional fields
26
     *                          are found in the $data map that are not in the $types array an exception is thrown. When false additional
27
     *                          fields are ignored.
28
     *
29
     * @throws DataValidationException
30
     *
31
     * @return void Returns nothing but only returns when the data is valid. Otherwise an exception is thrown.
32
     */
33 57
    public static function checkMap(CborMap $data, array $types, bool $complete = true): void
34
    {
35 57
        $data = $data->copy();
36 57
        foreach ($types as $key => $type) {
37 57
            $type = self::parseType($type, $key, $optional, $nullable);
38
39 56
            if ($data->has($key)) {
40 53
                self::validateDataKey($data->get($key), $key, $type, $nullable);
41 52
                $data->remove($key);
42 18
            } elseif (!$optional) {
43 9
                throw new DataValidationException(sprintf('Required key "%s" is missing in data.', $key));
44
            }
45
        }
46 45
        if ($complete && $data->count() !== 0) {
47 1
            throw new DataValidationException(sprintf('Unexpected fields in data (%s).', implode(', ', $data->getKeys())));
48
        }
49 44
    }
50
51
    /**
52
     * @param array $data     Data array to valdiate
53
     * @param array $types    Expected types in the data array. Keys match with the keys from the data array, the values
54
     *                        of this array are the expected types as strings. In case of objects this is the fully qualified classname, for
55
     *                        other types this can be any of the return values of PHP's gettype() function. When a type is prefixed with `?`
56
     *                        it is optional and not required to be in the data array.
57
     * @param bool  $complete Indicates whether the $types parameter completely covers the data. If any additional fields
58
     *                        are found in the $data array that are not in the $types array an exception is thrown. When false additional
59
     *                        fields are ignored.
60
     *
61
     * @throws DataValidationException
62
     *
63
     * @return void Returns nothing but only returns when the data is valid. Otherwise an exception is thrown.
64
     */
65 40
    public static function checkArray(array $data, array $types, bool $complete = true): void
66
    {
67 40
        foreach ($types as $key => $type) {
68 40
            $type = self::parseType($type, $key, $optional, $nullable);
69
70 39
            if (array_key_exists($key, $data)) {
71 38
                self::validateDataKey($data[$key], $key, $type, $nullable);
72 38
                unset($data[$key]);
73 29
            } elseif (!$optional) {
74 6
                throw new DataValidationException(sprintf('Required key "%s" is missing in data.', $key));
75
            }
76
        }
77 30
        if ($complete && \count($data) !== 0) {
78 1
            throw new DataValidationException(sprintf('Unexpected fields in data (%s).', implode(', ', array_keys($data))));
79
        }
80 29
    }
81
82 86
    private static function validateDataKey($value, $key, string $type, bool $nullable): void
83
    {
84 86
        if ($nullable && $value === null) {
85 17
            return;
86
        }
87
88 86
        $actualType = gettype($value);
89 86
        if ($actualType === 'object') {
90 45
            $actualType = get_class($value);
91
        }
92
93 86
        if ($actualType !== $type) {
94 11
            throw new DataValidationException(sprintf('Expecting key "%s" to be of type "%s" but has type "%s".', $key, $type, $actualType));
95
        }
96 85
    }
97
98 92
    private static function parseType(string $type, $key, bool &$optional = null, bool &$nullable = null)
99
    {
100 92
        $optional = false;
101 92
        $nullable = false;
102 92
        if ($type === '') {
103 2
            throw new DataValidationException(sprintf('Invalid type "%s" for key "%s".', $type, (string) $key));
104
        }
105
106 90
        if ($type[0] === '?') {
107 39
            $optional = true;
108 39
            $type = substr($type, 1);
109
        }
110 90
        if ($type[0] === ':') {  // TODO tests
111 23
            $nullable = true;
112 23
            $type = substr($type, 1);
113
        }
114 90
        return $type;
115
    }
116
}
117