Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
69.35% |
86 / 124 |
|
70.00% |
7 / 10 |
CRAP | |
0.00% |
0 / 1 |
CheckConstraintParameters | |
69.35% |
86 / 124 |
|
70.00% |
7 / 10 |
45.46 | |
0.00% |
0 / 1 |
newFromGlobalState | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
__construct | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
parsePropertyIds | |
45.00% |
9 / 20 |
|
0.00% |
0 / 1 |
6.66 | |||
parseConstraintIds | |
39.29% |
11 / 28 |
|
0.00% |
0 / 1 |
10.60 | |||
checkPropertyIds | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
checkConstraintIds | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
getResultPathForPropertyId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getResultPathForConstraintId | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
addConstraintParameterExceptionsToResult | |
100.00% |
30 / 30 |
|
100.00% |
1 / 1 |
4 | |||
getAllowedParams | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
getExamplesMessages | n/a |
0 / 0 |
n/a |
0 / 0 |
1 |
1 | <?php |
2 | |
3 | declare( strict_types = 1 ); |
4 | |
5 | namespace WikibaseQuality\ConstraintReport\Api; |
6 | |
7 | use InvalidArgumentException; |
8 | use MediaWiki\Api\ApiBase; |
9 | use MediaWiki\Api\ApiMain; |
10 | use MediaWiki\Api\ApiResult; |
11 | use Wikibase\DataModel\Entity\NumericPropertyId; |
12 | use Wikibase\DataModel\Services\Statement\StatementGuidParser; |
13 | use Wikibase\DataModel\Services\Statement\StatementGuidParsingException; |
14 | use Wikibase\Lib\LanguageFallbackChainFactory; |
15 | use Wikibase\Repo\Api\ApiErrorReporter; |
16 | use Wikibase\Repo\Api\ApiHelperFactory; |
17 | use WikibaseQuality\ConstraintReport\ConstraintCheck\DelegatingConstraintChecker; |
18 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterException; |
19 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessageRendererFactory; |
20 | use Wikimedia\ParamValidator\ParamValidator; |
21 | use Wikimedia\Stats\IBufferingStatsdDataFactory; |
22 | |
23 | /** |
24 | * API module that checks whether the parameters of a constraint statement are valid. |
25 | * |
26 | * @author Lucas Werkmeister |
27 | * @license GPL-2.0-or-later |
28 | */ |
29 | class CheckConstraintParameters extends ApiBase { |
30 | |
31 | public const PARAM_PROPERTY_ID = 'propertyid'; |
32 | public const PARAM_CONSTRAINT_ID = 'constraintid'; |
33 | public const KEY_STATUS = 'status'; |
34 | public const STATUS_OKAY = 'okay'; |
35 | public const STATUS_NOT_OKAY = 'not-okay'; |
36 | public const STATUS_NOT_FOUND = 'not-found'; |
37 | public const KEY_PROBLEMS = 'problems'; |
38 | public const KEY_MESSAGE_HTML = 'message-html'; |
39 | |
40 | private ApiErrorReporter $apiErrorReporter; |
41 | private LanguageFallbackChainFactory $languageFallbackChainFactory; |
42 | private DelegatingConstraintChecker $delegatingConstraintChecker; |
43 | private ViolationMessageRendererFactory $violationMessageRendererFactory; |
44 | private StatementGuidParser $statementGuidParser; |
45 | private IBufferingStatsdDataFactory $dataFactory; |
46 | |
47 | /** |
48 | * Creates new instance from global state. |
49 | */ |
50 | public static function newFromGlobalState( |
51 | ApiMain $main, |
52 | string $name, |
53 | IBufferingStatsdDataFactory $dataFactory, |
54 | ApiHelperFactory $apiHelperFactory, |
55 | LanguageFallbackChainFactory $languageFallbackChainFactory, |
56 | StatementGuidParser $statementGuidParser, |
57 | DelegatingConstraintChecker $delegatingConstraintChecker, |
58 | ViolationMessageRendererFactory $violationMessageRendererFactory |
59 | ): self { |
60 | return new self( |
61 | $main, |
62 | $name, |
63 | $apiHelperFactory, |
64 | $languageFallbackChainFactory, |
65 | $delegatingConstraintChecker, |
66 | $violationMessageRendererFactory, |
67 | $statementGuidParser, |
68 | $dataFactory |
69 | ); |
70 | } |
71 | |
72 | public function __construct( |
73 | ApiMain $main, |
74 | string $name, |
75 | ApiHelperFactory $apiHelperFactory, |
76 | LanguageFallbackChainFactory $languageFallbackChainFactory, |
77 | DelegatingConstraintChecker $delegatingConstraintChecker, |
78 | ViolationMessageRendererFactory $violationMessageRendererFactory, |
79 | StatementGuidParser $statementGuidParser, |
80 | IBufferingStatsdDataFactory $dataFactory |
81 | ) { |
82 | parent::__construct( $main, $name ); |
83 | |
84 | $this->apiErrorReporter = $apiHelperFactory->getErrorReporter( $this ); |
85 | $this->languageFallbackChainFactory = $languageFallbackChainFactory; |
86 | $this->delegatingConstraintChecker = $delegatingConstraintChecker; |
87 | $this->violationMessageRendererFactory = $violationMessageRendererFactory; |
88 | $this->statementGuidParser = $statementGuidParser; |
89 | $this->dataFactory = $dataFactory; |
90 | } |
91 | |
92 | public function execute() { |
93 | $this->dataFactory->increment( |
94 | 'wikibase.quality.constraints.api.checkConstraintParameters.execute' |
95 | ); |
96 | |
97 | $params = $this->extractRequestParams(); |
98 | $result = $this->getResult(); |
99 | |
100 | $propertyIds = $this->parsePropertyIds( $params[self::PARAM_PROPERTY_ID] ); |
101 | $constraintIds = $this->parseConstraintIds( $params[self::PARAM_CONSTRAINT_ID] ); |
102 | |
103 | $this->checkPropertyIds( $propertyIds, $result ); |
104 | $this->checkConstraintIds( $constraintIds, $result ); |
105 | |
106 | $result->addValue( null, 'success', 1 ); |
107 | } |
108 | |
109 | /** |
110 | * @param array|null $propertyIdSerializations |
111 | * @return NumericPropertyId[] |
112 | */ |
113 | private function parsePropertyIds( ?array $propertyIdSerializations ): array { |
114 | if ( $propertyIdSerializations === null ) { |
115 | return []; |
116 | } elseif ( $propertyIdSerializations === [] ) { |
117 | $this->apiErrorReporter->dieError( |
118 | 'If ' . self::PARAM_PROPERTY_ID . ' is specified, it must be nonempty.', |
119 | 'no-data' |
120 | ); |
121 | } |
122 | |
123 | return array_map( |
124 | function ( $propertyIdSerialization ) { |
125 | try { |
126 | return new NumericPropertyId( $propertyIdSerialization ); |
127 | } catch ( InvalidArgumentException $e ) { |
128 | $this->apiErrorReporter->dieError( |
129 | "Invalid id: $propertyIdSerialization", |
130 | 'invalid-property-id', |
131 | 0, // default argument |
132 | [ self::PARAM_PROPERTY_ID => $propertyIdSerialization ] |
133 | ); |
134 | } |
135 | }, |
136 | $propertyIdSerializations |
137 | ); |
138 | } |
139 | |
140 | /** |
141 | * @param array|null $constraintIds |
142 | * @return string[] |
143 | */ |
144 | private function parseConstraintIds( ?array $constraintIds ): array { |
145 | if ( $constraintIds === null ) { |
146 | return []; |
147 | } elseif ( $constraintIds === [] ) { |
148 | $this->apiErrorReporter->dieError( |
149 | 'If ' . self::PARAM_CONSTRAINT_ID . ' is specified, it must be nonempty.', |
150 | 'no-data' |
151 | ); |
152 | } |
153 | |
154 | return array_map( |
155 | function ( $constraintId ) { |
156 | try { |
157 | $propertyId = $this->statementGuidParser->parse( $constraintId )->getEntityId(); |
158 | if ( !$propertyId instanceof NumericPropertyId ) { |
159 | $this->apiErrorReporter->dieError( |
160 | "Invalid property ID: {$propertyId->getSerialization()}", |
161 | 'invalid-property-id', |
162 | 0, // default argument |
163 | [ self::PARAM_CONSTRAINT_ID => $constraintId ] |
164 | ); |
165 | } |
166 | return $constraintId; |
167 | } catch ( StatementGuidParsingException $e ) { |
168 | $this->apiErrorReporter->dieError( |
169 | "Invalid statement GUID: $constraintId", |
170 | 'invalid-guid', |
171 | 0, // default argument |
172 | [ self::PARAM_CONSTRAINT_ID => $constraintId ] |
173 | ); |
174 | } |
175 | }, |
176 | $constraintIds |
177 | ); |
178 | } |
179 | |
180 | /** |
181 | * @param NumericPropertyId[] $propertyIds |
182 | * @param ApiResult $result |
183 | */ |
184 | private function checkPropertyIds( array $propertyIds, ApiResult $result ): void { |
185 | foreach ( $propertyIds as $propertyId ) { |
186 | $result->addArrayType( $this->getResultPathForPropertyId( $propertyId ), 'assoc' ); |
187 | $allConstraintExceptions = $this->delegatingConstraintChecker |
188 | ->checkConstraintParametersOnPropertyId( $propertyId ); |
189 | foreach ( $allConstraintExceptions as $constraintId => $constraintParameterExceptions ) { |
190 | $this->addConstraintParameterExceptionsToResult( |
191 | $constraintId, |
192 | $constraintParameterExceptions, |
193 | $result |
194 | ); |
195 | } |
196 | } |
197 | } |
198 | |
199 | /** |
200 | * @param string[] $constraintIds |
201 | * @param ApiResult $result |
202 | */ |
203 | private function checkConstraintIds( array $constraintIds, ApiResult $result ): void { |
204 | foreach ( $constraintIds as $constraintId ) { |
205 | if ( $result->getResultData( $this->getResultPathForConstraintId( $constraintId ) ) ) { |
206 | // already checked as part of checkPropertyIds() |
207 | continue; |
208 | } |
209 | $constraintParameterExceptions = $this->delegatingConstraintChecker |
210 | ->checkConstraintParametersOnConstraintId( $constraintId ); |
211 | $this->addConstraintParameterExceptionsToResult( $constraintId, $constraintParameterExceptions, $result ); |
212 | } |
213 | } |
214 | |
215 | /** |
216 | * @param NumericPropertyId $propertyId |
217 | * @return string[] |
218 | */ |
219 | private function getResultPathForPropertyId( NumericPropertyId $propertyId ): array { |
220 | return [ $this->getModuleName(), $propertyId->getSerialization() ]; |
221 | } |
222 | |
223 | /** |
224 | * @param string $constraintId |
225 | * @return string[] |
226 | */ |
227 | private function getResultPathForConstraintId( string $constraintId ): array { |
228 | $propertyId = $this->statementGuidParser->parse( $constraintId )->getEntityId(); |
229 | '@phan-var NumericPropertyId $propertyId'; |
230 | return array_merge( $this->getResultPathForPropertyId( $propertyId ), [ $constraintId ] ); |
231 | } |
232 | |
233 | /** |
234 | * Add the ConstraintParameterExceptions for $constraintId to the API result. |
235 | * |
236 | * @param string $constraintId |
237 | * @param ConstraintParameterException[]|null $constraintParameterExceptions |
238 | * @param ApiResult $result |
239 | */ |
240 | private function addConstraintParameterExceptionsToResult( |
241 | string $constraintId, |
242 | ?array $constraintParameterExceptions, |
243 | ApiResult $result |
244 | ): void { |
245 | $path = $this->getResultPathForConstraintId( $constraintId ); |
246 | if ( $constraintParameterExceptions === null ) { |
247 | $result->addValue( |
248 | $path, |
249 | self::KEY_STATUS, |
250 | self::STATUS_NOT_FOUND |
251 | ); |
252 | } else { |
253 | $result->addValue( |
254 | $path, |
255 | self::KEY_STATUS, |
256 | $constraintParameterExceptions === [] ? self::STATUS_OKAY : self::STATUS_NOT_OKAY |
257 | ); |
258 | |
259 | $language = $this->getLanguage(); |
260 | $violationMessageRenderer = $this->violationMessageRendererFactory |
261 | ->getViolationMessageRenderer( |
262 | $language, |
263 | $this->languageFallbackChainFactory->newFromLanguage( $language ), |
264 | $this |
265 | ); |
266 | $problems = []; |
267 | foreach ( $constraintParameterExceptions as $constraintParameterException ) { |
268 | $problems[] = [ |
269 | self::KEY_MESSAGE_HTML => $violationMessageRenderer->render( |
270 | $constraintParameterException->getViolationMessage() ), |
271 | ]; |
272 | } |
273 | $result->addValue( |
274 | $path, |
275 | self::KEY_PROBLEMS, |
276 | $problems |
277 | ); |
278 | } |
279 | } |
280 | |
281 | /** |
282 | * @return array[] |
283 | * @codeCoverageIgnore |
284 | */ |
285 | public function getAllowedParams() { |
286 | return [ |
287 | self::PARAM_PROPERTY_ID => [ |
288 | ParamValidator::PARAM_TYPE => 'string', |
289 | ParamValidator::PARAM_ISMULTI => true, |
290 | ], |
291 | self::PARAM_CONSTRAINT_ID => [ |
292 | ParamValidator::PARAM_TYPE => 'string', |
293 | ParamValidator::PARAM_ISMULTI => true, |
294 | ], |
295 | ]; |
296 | } |
297 | |
298 | /** |
299 | * @return string[] |
300 | * @codeCoverageIgnore |
301 | */ |
302 | public function getExamplesMessages() { |
303 | return [ |
304 | 'action=wbcheckconstraintparameters&propertyid=P247' |
305 | => 'apihelp-wbcheckconstraintparameters-example-propertyid-1', |
306 | 'action=wbcheckconstraintparameters&' . |
307 | 'constraintid=P247$0fe1711e-4c0f-82ce-3af0-830b721d0fba|' . |
308 | 'P225$cdc71e4a-47a0-12c5-dfb3-3f6fc0b6613f' |
309 | => 'apihelp-wbcheckconstraintparameters-example-constraintid-2', |
310 | ]; |
311 | } |
312 | |
313 | } |