Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
54 / 54 |
|
100.00% |
9 / 9 |
CRAP | |
100.00% |
1 / 1 |
RootSpecHandler | |
100.00% |
54 / 54 |
|
100.00% |
9 / 9 |
13 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
run | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
getInfoSpec | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
getLicenseSpec | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getContactSpec | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getServerSpec | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getPathsSpec | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
getRouteSpec | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
getComponentsSpec | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Rest\Handler; |
4 | |
5 | use MediaWiki\Config\Config; |
6 | use MediaWiki\Config\ServiceOptions; |
7 | use MediaWiki\MainConfigNames; |
8 | use MediaWiki\Rest\ResponseFactory; |
9 | use MediaWiki\Rest\SimpleHandler; |
10 | use MediaWiki\Rest\Validator\Validator; |
11 | |
12 | /** |
13 | * Core REST API endpoint that outputs an OpenAPI spec of a set of routes. |
14 | */ |
15 | class RootSpecHandler extends SimpleHandler { |
16 | /** |
17 | * @internal |
18 | */ |
19 | private const CONSTRUCTOR_OPTIONS = [ |
20 | MainConfigNames::RightsUrl, |
21 | MainConfigNames::RightsText, |
22 | MainConfigNames::EmergencyContact, |
23 | MainConfigNames::Sitename, |
24 | ]; |
25 | |
26 | /** @var ServiceOptions */ |
27 | private ServiceOptions $options; |
28 | |
29 | /** |
30 | * @param Config $config |
31 | */ |
32 | public function __construct( Config $config ) { |
33 | $options = new ServiceOptions( self::CONSTRUCTOR_OPTIONS, $config ); |
34 | $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS ); |
35 | $this->options = $options; |
36 | } |
37 | |
38 | public function run(): array { |
39 | // TODO: implement caching, get cache key from Router. |
40 | |
41 | return [ |
42 | 'openapi' => '3.0.0', |
43 | 'info' => $this->getInfoSpec(), |
44 | 'servers' => $this->getServerSpec(), |
45 | 'paths' => $this->getPathsSpec(), |
46 | 'components' => $this->getComponentsSpec(), |
47 | ]; |
48 | } |
49 | |
50 | private function getInfoSpec(): array { |
51 | return [ |
52 | 'title' => $this->options->get( MainConfigNames::Sitename ), |
53 | 'version' => MW_VERSION, |
54 | 'license' => $this->getLicenseSpec(), |
55 | 'contact' => $this->getContactSpec(), |
56 | ]; |
57 | } |
58 | |
59 | private function getLicenseSpec(): array { |
60 | return [ |
61 | 'name' => $this->options->get( MainConfigNames::RightsText ), |
62 | 'url' => $this->options->get( MainConfigNames::RightsUrl ), |
63 | ]; |
64 | } |
65 | |
66 | private function getContactSpec(): array { |
67 | return [ |
68 | 'email' => $this->options->get( MainConfigNames::EmergencyContact ), |
69 | ]; |
70 | } |
71 | |
72 | private function getServerSpec(): array { |
73 | return [ |
74 | [ |
75 | 'url' => $this->getRouter()->getRouteUrl( '/' ), |
76 | ] |
77 | ]; |
78 | } |
79 | |
80 | private function getPathsSpec(): array { |
81 | $specs = []; |
82 | |
83 | foreach ( $this->getRouter()->getAllRoutes() as $route ) { |
84 | $path = $route['path']; |
85 | $methods = $route['method'] ?? [ 'get' ]; |
86 | |
87 | foreach ( (array)$methods as $mth ) { |
88 | $mth = strtolower( $mth ); |
89 | $specs[ $path ][ $mth ] = $this->getRouteSpec( $route, $mth ); |
90 | } |
91 | } |
92 | |
93 | return $specs; |
94 | } |
95 | |
96 | private function getRouteSpec( array $handlerObjectSpec, string $method ): array { |
97 | $handler = $this->getRouter()->instantiateHandlerObject( $handlerObjectSpec ); |
98 | $handler->initContext( $this->getRouter(), $handlerObjectSpec ); |
99 | |
100 | $operationSpec = $handler->getOpenApiSpec( $method ); |
101 | |
102 | $overrides = array_intersect_key( |
103 | $handlerObjectSpec, |
104 | array_flip( [ 'description', 'summary', 'tags', 'deprecated', 'externalDocs', 'security' ] ) |
105 | ); |
106 | |
107 | $operationSpec = $overrides + $operationSpec; |
108 | |
109 | return $operationSpec; |
110 | } |
111 | |
112 | private function getComponentsSpec() { |
113 | $components = []; |
114 | |
115 | // XXX: also collect reusable components from handler specs (but how to avoid name collisions?). |
116 | $componentsSources = [ |
117 | [ 'schemas' => Validator::getParameterTypeSchemas() ], |
118 | ResponseFactory::getResponseComponents() |
119 | ]; |
120 | |
121 | // 2D merge |
122 | foreach ( $componentsSources as $cmps ) { |
123 | foreach ( $cmps as $name => $cmp ) { |
124 | $components[$name] = array_merge( $components[$name] ?? [], $cmp ); |
125 | } |
126 | } |
127 | |
128 | return $components; |
129 | } |
130 | |
131 | } |