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
;
10 use ReflectionException
;
14 * Generates the OpenAPI documentation for the DokuWiki API
16 class OpenAPIGenerator
21 /** @var array Holds the documentation tree while building */
22 protected $documentation = [];
25 * OpenAPIGenerator constructor.
27 public function __construct()
29 $this->api
= new Api();
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
),
51 return json_encode($this->documentation
, JSON_PRETTY_PRINT
);
55 * Add the current DokuWiki instance as a server
59 protected function addServers()
61 $this->documentation
['servers'] = [
63 'url' => DOKU_URL
. 'lib/exe/jsonrpc.php',
69 * Define the default security schemes
73 protected function addSecurity()
75 $this->documentation
['components']['securitySchemes'] = [
83 'bearerFormat' => 'JWT',
86 $this->documentation
['security'] = [
97 * Add all methods available in the API to the documentation
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
120 protected function getMethodDefinition(string $method, ApiCall
$call)
122 $description = $call->getDescription();
123 $links = $call->getDocs()->getTag('link');
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)
141 'operationId' => $method,
142 'summary' => $call->getSummary(),
143 'description' => $description,
144 'tags' => [PhpString
::ucwords($call->getCategory())],
148 'application/json' => $this->getMethodArguments($call->getArgs()),
153 'description' => 'Result',
155 'application/json' => [
162 'description' => 'Error object in case of an error',
166 'description' => 'The error code',
171 'description' => 'The error message',
172 'examples' => ['Success'],
184 if ($call->isPublic()) {
185 $definition['security'] = [
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'];
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()
208 protected function getMethodArguments($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']];
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;
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
255 protected function generateExample($name, $type)
259 if ($name === 'rev') return 0;
260 if ($name === 'revision') return 0;
261 if ($name === 'timestamp') return time() - 60 * 24 * 30 * 2;
266 if ($name === 'page') return 'playground:playground';
267 if ($name === 'media') return 'wiki:dokuwiki-128.png';
268 return 'some-' . $name;
270 return ['some-' . $name, 'other-' . $name];
272 return new stdClass();
277 * Generates a markdown link from a dokuwiki.org URL
280 * @return mixed|string
282 protected function generateLink($url)
284 if (preg_match('/^https?:\/\/(www\.)?dokuwiki\.org\/(.+)$/', $url, $match)) {
287 $name = str_replace(['_', '#', ':'], [' ', ' ', ' '], $name);
288 $name = PhpString
::ucwords($name);
290 return "[$name]($url)";
298 * Generate the OpenAPI schema for the given type
302 * @todo add example generation here
304 public function typeToSchema(Type
$type)
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') {
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