aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorAndreas Gohr <andi@splitbrain.org>2023-12-01 20:01:19 +0100
committerAndreas Gohr <andi@splitbrain.org>2023-12-01 20:01:19 +0100
commit5622466683c900fb95ca4ef7a08bbae14e355219 (patch)
tree3c6d78a2142874144f8e509abd2779e78e5eb8f0
parent35da47e94b9c89fb20775f7d5bd2375489661d6e (diff)
downloaddokuwiki-5622466683c900fb95ca4ef7a08bbae14e355219.tar.gz
dokuwiki-5622466683c900fb95ca4ef7a08bbae14e355219.zip
OpenAPI Explorer basically works
-rw-r--r--inc/Remote/OpenAPIGenerator.php239
-rw-r--r--inc/lang/en/openapi.md0
-rw-r--r--lib/exe/openapi.php35
3 files changed, 164 insertions, 110 deletions
diff --git a/inc/Remote/OpenAPIGenerator.php b/inc/Remote/OpenAPIGenerator.php
index c11127ff2..3724100bb 100644
--- a/inc/Remote/OpenAPIGenerator.php
+++ b/inc/Remote/OpenAPIGenerator.php
@@ -17,10 +17,19 @@ class OpenAPIGenerator
$this->documentation['openapi'] = '3.1.0';
$this->documentation['info'] = [
'title' => 'DokuWiki API',
- 'description' => 'The DokuWiki API',
- 'version' => '1.0.0',
+ 'description' => 'The DokuWiki API OpenAPI specification',
+ 'version' => ((string)ApiCore::API_VERSION),
];
- $this->documentation['paths'] = [];
+
+ }
+
+ public function generate()
+ {
+ $this->addServers();
+ $this->addSecurity();
+ $this->addMethods();
+
+ return json_encode($this->documentation, JSON_PRETTY_PRINT);
}
protected function addServers()
@@ -32,134 +41,152 @@ class OpenAPIGenerator
];
}
- /**
- * Parses the description of a method
- *
- * @param string $desc
- * @return array with keys 'summary', 'desc', 'args' and 'return'
- */
- protected function parseMethodDescription($desc)
+ protected function addSecurity()
{
- $data = [
- 'summary' => '',
- 'desc' => '',
- 'args' => [],
- 'return' => '',
+ $this->documentation['components']['securitySchemes'] = [
+ 'basicAuth' => [
+ 'type' => 'http',
+ 'scheme' => 'basic',
+ ],
+ 'jwt' => [
+ 'type' => 'http',
+ 'scheme' => 'bearer',
+ 'bearerFormat' => 'JWT',
+ ]
+ ];
+ $this->documentation['security'] = [
+ [
+ 'basicAuth' => [],
+ ],
+ [
+ 'jwt' => [],
+ ],
];
-
- $lines = explode("\n", trim($desc));
- foreach ($lines as $line) {
- $line = trim($line);
-
- if ($line && $line[0] === '@') {
- // this is a doc block tag
- if (str_starts_with('@param', $line)) {
- $parts = sexplode(' ', $line, 4); // @param type $name description
- $data['args'][] = [ltrim($parts[1], '$'), $parts[3]]; // assumes params are in the right order
- continue;
- }
-
- if (str_starts_with('@return', $line)) {
- $parts = sexplode(' ', $line, 3); // @return type description
- $data['return'] = $parts[2];
- continue;
- }
-
- // ignore all other tags
- continue;
- }
-
- if (empty($data['summary'])) {
- $data['summary'] = $line;
- } else {
- $data['desc'] .= $line . "\n";
- }
- }
-
- $data['desc'] = trim($data['desc']);
- return $data;
}
- protected function getMethodDefinition($method, $info)
+ protected function addMethods()
{
- $desc = $this->parseMethodDescription($info['doc']);
-
- $docs = [
- 'summary' => $desc['summary'],
- 'description' => $desc['desc'],
- 'operationId' => $method,
- ];
-
- $body = $this->getMethodArguments($info['args'], $desc['args']);
- if ($body) $docs['requestBody'] = $body;
+ $methods = $this->api->getMethods();
- return $docs;
+ $this->documentation['paths'] = [];
+ foreach ($methods as $method => $call) {
+ $this->documentation['paths']['/' . $method] = [
+ 'post' => $this->getMethodDefinition($method, $call),
+ ];
+ }
}
- public function getMethodArguments($args, $info)
+ protected function getMethodDefinition(string $method, ApiCall $call)
{
- if (!$args) return null;
-
- $docs = [
- 'required' => true,
- 'description' => 'The positional arguments for the method',
- 'content' => [
- 'application/json' => [
- 'schema' => [
- 'type' => 'array',
- 'prefixItems' => [],
- 'unevaluatedItems' => false,
+ $retType = $this->fixTypes($call->getReturn()['type']);
+ $retExample = $this->generateExample('result', $retType);
+
+ return [
+ 'operationId' => $method,
+ 'summary' => $call->getSummary(),
+ 'description' => $call->getDescription(),
+ 'requestBody' => [
+ 'required' => true,
+ 'content' => [
+ 'application/json' => $this->getMethodArguments($call->getArgs()),
+ ]
+ ],
+ 'responses' => [
+ 200 => [
+ 'description' => 'Result',
+ 'content' => [
+ 'application/json' => [
+ 'schema' => [
+ 'type' => 'object',
+ 'properties' => [
+ 'result' => [
+ 'type' => $retType,
+ 'description' => $call->getReturn()['description'],
+ 'examples' => [$retExample],
+ ],
+ 'error' => [
+ 'type' => 'object',
+ 'description' => 'Error object in case of an error',
+ 'properties' => [
+ 'code' => [
+ 'type' => 'integer',
+ 'description' => 'The error code',
+ 'examples' => [0],
+ ],
+ 'message' => [
+ 'type' => 'string',
+ 'description' => 'The error message',
+ 'examples' => ['Success'],
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
],
],
- ],
+ ]
];
+ }
- foreach ($args as $pos => $type) {
-
- switch ($type) {
- case 'int':
- $type= 'integer';
- break;
- case 'bool':
- $type = 'boolean';
- break;
- case 'file':
- $type = 'string';
- break;
+ protected function getMethodArguments($args)
+ {
+ if (!$args) {
+ // even if no arguments are needed, we need to define a body
+ // this is to ensure the openapi spec knows that a application/json header is needed
+ return ['schema' => ['type' => 'null']];
+ }
- }
+ $props = [];
+ $schema = [
+ 'schema' => [
+ 'type' => 'object',
+ 'properties' => &$props
+ ]
+ ];
- $item = [
+ foreach ($args as $name => $info) {
+ $type = $this->fixTypes($info['type']);
+ $example = $this->generateExample($name, $type);
+ $props[$name] = [
'type' => $type,
- 'name' => 'arg' . $pos,
+ 'description' => $info['description'],
+ 'examples' => [ $example ],
];
- if (isset($info[$pos])) {
- if (isset($info[$pos][0])) $item['name'] = $info[$pos][0];
- if (isset($info[$pos][1])) $item['description'] = $info[$pos][1];
- }
-
- $docs['content']['application/json']['schema']['prefixItems'][] = $item;
}
- return $docs;
+ return $schema;
}
- protected function addMethods()
+ protected function fixTypes($type)
{
- $methods = $this->api->getMethods();
+ switch ($type) {
+ case 'int':
+ $type = 'integer';
+ break;
+ case 'bool':
+ $type = 'boolean';
+ break;
+ case 'file':
+ $type = 'string';
+ break;
- foreach ($methods as $method => $info) {
- $this->documentation['paths']['/' . $method] = [
- 'post' => $this->getMethodDefinition($method, $info),
- ];
}
+ return $type;
}
- public function generate()
+ protected function generateExample($name, $type)
{
- $this->addServers();
- $this->addMethods();
-
- return json_encode($this->documentation, JSON_PRETTY_PRINT);
+ switch ($type) {
+ case 'integer':
+ return 42;
+ case 'boolean':
+ return true;
+ case 'string':
+ return 'some-'.$name;
+ case 'array':
+ return ['some-'.$name, 'other-'.$name];
+ default:
+ return new \stdClass();
+ }
}
-
}
diff --git a/inc/lang/en/openapi.md b/inc/lang/en/openapi.md
deleted file mode 100644
index e69de29bb..000000000
--- a/inc/lang/en/openapi.md
+++ /dev/null
diff --git a/lib/exe/openapi.php b/lib/exe/openapi.php
index a228bf834..69cfb93b2 100644
--- a/lib/exe/openapi.php
+++ b/lib/exe/openapi.php
@@ -1,7 +1,6 @@
<?php
if (!defined('DOKU_INC')) define('DOKU_INC', __DIR__ . '/../../');
-if (!defined('NOSESSION')) define('NOSESSION', true); // no session or auth required here
require_once(DOKU_INC . 'inc/init.php');
global $INPUT;
@@ -17,7 +16,8 @@ if ($INPUT->has('spec')) {
<html lang="en">
<head>
<meta charset="utf-8">
- <script src="https://unpkg.com/openapi-explorer/dist/browser/openapi-explorer.min.js" type="module" defer=""></script>
+ <script src="https://unpkg.com/openapi-explorer/dist/browser/openapi-explorer.min.js" type="module"
+ defer=""></script>
<style>
body {
font-family: sans-serif;
@@ -28,9 +28,36 @@ if ($INPUT->has('spec')) {
<openapi-explorer
spec-url="<?php echo DOKU_URL ?>lib/exe/openapi.php?spec=1"
hide-server-selection="true"
- default-schema-tab="body"
use-path-in-nav-bar="true"
-></openapi-explorer>
+>
+ <div slot="overview-api-description">
+ <p>
+ This is an auto generated description and OpenAPI specification for the
+ <a href="https://www.dokuwiki.org/devel/jsonrpc">DokuWiki JSON-RPC API</a>.
+ It is generated from the source code and the inline documentation.
+ </p>
+
+ <p>
+ <a href="<?php echo DOKU_BASE ?>/lib/exe/openapi.php?spec=1" download="dokuwiki.json">Download
+ the API Spec</a>
+ </p>
+
+ </div>
+
+ <div slot="authentication-footer">
+ <p>
+ <?php
+ if ($INPUT->server->has('REMOTE_USER')) {
+ echo 'You are currently logged in as <strong>' . hsc($INPUT->server->str('REMOTE_USER')) . '</strong>.';
+ echo '<br>API calls in this tool will be automatically executed with your permissions.';
+ } else {
+ echo 'You are currently not logged in.<br>';
+ echo 'You can provide credentials above.';
+ }
+ ?>
+ </p>
+ </div>
+</openapi-explorer>
</body>
</html>