Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.22% covered (success)
93.22%
55 / 59
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ZErrorTypeRegistry
93.22% covered (success)
93.22%
55 / 59
77.78% covered (warning)
77.78%
7 / 9
26.21
0.00% covered (danger)
0.00%
0 / 1
 initialize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 registerZErrorTypeLabel
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 unregister
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 isZErrorTypeKnown
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 fetchZErrorType
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 instanceOfZErrorType
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 isZErrorTypeCached
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 isBuiltinZErrorType
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getZErrorTypeLabel
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
1<?php
2/**
3 * WikiLambda ZErrorTypeRegistry
4 *
5 * @file
6 * @ingroup Extensions
7 * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
8 * @license MIT
9 */
10
11namespace MediaWiki\Extension\WikiLambda\Registry;
12
13use MediaWiki\Context\RequestContext;
14use MediaWiki\Extension\WikiLambda\WikiLambdaServices;
15use MediaWiki\Extension\WikiLambda\ZErrorException;
16use MediaWiki\Extension\WikiLambda\ZObjects\ZPersistentObject;
17use MediaWiki\MediaWikiServices;
18use ReflectionClass;
19
20/**
21 * A registry service for ZErrorType
22 */
23class ZErrorTypeRegistry extends ZObjectRegistry {
24
25    // TODO (T300512): Bring all constants (error types ZIDs) to schemata
26    public const Z_ERROR_UNKNOWN = 'Z500';
27    public const Z_ERROR_INVALID_SYNTAX = 'Z501';
28    public const Z_ERROR_NOT_WELLFORMED = 'Z502';
29    public const Z_ERROR_NOT_IMPLEMENTED_YET = 'Z503';
30    public const Z_ERROR_ZID_NOT_FOUND = 'Z504';
31    public const Z_ERROR_ARGUMENT_COUNT_MISMATCH = 'Z505';
32    public const Z_ERROR_ARGUMENT_TYPE_MISMATCH = 'Z506';
33    public const Z_ERROR_EVALUATION = 'Z507';
34    public const Z_ERROR_LIST = 'Z509';
35    public const Z_ERROR_MISSING_KEY = 'Z511';
36    public const Z_ERROR_MISSING_PERSISTENT_VALUE = 'Z513';
37    public const Z_ERROR_RETURN_TYPE_MISMATCH = 'Z517';
38    public const Z_ERROR_OBJECT_TYPE_MISMATCH = 'Z518';
39    public const Z_ERROR_UNDEFINED_LIST_TYPE = 'Z519';
40    public const Z_ERROR_WRONG_LIST_TYPE = 'Z520';
41    public const Z_ERROR_NOT_NUMBER_BOOLEAN_NULL = 'Z521';
42    public const Z_ERROR_ARRAY_ELEMENT_NOT_WELLFORMED = 'Z522';
43    public const Z_ERROR_MISSING_TYPE = 'Z523';
44    public const Z_ERROR_TYPE_NOT_STRING_ARRAY = 'Z524';
45    public const Z_ERROR_INVALID_KEY = 'Z525';
46    public const Z_ERROR_KEY_VALUE_NOT_WELLFORMED = 'Z526';
47    public const Z_ERROR_CONNECTION_FAILURE = 'Z529';
48    public const Z_ERROR_API_FAILURE = 'Z530';
49    public const Z_ERROR_STRING_VALUE_MISSING = 'Z532';
50    public const Z_ERROR_STRING_VALUE_WRONG_TYPE = 'Z533';
51    public const Z_ERROR_REFERENCE_VALUE_MISSING = 'Z535';
52    public const Z_ERROR_REFERENCE_VALUE_WRONG_TYPE = 'Z536';
53    public const Z_ERROR_REFERENCE_VALUE_INVALID = 'Z537';
54    public const Z_ERROR_WRONG_NAMESPACE = 'Z538';
55    public const Z_ERROR_WRONG_CONTENT_TYPE = 'Z539';
56    public const Z_ERROR_INVALID_LANG_CODE = 'Z540';
57    public const Z_ERROR_LANG_NOT_FOUND = 'Z541';
58    public const Z_ERROR_UNEXPECTED_ZTYPE = 'Z542';
59    public const Z_ERROR_ZTYPE_NOT_FOUND = 'Z543';
60    public const Z_ERROR_CONFLICTING_TYPE_NAMES = 'Z544';
61    public const Z_ERROR_CONFLICTING_TYPE_ZIDS = 'Z545';
62    public const Z_ERROR_BUILTIN_TYPE_NOT_FOUND = 'Z546';
63    public const Z_ERROR_INVALID_FORMAT = 'Z547';
64    public const Z_ERROR_INVALID_JSON = 'Z548';
65    public const Z_ERROR_INVALID_REFERENCE = 'Z549';
66    public const Z_ERROR_UNKNOWN_REFERENCE = 'Z550';
67    public const Z_ERROR_SCHEMA_TYPE_MISMATCH = 'Z551';
68    public const Z_ERROR_ARRAY_TYPE_MISMATCH = 'Z552';
69    public const Z_ERROR_DISALLOWED_ROOT_ZOBJECT = 'Z553';
70    public const Z_ERROR_LABEL_CLASH = 'Z554';
71    public const Z_ERROR_UNMATCHING_ZID = 'Z555';
72    public const Z_ERROR_INVALID_TITLE = 'Z556';
73    public const Z_ERROR_USER_CANNOT_EDIT = 'Z557';
74    public const Z_ERROR_USER_CANNOT_RUN = 'Z559';
75    public const Z_ERROR_INVALID_EVALUATION_RESULT = 'Z560';
76    public const Z_ERROR_ORCHESTRATOR_RATE_LIMIT = 'Z570';
77    public const Z_ERROR_EVALUATOR_RATE_LIMIT = 'Z571';
78    public const Z_ERROR_INVALID_ORCHESTRATOR_RESULT = 'Z577';
79
80    public const Z_ERROR_DUPLICATE_LANGUAGES = 'Z580';
81
82    /**
83     * Initialize ZErrorTypeRegistry
84     */
85    protected function initialize(): void {
86        // Registry for ZObjects of type ZErrorType/Z500
87        $this->type = ZTypeRegistry::Z_ERRORTYPE;
88    }
89
90    /**
91     * @param string $errorType
92     * @param ZPersistentObject $zobject
93     * @param string $languageCode
94     * @return ?string
95     */
96    protected function registerZErrorTypeLabel( $errorType, $zobject, $languageCode = 'en' ): ?string {
97        $langRegistry = ZLangRegistry::singleton();
98        if ( !$langRegistry->isLanguageKnownGivenCode( $languageCode ) ) {
99            $languageCode = 'en';
100        }
101
102        $registryKey = $errorType . ':' . $languageCode;
103
104        $language = MediaWikiServices::getInstance()
105            ->getLanguageFactory()
106            ->getLanguage( $languageCode );
107
108        $errorLabel = $zobject->getLabel( $language, true );
109        if ( $errorLabel ) {
110            $this->register( $registryKey, $errorLabel );
111        }
112
113        return $errorLabel;
114    }
115
116    /**
117     * Unregisters the given error type zid from the registry
118     * by deleting all the language entries associated for this zid
119     *
120     * @param string $zid
121     */
122    public function unregister( string $zid ): void {
123        $prefix = $zid . ':';
124        foreach ( $this->registry as $errorKey => $errorLabel ) {
125            if ( str_starts_with( $errorKey, $prefix ) ) {
126                unset( $this->registry[ $errorKey ] );
127            }
128        }
129    }
130
131    /**
132     * Check if the given ZErrorType Zid is known
133     *
134     * @param string $errorType
135     * @return bool
136     */
137    public function isZErrorTypeKnown( string $errorType ): bool {
138        if ( $this->isZErrorTypeCached( $errorType ) ) {
139            return true;
140        }
141
142        if ( self::isBuiltinZErrorType( $errorType ) ) {
143            return true;
144        }
145
146        $zobject = $this->fetchZErrorType( $errorType );
147        if ( !$zobject ) {
148            return false;
149        }
150
151        $this->registerZErrorTypeLabel(
152            $errorType,
153            $zobject,
154            RequestContext::getMain()->getLanguage()->getCode()
155        );
156
157        return true;
158    }
159
160    /**
161     * Fetches a given error type Zid from the database, throwing error if the ZErrorType does not
162     * exist or if the fetched object is not of the wanted type
163     *
164     * @param string $errorType
165     * @return ZPersistentObject|bool Found ZObject
166     */
167    private function fetchZErrorType( string $errorType ) {
168        $zObjectStore = WikiLambdaServices::getZObjectStore();
169
170        // This can throw a ZErrorException if the ZID is not found, which will bubble up
171        $zobjectContent = $zObjectStore->fetchZObject( $errorType );
172
173        // ZObjectContent->getZType is a shortcut to ZObjectContent->getInnerZObject->getZType
174        if ( !$zobjectContent || $zobjectContent->getZType() !== ZTypeRegistry::Z_ERRORTYPE ) {
175            return false;
176        }
177
178        return $zobjectContent->getZObject();
179    }
180
181    /**
182     * Check if the given Zid belongs to a ZErrorType
183     *
184     * @param string $zid
185     * @return bool
186     */
187    public function instanceOfZErrorType( string $zid ): bool {
188        try {
189            return $this->isZErrorTypeKnown( $zid );
190        } catch ( ZErrorException ) {
191            return false;
192        }
193    }
194
195    /**
196     * Check if the given Zid is an already cached ZErrorType (Z50)
197     * If a language code is given, check it the label in that language is stored
198     *
199     * @param string $errorType
200     * @param string|bool $langCode
201     * @return bool
202     */
203    private function isZErrorTypeCached( string $errorType, $langCode = false ): bool {
204        if ( $langCode ) {
205            $registryKey = $errorType . ':' . $langCode;
206            return array_key_exists( $registryKey, $this->registry );
207        }
208
209        $prefix = $errorType . ':';
210        foreach ( $this->registry as $errorKey => $errorLabel ) {
211            if ( str_starts_with( $errorKey, $prefix ) ) {
212                return true;
213            }
214        }
215
216        return false;
217    }
218
219    /**
220     * Check if the given Zid belongs to a builtin ZErrorType (from the ones declared above)
221     *
222     * @param string $errorType
223     * @return bool
224     */
225    private static function isBuiltinZErrorType( string $errorType ): bool {
226        static $lookup = null;
227        if ( $lookup === null ) {
228            $lookup = array_flip( ( new ReflectionClass( self::class ) )->getConstants() );
229        }
230        return isset( $lookup[ $errorType ] );
231    }
232
233    /**
234     * Gets the ZErrorType label in English
235     *
236     * @param string $errorType
237     * @param string $errorLanguageCode Language code, defaulting to English
238     * @return string
239     */
240    public function getZErrorTypeLabel( string $errorType, string $errorLanguageCode = 'en' ): string {
241        $registryCacheKey = $errorType . ':' . $errorLanguageCode;
242
243        if ( $this->isZErrorTypeCached( $errorType, $errorLanguageCode ) ) {
244            return $this->registry[ $registryCacheKey ];
245        }
246
247        $zobject = $this->fetchZErrorType( $errorType );
248        if ( !$zobject ) {
249            return "Unknown error $errorType";
250        }
251
252        $errorLabel = $this->registerZErrorTypeLabel( $errorType, $zobject, $errorLanguageCode );
253        if ( !$errorLabel ) {
254            return "Unknown error $errorType";
255        }
256
257        return $errorLabel;
258    }
259}