Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
56.60% covered (warning)
56.60%
30 / 53
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
PublicApiRun
56.60% covered (warning)
56.60%
30 / 53
50.00% covered (danger)
50.00%
1 / 2
23.77
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 run
54.90% covered (warning)
54.90%
28 / 51
0.00% covered (danger)
0.00%
0 / 1
16.43
 getAllowedParams
n/a
0 / 0
n/a
0 / 0
1
 getExamplesMessages
n/a
0 / 0
n/a
0 / 0
1
1<?php
2/**
3 * WikiLambda function call run public API
4 *
5 * @file
6 * @ingroup Extensions
7 * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
8 * @license MIT
9 */
10
11namespace MediaWiki\Extension\WikiLambda\PublicAPI;
12
13use JsonException;
14use MediaWiki\Api\ApiMain;
15use MediaWiki\Api\ApiUsageException;
16use MediaWiki\Extension\WikiLambda\ActionAPI\WikiLambdaApiBase;
17use MediaWiki\Extension\WikiLambda\HttpStatus;
18use MediaWiki\Extension\WikiLambda\Registry\ZErrorTypeRegistry;
19use MediaWiki\Extension\WikiLambda\ZErrorFactory;
20use MediaWiki\Extension\WikiLambda\ZObjectUtils;
21use Wikimedia\ParamValidator\ParamValidator;
22
23class PublicApiRun extends WikiLambdaApiBase {
24
25    /**
26     * @inheritDoc
27     */
28    public function __construct( ApiMain $mainModule, string $moduleName ) {
29        parent::__construct( $mainModule, $moduleName, '', true );
30
31        $this->setUp();
32    }
33
34    /**
35     * @inheritDoc
36     */
37    protected function run() {
38        $start = microtime( true );
39
40        // Get input parameters
41        $params = $this->extractRequestParams();
42        $zObjectString = $params[ 'function_call' ];
43
44        // Initialize output
45        $pageResult = $this->getResult();
46
47        // 1. JSON decode input zobject and die with ZError if invalid syntax
48        try {
49            $zObjectAsStdClass = json_decode( $zObjectString, false, 512, JSON_THROW_ON_ERROR );
50        } catch ( JsonException $e ) {
51            // (T389702) If the JSON is invalid, we return a 400 error rather than have PHP die
52            $this->submitFunctionCallEvent( HttpStatus::BAD_REQUEST, null, $start );
53            $zError = ZErrorFactory::createZErrorInstance( ZErrorTypeRegistry::Z_ERROR_INVALID_SYNTAX, [
54                'message' => $e->getMessage(),
55                'input' => $zObjectString
56            ] );
57            WikiLambdaApiBase::dieWithZError( $zError, HttpStatus::BAD_REQUEST );
58        }
59
60        // Cautionary canonicalization
61        $zObjectAsStdClass = ZObjectUtils::canonicalize( $zObjectAsStdClass );
62
63        // Initialize flags:
64        $flags = [
65            'validate' => true,
66            'isUnsavedCode' => false,
67            'bypassCache' => false
68        ];
69
70        // Get function zid for logging
71        $function = ZObjectUtils::getFunctionZidOrNull( $zObjectAsStdClass );
72        if ( !ZObjectUtils::isValidZObjectReference( (string)$function ) ) {
73            $function = 'No valid ZID';
74            $this->getLogger()->info(
75                __METHOD__ . ' unable to find a ZID for the function called',
76                [
77                    'zobject' => $zObjectString
78                ]
79            );
80        }
81
82        // Arbitrary implementation calls need more than wikilambda-execute;
83        // require wikilambda-execute-unsaved-code, so that it can be independently
84        // activated/deactivated (to run an arbitrary implementation, you have to
85        // pass a custom function with the raw implementation rather than a ZID string.)
86        if ( $this->hasUnsavedCode( $zObjectAsStdClass ) ) {
87            $flags[ 'isUnsavedCode' ] = true;
88        }
89
90        try {
91            $response = $this->executeFunctionCall( $zObjectAsStdClass, $flags );
92
93            $result = [
94                'data' => $response['result']
95            ];
96
97            $httpStatusCode = $response['httpStatusCode'];
98
99            $pageResult->addValue( [], $this->getModuleName(), $result );
100            $this->submitFunctionCallEvent( $httpStatusCode, $function, $start );
101
102        } catch ( ApiUsageException $e ) {
103            // Whenever executeFunctionCall dies with error, we intercept it so that:
104            // * we submit a function call metrics event,
105            // * we rethrow the error
106            $errorCode = $e->getCode();
107            $httpStatusCode = ( is_int( $errorCode ) && $errorCode >= 100 && $errorCode < 600 ) ?
108                $errorCode : HttpStatus::BAD_REQUEST;
109            $this->submitFunctionCallEvent( $httpStatusCode, $function, $start );
110            throw $e;
111
112        } catch ( \Throwable $e ) {
113            // Whatever we catch here is an unexpected system error:
114            // * we log accordingly as error
115            // * we rethrow the error
116            $this->getLogger()->error(
117                __METHOD__ . ' caused an unexpected system failure',
118                [
119                    'zobject' => $zObjectString,
120                    'exception' => $e
121                ]
122            );
123            throw $e;
124        }
125    }
126
127    /**
128     * @inheritDoc
129     * @codeCoverageIgnore
130     */
131    protected function getAllowedParams(): array {
132        return [
133            'function_call' => [
134                ParamValidator::PARAM_TYPE => 'text',
135                ParamValidator::PARAM_REQUIRED => true,
136            ]
137        ];
138    }
139
140    /**
141     * @see ApiBase::getExamplesMessages()
142     * @return array
143     * @codeCoverageIgnore
144     */
145    protected function getExamplesMessages() {
146        return [
147            'action=wikifunctions_run&function_call='
148                . urlencode( ZObjectUtils::readTestFile( 'Z902_false.json' ) )
149                => 'apihelp-wikilambda_function_call-example-if',
150        ];
151    }
152}