Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 54 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
ActionModuleBasedHandler | |
0.00% |
0 / 54 |
|
0.00% |
0 / 8 |
182 | |
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 / 23 |
|
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 | |||
makeMessageValue | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Rest\Handler; |
4 | |
5 | use ApiBase; |
6 | use ApiMain; |
7 | use ApiMessage; |
8 | use ApiUsageException; |
9 | use 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()->getErrorsByType( 'error' ) as $error ) { |
118 | $msg = ApiMessage::create( $error ); |
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 HttpException( |
125 | 'Unmapped action module error: ' . $ex->getMessage(), |
126 | $ex->getCode() |
127 | ); |
128 | } |
129 | |
130 | $actionModuleResult = $apiMain->getResult()->getResultData( null, [ 'Strip' => 'all' ] ); |
131 | |
132 | // construct result |
133 | $resultData = $this->mapActionModuleResult( $actionModuleResult ); |
134 | |
135 | $response = $this->getResponseFactory()->createFromReturnValue( $resultData ); |
136 | |
137 | $this->mapActionModuleResponse( |
138 | $apiMain->getRequest()->response(), |
139 | $actionModuleResult, |
140 | $response |
141 | ); |
142 | |
143 | return $response; |
144 | } |
145 | |
146 | /** |
147 | * Maps a REST API request to an action API request. |
148 | * Implementations typically use information returned by $this->getValidatedBody() |
149 | * and $this->getValidatedParams() to construct the return value. |
150 | * |
151 | * The return value of this method controls which action module is called by execute(). |
152 | * |
153 | * @return array Emulated request parameters to be passed to the ApiModule. |
154 | */ |
155 | abstract protected function getActionModuleParameters(); |
156 | |
157 | /** |
158 | * Maps an action API result to a REST API result. |
159 | * |
160 | * @param array $data Data structure retrieved from the ApiResult returned by the ApiModule |
161 | * |
162 | * @return mixed Data structure to be converted to JSON and wrapped in a REST Response. |
163 | * Will be processed by ResponseFactory::createFromReturnValue(). |
164 | */ |
165 | abstract protected function mapActionModuleResult( array $data ); |
166 | |
167 | /** |
168 | * Transfers relevant information, such as header values, from the WebResponse constructed |
169 | * by the action API call to a REST Response object. |
170 | * |
171 | * Subclasses may override this to provide special case handling for header fields. |
172 | * For mapping the response body, override mapActionModuleResult() instead. |
173 | * |
174 | * Subclasses overriding this method should call this method in the parent class, |
175 | * to preserve baseline behavior. |
176 | * |
177 | * @stable to override |
178 | * |
179 | * @param WebResponse $actionModuleResponse |
180 | * @param array $actionModuleResult |
181 | * @param Response $response |
182 | */ |
183 | protected function mapActionModuleResponse( |
184 | WebResponse $actionModuleResponse, |
185 | array $actionModuleResult, |
186 | Response $response |
187 | ) { |
188 | // TODO: map status, headers, cookies, etc |
189 | } |
190 | |
191 | /** |
192 | * Throws a HttpException for a given IApiMessage that represents an error. |
193 | * Never returns normally. |
194 | * |
195 | * Subclasses may override this to provide mappings for specific error codes, |
196 | * typically based on $msg->getApiCode(). Subclasses overriding this method must |
197 | * always either throw an exception, or call this method in the parent class, |
198 | * which then throws an exception. |
199 | * |
200 | * @stable to override |
201 | * |
202 | * @param IApiMessage $msg A message object representing an error in an action module, |
203 | * typically from calling getStatusValue()->getErrorsByType( 'error' ) on |
204 | * an ApiUsageException. |
205 | * @param int $statusCode The HTTP status indicated by the original exception |
206 | * |
207 | * @throws HttpException always. |
208 | */ |
209 | protected function throwHttpExceptionForActionModuleError( IApiMessage $msg, $statusCode = 400 ) { |
210 | // override to supply mappings |
211 | |
212 | throw new LocalizedHttpException( |
213 | $this->makeMessageValue( $msg ), |
214 | $statusCode, |
215 | // Include the original error code in the response. |
216 | // This makes it easier to track down the original cause of the error, |
217 | // and allows more specific mappings to be added to |
218 | // implementations of throwHttpExceptionForActionModuleError() provided by |
219 | // subclasses |
220 | [ 'actionModuleErrorCode' => $msg->getApiCode() ] |
221 | ); |
222 | } |
223 | |
224 | /** |
225 | * Constructs a MessageValue from an IApiMessage. |
226 | * |
227 | * @param IApiMessage $msg |
228 | * |
229 | * @return MessageValue |
230 | */ |
231 | protected function makeMessageValue( IApiMessage $msg ) { |
232 | return $this->getMessageValueConverter()->convertMessage( $msg ); |
233 | } |
234 | |
235 | } |