Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
JSONSchema
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
420
0.00% covered (danger)
0.00%
0 / 1
 validate
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
420
1<?php
2
3namespace MediaWiki\Extension\GLTFHandler\Parser;
4
5use InvalidArgumentException;
6use function array_diff_key;
7use function array_keys;
8use function array_map;
9use function array_slice;
10use function count;
11use function gettype;
12use function implode;
13use function is_array;
14use function is_float;
15use function is_int;
16
17final class JSONSchema {
18
19    /** @var int all keys are optional - do not bail if key does not exist */
20    public const FLAG_OPTIONAL = 1 << 0;
21    /** @var int bail upon encountering a key that is not defined in schema */
22    public const FLAG_REPORT_UNKNOWN_KEYS = 1 << 1;
23    /** @var int do not nest - only validate the parent level */
24    public const FLAG_NO_NESTING = 1 << 2;
25
26    /**
27     * @param mixed $json a parsed json output
28     * @param array $schema a schema detailing structure and type
29     * @param list<string|int> $base base directory to move to in $json
30     * @param int $flags Bitmask of self::FLAG_* constants
31     * @param int $code error code to use when an error encounters
32     */
33    public static function validate( mixed $json, array $schema, array $base = [], int $flags = 0, int $code = GLTFParser::ERR_INVALID_SCHEMA ): void {
34        $optional = ( $flags & self::FLAG_OPTIONAL ) > 0;
35        $report_unknown_k = ( $flags & self::FLAG_REPORT_UNKNOWN_KEYS ) > 0;
36        $nesting = ( $flags & self::FLAG_NO_NESTING ) === 0;
37
38        foreach ( $base as $offset => $key ) {
39            is_array( $json ) || throw new InvalidArgumentException( "Directory /" . implode( "/", array_slice( $base, 0, $offset + 1 ) ) . " must be an array", $code );
40            isset( $json[$key] ) || throw new InvalidArgumentException( "Directory /" . implode( "/", array_slice( $base, 0, $offset + 1 ) ) . " must be set", $code );
41            $json = $json[$key];
42        }
43
44        $buffer = [];
45        foreach ( $schema as $k => $v ) {
46            $buffer[] = [ [ $k ], $v, 0, $base ];
47        }
48        $index = 0;
49        while ( isset( $buffer[$index] ) ) {
50            [ $keys, $expect, $depth, $directory ] = $buffer[$index++];
51            $sub_schema = $schema;
52            $value = $json;
53            $directory_cur = $directory;
54            foreach ( $keys as $key ) {
55                $directory_cur[] = $key;
56                is_array( $value ) || throw new InvalidArgumentException( "Directory /" . implode( "/", $directory_cur ) . " must be an array", $code );
57                if ( $report_unknown_k && is_array( $sub_schema ) ) {
58                    $unknown_k = array_keys( array_diff_key( $value, $sub_schema ) );
59                    count( $unknown_k ) === 0 || throw new InvalidArgumentException( "Unknown sub-directory encountered in /" . implode( "/", $directory_cur ) . ": [" . implode( ", ", array_slice( $unknown_k, 0, 8 ) ) . "], expected one of: [" . implode( ", ", array_keys( $sub_schema ) ) . "]", $code );
60                }
61                if ( $optional && !isset( $value[$key] ) ) {
62                    continue 2;
63                }
64                isset( $value[$key] ) || throw new InvalidArgumentException( "Directory /" . implode( "/", $directory_cur ) . " must be set", $code );
65                $value = $value[$key];
66                $sub_schema = $sub_schema[$key];
67            }
68            $directory_path = implode( "/", array_map( 'strval', $directory_cur ?? [] ) );
69            gettype( $value ) === gettype( $expect ) || ( is_float( $expect ) && is_int( $value ) ) || throw new InvalidArgumentException( "Expected type of value at /{$directory_path} to be " . gettype( $expect ) . ", got " . gettype( $value ), $code );
70            if ( is_array( $expect ) && $nesting ) {
71                foreach ( $expect as $k => $v ) {
72                    $keys2 = $keys;
73                    $keys2[] = $k;
74                    $directory_next = $directory;
75                    $directory_next[] = $k;
76                    $buffer[] = [ $keys2, $v, $depth + 1, $directory_next ];
77                }
78            }
79        }
80    }
81}