Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 36 |
|
0.00% |
0 / 1 |
CRAP | |
0.00% |
0 / 1 |
| JSONSchema | |
0.00% |
0 / 36 |
|
0.00% |
0 / 1 |
420 | |
0.00% |
0 / 1 |
| validate | |
0.00% |
0 / 36 |
|
0.00% |
0 / 1 |
420 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace MediaWiki\Extension\GLTFHandler\Parser; |
| 4 | |
| 5 | use InvalidArgumentException; |
| 6 | use function array_diff_key; |
| 7 | use function array_keys; |
| 8 | use function array_map; |
| 9 | use function array_slice; |
| 10 | use function count; |
| 11 | use function gettype; |
| 12 | use function implode; |
| 13 | use function is_array; |
| 14 | use function is_float; |
| 15 | use function is_int; |
| 16 | |
| 17 | final 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 | } |