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