moved OpenAPI generator to correct namespace
[dokuwiki.git] / inc / Remote / OpenApiDoc / OpenAPIGenerator.php
blobc61ce0bb819e7b9cee181ab942ba7ff9b6a8117a
1 <?php
3 namespace dokuwiki\Remote\OpenApiDoc;
5 use dokuwiki\Remote\Api;
6 use dokuwiki\Remote\ApiCall;
7 use dokuwiki\Remote\ApiCore;
8 use dokuwiki\Utf8\PhpString;
9 use ReflectionClass;
10 use ReflectionException;
11 use stdClass;
13 /**
14 * Generates the OpenAPI documentation for the DokuWiki API
16 class OpenAPIGenerator
18 /** @var Api */
19 protected $api;
21 /** @var array Holds the documentation tree while building */
22 protected $documentation = [];
24 /**
25 * OpenAPIGenerator constructor.
27 public function __construct()
29 $this->api = new Api();
32 /**
33 * Generate the OpenAPI documentation
35 * @return string JSON encoded OpenAPI specification
37 public function generate()
39 $this->documentation = [];
40 $this->documentation['openapi'] = '3.1.0';
41 $this->documentation['info'] = [
42 'title' => 'DokuWiki API',
43 'description' => 'The DokuWiki API OpenAPI specification',
44 'version' => ((string)ApiCore::API_VERSION),
47 $this->addServers();
48 $this->addSecurity();
49 $this->addMethods();
51 return json_encode($this->documentation, JSON_PRETTY_PRINT);
54 /**
55 * Add the current DokuWiki instance as a server
57 * @return void
59 protected function addServers()
61 $this->documentation['servers'] = [
63 'url' => DOKU_URL . 'lib/exe/jsonrpc.php',
68 /**
69 * Define the default security schemes
71 * @return void
73 protected function addSecurity()
75 $this->documentation['components']['securitySchemes'] = [
76 'basicAuth' => [
77 'type' => 'http',
78 'scheme' => 'basic',
80 'jwt' => [
81 'type' => 'http',
82 'scheme' => 'bearer',
83 'bearerFormat' => 'JWT',
86 $this->documentation['security'] = [
88 'basicAuth' => [],
91 'jwt' => [],
96 /**
97 * Add all methods available in the API to the documentation
99 * @return void
101 protected function addMethods()
103 $methods = $this->api->getMethods();
105 $this->documentation['paths'] = [];
106 foreach ($methods as $method => $call) {
107 $this->documentation['paths']['/' . $method] = [
108 'post' => $this->getMethodDefinition($method, $call),
114 * Create the schema definition for a single API method
116 * @param string $method API method name
117 * @param ApiCall $call The call definition
118 * @return array
120 protected function getMethodDefinition(string $method, ApiCall $call)
122 $description = $call->getDescription();
123 $links = $call->getDocs()->getTag('link');
124 if ($links) {
125 $description .= "\n\n**See also:**";
126 foreach ($links as $link) {
127 $description .= "\n\n* " . $this->generateLink($link);
131 $retType = $call->getReturn()['type'];
132 $result = array_merge(
134 'description' => $call->getReturn()['description'],
135 'examples' => [$this->generateExample('result', $retType->getOpenApiType())],
137 $this->typeToSchema($retType)
140 $definition = [
141 'operationId' => $method,
142 'summary' => $call->getSummary(),
143 'description' => $description,
144 'tags' => [PhpString::ucwords($call->getCategory())],
145 'requestBody' => [
146 'required' => true,
147 'content' => [
148 'application/json' => $this->getMethodArguments($call->getArgs()),
151 'responses' => [
152 200 => [
153 'description' => 'Result',
154 'content' => [
155 'application/json' => [
156 'schema' => [
157 'type' => 'object',
158 'properties' => [
159 'result' => $result,
160 'error' => [
161 'type' => 'object',
162 'description' => 'Error object in case of an error',
163 'properties' => [
164 'code' => [
165 'type' => 'integer',
166 'description' => 'The error code',
167 'examples' => [0],
169 'message' => [
170 'type' => 'string',
171 'description' => 'The error message',
172 'examples' => ['Success'],
184 if ($call->isPublic()) {
185 $definition['security'] = [
186 new stdClass(),
188 $definition['description'] = 'This method is public and does not require authentication. ' .
189 "\n\n" . $definition['description'];
192 if ($call->getDocs()->getTag('deprecated')) {
193 $definition['deprecated'] = true;
194 $definition['description'] = '**This method is deprecated.** ' .
195 $call->getDocs()->getTag('deprecated')[0] .
196 "\n\n" . $definition['description'];
199 return $definition;
203 * Create the schema definition for the arguments of a single API method
205 * @param array $args The arguments of the method as returned by ApiCall::getArgs()
206 * @return array
208 protected function getMethodArguments($args)
210 if (!$args) {
211 // even if no arguments are needed, we need to define a body
212 // this is to ensure the openapi spec knows that a application/json header is needed
213 return ['schema' => ['type' => 'null']];
216 $props = [];
217 $reqs = [];
218 $schema = [
219 'schema' => [
220 'type' => 'object',
221 'required' => &$reqs,
222 'properties' => &$props
226 foreach ($args as $name => $info) {
227 $example = $this->generateExample($name, $info['type']->getOpenApiType());
229 $description = $info['description'];
230 if ($info['optional'] && isset($info['default'])) {
231 $description .= ' [_default: `' . json_encode($info['default']) . '`_]';
234 $props[$name] = array_merge(
236 'description' => $description,
237 'examples' => [$example],
239 $this->typeToSchema($info['type'])
241 if (!$info['optional']) $reqs[] = $name;
245 return $schema;
249 * Generate an example value for the given parameter
251 * @param string $name The parameter's name
252 * @param string $type The parameter's type
253 * @return mixed
255 protected function generateExample($name, $type)
257 switch ($type) {
258 case 'integer':
259 if ($name === 'rev') return 0;
260 if ($name === 'revision') return 0;
261 if ($name === 'timestamp') return time() - 60 * 24 * 30 * 2;
262 return 42;
263 case 'boolean':
264 return true;
265 case 'string':
266 if ($name === 'page') return 'playground:playground';
267 if ($name === 'media') return 'wiki:dokuwiki-128.png';
268 return 'some-' . $name;
269 case 'array':
270 return ['some-' . $name, 'other-' . $name];
271 default:
272 return new stdClass();
277 * Generates a markdown link from a dokuwiki.org URL
279 * @param $url
280 * @return mixed|string
282 protected function generateLink($url)
284 if (preg_match('/^https?:\/\/(www\.)?dokuwiki\.org\/(.+)$/', $url, $match)) {
285 $name = $match[2];
287 $name = str_replace(['_', '#', ':'], [' ', ' ', ' '], $name);
288 $name = PhpString::ucwords($name);
290 return "[$name]($url)";
291 } else {
292 return $url;
298 * Generate the OpenAPI schema for the given type
300 * @param Type $type
301 * @return array
302 * @todo add example generation here
304 public function typeToSchema(Type $type)
306 $schema = [
307 'type' => $type->getOpenApiType(),
310 // if a sub type is known, define the items
311 if ($schema['type'] === 'array' && $type->getSubType()) {
312 $schema['items'] = $this->typeToSchema($type->getSubType());
315 // if this is an object, define the properties
316 if ($schema['type'] === 'object') {
317 try {
318 $baseType = $type->getBaseType();
319 $doc = new DocBlockClass(new ReflectionClass($baseType));
320 $schema['properties'] = [];
321 foreach ($doc->getPropertyDocs() as $property => $propertyDoc) {
322 $schema['properties'][$property] = array_merge(
324 'description' => $propertyDoc->getSummary(),
326 $this->typeToSchema($propertyDoc->getType())
329 } catch (ReflectionException $e) {
330 // The class is not available, so we cannot generate a schema
334 return $schema;