OpenAPI Explorer basically works
This commit is contained in:
parent
8a9282a2e6
commit
0c6e917818
|
@ -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',
|
||||
]
|
||||
];
|
||||
|
||||
$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)
|
||||
{
|
||||
$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;
|
||||
|
||||
return $docs;
|
||||
}
|
||||
|
||||
public function getMethodArguments($args, $info)
|
||||
{
|
||||
if (!$args) return null;
|
||||
|
||||
$docs = [
|
||||
'required' => true,
|
||||
'description' => 'The positional arguments for the method',
|
||||
'content' => [
|
||||
'application/json' => [
|
||||
'schema' => [
|
||||
'type' => 'array',
|
||||
'prefixItems' => [],
|
||||
'unevaluatedItems' => false,
|
||||
],
|
||||
],
|
||||
$this->documentation['security'] = [
|
||||
[
|
||||
'basicAuth' => [],
|
||||
],
|
||||
[
|
||||
'jwt' => [],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($args as $pos => $type) {
|
||||
|
||||
switch ($type) {
|
||||
case 'int':
|
||||
$type= 'integer';
|
||||
break;
|
||||
case 'bool':
|
||||
$type = 'boolean';
|
||||
break;
|
||||
case 'file':
|
||||
$type = 'string';
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
$item = [
|
||||
'type' => $type,
|
||||
'name' => 'arg' . $pos,
|
||||
];
|
||||
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;
|
||||
}
|
||||
|
||||
protected function addMethods()
|
||||
{
|
||||
$methods = $this->api->getMethods();
|
||||
|
||||
foreach ($methods as $method => $info) {
|
||||
$this->documentation['paths'] = [];
|
||||
foreach ($methods as $method => $call) {
|
||||
$this->documentation['paths']['/' . $method] = [
|
||||
'post' => $this->getMethodDefinition($method, $info),
|
||||
'post' => $this->getMethodDefinition($method, $call),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function generate()
|
||||
protected function getMethodDefinition(string $method, ApiCall $call)
|
||||
{
|
||||
$this->addServers();
|
||||
$this->addMethods();
|
||||
$retType = $this->fixTypes($call->getReturn()['type']);
|
||||
$retExample = $this->generateExample('result', $retType);
|
||||
|
||||
return json_encode($this->documentation, JSON_PRETTY_PRINT);
|
||||
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'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
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
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($args as $name => $info) {
|
||||
$type = $this->fixTypes($info['type']);
|
||||
$example = $this->generateExample($name, $type);
|
||||
$props[$name] = [
|
||||
'type' => $type,
|
||||
'description' => $info['description'],
|
||||
'examples' => [ $example ],
|
||||
];
|
||||
}
|
||||
return $schema;
|
||||
}
|
||||
|
||||
protected function fixTypes($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'int':
|
||||
$type = 'integer';
|
||||
break;
|
||||
case 'bool':
|
||||
$type = 'boolean';
|
||||
break;
|
||||
case 'file':
|
||||
$type = 'string';
|
||||
break;
|
||||
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
protected function generateExample($name, $type)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in New Issue