Completed
Push — master ( a718f1...259f04 )
by
unknown
01:49
created

CheckConstraintParameters::getAllowedParams()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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