Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 52 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
ActionModuleBasedHandler | |
0.00% |
0 / 52 |
|
0.00% |
0 / 7 |
156 | |
0.00% |
0 / 1 |
getUser | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setApiMain | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getApiMain | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
overrideActionModule | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
30 | |||
getActionModuleParameters | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
mapActionModuleResult | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
mapActionModuleResponse | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
throwHttpExceptionForActionModuleError | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Rest\Handler; |
4 | |
5 | use MediaWiki\Api\ApiBase; |
6 | use MediaWiki\Api\ApiMain; |
7 | use MediaWiki\Api\ApiMessage; |
8 | use MediaWiki\Api\ApiUsageException; |
9 | use MediaWiki\Api\IApiMessage; |
10 | use MediaWiki\Context\RequestContext; |
11 | use MediaWiki\Request\FauxRequest; |
12 | use MediaWiki\Request\WebResponse; |
13 | use MediaWiki\Rest\Handler; |
14 | use MediaWiki\Rest\Handler\Helper\RestStatusTrait; |
15 | use MediaWiki\Rest\HttpException; |
16 | use MediaWiki\Rest\LocalizedHttpException; |
17 | use MediaWiki\Rest\Response; |
18 | use Wikimedia\Message\MessageValue; |
19 | |
20 | /** |
21 | * Base class for REST handlers that are implemented by mapping to an existing ApiModule. |
22 | * |
23 | * @stable to extend |
24 | */ |
25 | abstract class ActionModuleBasedHandler extends Handler { |
26 | use RestStatusTrait; |
27 | |
28 | /** |
29 | * @var ApiMain|null |
30 | */ |
31 | private $apiMain = null; |
32 | |
33 | protected function getUser() { |
34 | return $this->getApiMain()->getUser(); |
35 | } |
36 | |
37 | /** |
38 | * Set main action API entry point for testing. |
39 | * |
40 | * @param ApiMain $apiMain |
41 | */ |
42 | public function setApiMain( ApiMain $apiMain ) { |
43 | $this->apiMain = $apiMain; |
44 | } |
45 | |
46 | /** |
47 | * @return ApiMain |
48 | */ |
49 | public function getApiMain() { |
50 | if ( $this->apiMain ) { |
51 | return $this->apiMain; |
52 | } |
53 | |
54 | $context = RequestContext::getMain(); |
55 | $session = $context->getRequest()->getSession(); |
56 | |
57 | // NOTE: This being a MediaWiki\Request\FauxRequest instance triggers special case behavior |
58 | // in ApiMain, causing ApiMain::isInternalMode() to return true. Among other things, |
59 | // this causes ApiMain to throw errors rather than encode them in the result data. |
60 | $fauxRequest = new FauxRequest( [], true, $session ); |
61 | $fauxRequest->setSessionId( $session->getSessionId() ); |
62 | |
63 | $fauxContext = new RequestContext(); |
64 | $fauxContext->setRequest( $fauxRequest ); |
65 | $fauxContext->setUser( $context->getUser() ); |
66 | $fauxContext->setLanguage( $context->getLanguage() ); |
67 | |
68 | $this->apiMain = new ApiMain( $fauxContext, true ); |
69 | return $this->apiMain; |
70 | } |
71 | |
72 | /** |
73 | * Overrides an action API module. Used for testing. |
74 | * |
75 | * @param string $name |
76 | * @param string $group |
77 | * @param ApiBase $module |
78 | */ |
79 | public function overrideActionModule( string $name, string $group, ApiBase $module ) { |
80 | $this->getApiMain()->getModuleManager()->addModule( |
81 | $name, |
82 | $group, |
83 | [ |
84 | 'class' => get_class( $module ), |
85 | 'factory' => static function () use ( $module ) { |
86 | return $module; |
87 | } |
88 | ] |
89 | ); |
90 | } |
91 | |
92 | /** |
93 | * Main execution method, implemented to delegate execution to ApiMain. |
94 | * Which action API module gets called is controlled by the parameter array returned |
95 | * by getActionModuleParameters(). The response from the action module is passed to |
96 | * mapActionModuleResult(), any ApiUsageException thrown will be converted to a |
97 | * HttpException by throwHttpExceptionForActionModuleError(). |
98 | * |
99 | * @return mixed |
100 | */ |
101 | public function execute() { |
102 | $apiMain = $this->getApiMain(); |
103 | |
104 | $params = $this->getActionModuleParameters(); |
105 | $request = $apiMain->getRequest(); |
106 | |
107 | foreach ( $params as $key => $value ) { |
108 | $request->setVal( $key, $value ); |
109 | } |
110 | |
111 | try { |
112 | // NOTE: ApiMain detects this to be an internal call, so it will throw |
113 | // ApiUsageException rather than putting error messages into the result. |
114 | $apiMain->execute(); |
115 | } catch ( ApiUsageException $ex ) { |
116 | // use a fake loop to throw the first error |
117 | foreach ( $ex->getStatusValue()->getMessages( 'error' ) as $msg ) { |
118 | $msg = ApiMessage::create( $msg ); |
119 | $this->throwHttpExceptionForActionModuleError( $msg, $ex->getCode() ?: 400 ); |
120 | } |
121 | |
122 | // This should never happen, since ApiUsageExceptions should always |
123 | // have errors in their Status object. |
124 | throw new LocalizedHttpException( new MessageValue( "rest-unmapped-action-error", [ $ex->getMessage() ] ), |
125 | $ex->getCode() |
126 | ); |
127 | } |
128 | |
129 | $actionModuleResult = $apiMain->getResult()->getResultData( null, [ 'Strip' => 'all' ] ); |
130 | |
131 | // construct result |
132 | $resultData = $this->mapActionModuleResult( $actionModuleResult ); |
133 | |
134 | $response = $this->getResponseFactory()->createFromReturnValue( $resultData ); |
135 | |
136 | $this->mapActionModuleResponse( |
137 | $apiMain->getRequest()->response(), |
138 | $actionModuleResult, |
139 | $response |
140 | ); |
141 | |
142 | return $response; |
143 | } |
144 | |
145 | /** |
146 | * Maps a REST API request to an action API request. |
147 | * Implementations typically use information returned by $this->getValidatedBody() |
148 | * and $this->getValidatedParams() to construct the return value. |
149 | * |
150 | * The return value of this method controls which action module is called by execute(). |
151 | * |
152 | * @return array Emulated request parameters to be passed to the ApiModule. |
153 | */ |
154 | abstract protected function getActionModuleParameters(); |
155 | |
156 | /** |
157 | * Maps an action API result to a REST API result. |
158 | * |
159 | * @param array $data Data structure retrieved from the ApiResult returned by the ApiModule |
160 | * |
161 | * @return mixed Data structure to be converted to JSON and wrapped in a REST Response. |
162 | * Will be processed by ResponseFactory::createFromReturnValue(). |
163 | */ |
164 | abstract protected function mapActionModuleResult( array $data ); |
165 | |
166 | /** |
167 | * Transfers relevant information, such as header values, from the WebResponse constructed |
168 | * by the action API call to a REST Response object. |
169 | * |
170 | * Subclasses may override this to provide special case handling for header fields. |
171 | * For mapping the response body, override mapActionModuleResult() instead. |
172 | * |
173 | * Subclasses overriding this method should call this method in the parent class, |
174 | * to preserve baseline behavior. |
175 | * |
176 | * @stable to override |
177 | * |
178 | * @param WebResponse $actionModuleResponse |
179 | * @param array $actionModuleResult |
180 | * @param Response $response |
181 | */ |
182 | protected function mapActionModuleResponse( |
183 | WebResponse $actionModuleResponse, |
184 | array $actionModuleResult, |
185 | Response $response |
186 | ) { |
187 | // TODO: map status, headers, cookies, etc |
188 | } |
189 | |
190 | /** |
191 | * Throws a HttpException for a given IApiMessage that represents an error. |
192 | * Never returns normally. |
193 | * |
194 | * Subclasses may override this to provide mappings for specific error codes, |
195 | * typically based on $msg->getApiCode(). Subclasses overriding this method must |
196 | * always either throw an exception, or call this method in the parent class, |
197 | * which then throws an exception. |
198 | * |
199 | * @stable to override |
200 | * |
201 | * @param IApiMessage $msg A message object representing an error in an action module, |
202 | * typically from calling getStatusValue()->getMessages( 'error' ) on |
203 | * an ApiUsageException. |
204 | * @param int $statusCode The HTTP status indicated by the original exception |
205 | * |
206 | * @throws HttpException always. |
207 | */ |
208 | protected function throwHttpExceptionForActionModuleError( IApiMessage $msg, $statusCode = 400 ) { |
209 | // override to supply mappings |
210 | |
211 | throw new LocalizedHttpException( |
212 | MessageValue::newFromSpecifier( $msg ), |
213 | $statusCode, |
214 | // Include the original error code in the response. |
215 | // This makes it easier to track down the original cause of the error, |
216 | // and allows more specific mappings to be added to |
217 | // implementations of throwHttpExceptionForActionModuleError() provided by |
218 | // subclasses |
219 | [ 'actionModuleErrorCode' => $msg->getApiCode() ] |
220 | ); |
221 | } |
222 | |
223 | } |