diff options
author | Andreas Gohr <andi@splitbrain.org> | 2023-12-01 20:01:19 +0100 |
---|---|---|
committer | Andreas Gohr <andi@splitbrain.org> | 2024-01-07 13:41:19 +0100 |
commit | 0c6e917818109b1f50cd46754bb3bf353e36d889 (patch) | |
tree | 0e7e9186dece02fa5bf48a08ebcc837a36b54894 /inc/Remote | |
parent | 8a9282a2e61c0438c945477deae2e857040e4ba8 (diff) | |
download | dokuwiki-0c6e917818109b1f50cd46754bb3bf353e36d889.tar.gz dokuwiki-0c6e917818109b1f50cd46754bb3bf353e36d889.zip |
OpenAPI Explorer basically works
Diffstat (limited to 'inc/Remote')
-rw-r--r-- | inc/Remote/OpenAPIGenerator.php | 239 |
1 files changed, 133 insertions, 106 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(); + } } - } |