diff options
author | Andreas Gohr <andi@splitbrain.org> | 2023-12-01 20:01:19 +0100 |
---|---|---|
committer | Andreas Gohr <andi@splitbrain.org> | 2023-12-01 20:01:19 +0100 |
commit | 5622466683c900fb95ca4ef7a08bbae14e355219 (patch) | |
tree | 3c6d78a2142874144f8e509abd2779e78e5eb8f0 | |
parent | 35da47e94b9c89fb20775f7d5bd2375489661d6e (diff) | |
download | dokuwiki-5622466683c900fb95ca4ef7a08bbae14e355219.tar.gz dokuwiki-5622466683c900fb95ca4ef7a08bbae14e355219.zip |
OpenAPI Explorer basically works
-rw-r--r-- | inc/Remote/OpenAPIGenerator.php | 239 | ||||
-rw-r--r-- | inc/lang/en/openapi.md | 0 | ||||
-rw-r--r-- | lib/exe/openapi.php | 35 |
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> |