Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.74% covered (success)
94.74%
36 / 38
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
JsonSchemaValidator
94.74% covered (success)
94.74%
36 / 38
75.00% covered (warning)
75.00%
6 / 8
15.03
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 areSchemasSupported
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSchemaBuilder
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validateStrictly
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validatePermissively
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validate
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
5
 getSchemaVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSchemaIterator
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\CommunityConfiguration\Validation;
4
5use IBufferingStatsdDataFactory;
6use InvalidArgumentException;
7use Iterator;
8use JsonSchema\Validator;
9use MediaWiki\Extension\CommunityConfiguration\Schema\JsonSchema;
10use MediaWiki\Extension\CommunityConfiguration\Schema\JsonSchemaBuilder;
11use MediaWiki\Extension\CommunityConfiguration\Schema\JsonSchemaIterator;
12use MediaWiki\Extension\CommunityConfiguration\Schema\JsonSchemaReader;
13use MediaWiki\Extension\CommunityConfiguration\Schema\SchemaBuilder;
14
15/**
16 * JSON Schema validator.
17 */
18class JsonSchemaValidator implements IValidator {
19
20    private JsonSchemaReader $jsonSchema;
21    private JsonSchemaBuilder $jsonSchemaBuilder;
22    private Iterator $jsonSchemaIterator;
23    private IBufferingStatsdDataFactory $statsdDataFactory;
24
25    /**
26     * @param JsonSchema|string $classNameOrClassInstance JsonSchema derived class name (instance only allowed in tests)
27     * @param IBufferingStatsdDataFactory $statsdDataFactory
28     */
29    public function __construct(
30        $classNameOrClassInstance,
31        IBufferingStatsdDataFactory $statsdDataFactory
32    ) {
33        // @codeCoverageIgnoreStart
34        if ( is_object( $classNameOrClassInstance ) ) {
35            if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
36                throw new InvalidArgumentException(
37                    'JsonSchema should never be instantiated in production code'
38                );
39            }
40            if ( !( $classNameOrClassInstance instanceof JsonSchema ) ) {
41                throw new InvalidArgumentException(
42                    get_class( $classNameOrClassInstance ) . ' must be instance of ' . JsonSchema::class
43                );
44            }
45        }
46        // @codeCoverageIgnoreEnd
47
48        $this->jsonSchema = new JsonSchemaReader( $classNameOrClassInstance );
49        $this->jsonSchemaBuilder = new JsonSchemaBuilder( $statsdDataFactory, $this->jsonSchema );
50        $this->jsonSchemaIterator = new JsonSchemaIterator( $this->jsonSchema );
51        $this->statsdDataFactory = $statsdDataFactory;
52    }
53
54    /**
55     * @inheritDoc
56     */
57    public function areSchemasSupported(): bool {
58        return true;
59    }
60
61    /**
62     * @inheritDoc
63     */
64    public function getSchemaBuilder(): SchemaBuilder {
65        return $this->jsonSchemaBuilder;
66    }
67
68    /**
69     * @inheritDoc
70     */
71    public function validateStrictly( $config ): ValidationStatus {
72        return $this->validate( $config, false );
73    }
74
75    /**
76     * @inheritDoc
77     */
78    public function validatePermissively( $config ): ValidationStatus {
79        return $this->validate( $config, true );
80    }
81
82    /**
83     * @param mixed $config
84     * @param bool $modeForReading
85     * @return ValidationStatus
86     */
87    private function validate( $config, bool $modeForReading ): ValidationStatus {
88        $start = microtime( true );
89
90        $validator = new Validator();
91        $validator->validate(
92            $config,
93            $this->jsonSchemaBuilder->getRootSchema()
94        );
95        if ( $validator->isValid() ) {
96            return ValidationStatus::newGood();
97        }
98        $status = new ValidationStatus();
99        foreach ( $validator->getErrors() as $error ) {
100            if ( $modeForReading && in_array( $error['constraint'], [ 'required', 'additionalProp', 'enum' ] ) ) {
101                $status->addWarning(
102                    $error['property'],
103                    $error['pointer'],
104                    $error['message'],
105                    [ 'constraint' => $error['constraint'] ],
106                );
107            } else {
108                $status->addFatal(
109                    $error['property'],
110                    $error['pointer'],
111                    $error['message'],
112                    [ 'constraint' => $error['constraint'] ],
113                );
114            }
115        }
116
117        $this->statsdDataFactory->timing(
118            'timing.communityConfiguration.JsonSchemaValidator.validate',
119            microtime( true ) - $start
120        );
121        return $status;
122    }
123
124    /**
125     * @inheritDoc
126     */
127    public function getSchemaVersion(): ?string {
128        return $this->jsonSchema->getVersion();
129    }
130
131    public function getSchemaIterator(): Iterator {
132        return $this->jsonSchemaIterator;
133    }
134}