DataValidator   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 114
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 37
c 1
b 0
f 0
dl 0
loc 114
ccs 44
cts 44
cp 1
rs 10
wmc 22

5 Methods

Rating   Name   Duplication   Size   Complexity  
A checkMap() 0 15 6
A checkArray() 0 14 6
A parseType() 0 17 4
A validateDataKey() 0 13 5
A __construct() 0 2 1
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 62
    public static function checkMap(CborMap $data, array $types, bool $complete = true): void
34
    {
35 62
        $data = $data->copy();
36 62
        foreach ($types as $key => $type) {
37 62
            $type = self::parseType($type, $key, $optional, $nullable);
38
39 61
            if ($data->has($key)) {
40 56
                self::validateDataKey($data->get($key), $key, $type, $nullable);
41 55
                $data->remove($key);
42 20
            } elseif (!$optional) {
43 11
                throw new DataValidationException(sprintf('Required key "%s" is missing in data.', $key));
44
            }
45
        }
46 48
        if ($complete && $data->count() !== 0) {
47 1
            throw new DataValidationException(sprintf('Unexpected fields in data (%s).', implode(', ', $data->getKeys())));
48
        }
49 47
    }
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 45
    public static function checkArray(array $data, array $types, bool $complete = true): void
66
    {
67 45
        foreach ($types as $key => $type) {
68 45
            $type = self::parseType($type, $key, $optional, $nullable);
69
70 44
            if (array_key_exists($key, $data)) {
71 43
                self::validateDataKey($data[$key], $key, $type, $nullable);
72 43
                unset($data[$key]);
73 34
            } elseif (!$optional) {
74 6
                throw new DataValidationException(sprintf('Required key "%s" is missing in data.', $key));
75
            }
76
        }
77 35
        if ($complete && \count($data) !== 0) {
78 1
            throw new DataValidationException(sprintf('Unexpected fields in data (%s).', implode(', ', array_keys($data))));
79
        }
80 34
    }
81
82
    /**
83
     * @param mixed $value
84
     * @param mixed $key
85
     *
86
     * @throws DataValidationException
87
     */
88 94
    private static function validateDataKey($value, $key, string $type, bool $nullable): void
89
    {
90 94
        if ($nullable && $value === null) {
91 17
            return;
92
        }
93
94 94
        $actualType = gettype($value);
95 94
        if ($actualType === 'object') {
96 48
            $actualType = get_class($value);
97
        }
98
99 94
        if ($actualType !== $type) {
100 11
            throw new DataValidationException(sprintf('Expecting key "%s" to have value of type "%s" but has type "%s".', (string) $key, $type, $actualType));
101
        }
102 93
    }
103
104
    /**
105
     * @param mixed $key
106
     */
107 102
    private static function parseType(string $type, $key, bool &$optional = null, bool &$nullable = null): string
108
    {
109 102
        $optional = false;
110 102
        $nullable = false;
111 102
        if ($type === '') {
112 2
            throw new DataValidationException(sprintf('Invalid type "%s" for key "%s".', $type, (string) $key));
113
        }
114
115 100
        if ($type[0] === '?') {
116 44
            $optional = true;
117 44
            $type = substr($type, 1);
118
        }
119 100
        if ($type[0] === ':') {
120 23
            $nullable = true;
121 23
            $type = substr($type, 1);
122
        }
123 100
        return $type;
124
    }
125
}
126