Skip to content
Snippets Groups Projects
Commit 5a7c11eb authored by Rich Gerdes's avatar Rich Gerdes Committed by Rich Gerdes
Browse files

Issue #2948570 by richgerdes: Refactor doc module implementations

parent d7e7e618
No related branches found
Tags 8.x-1.0-beta2
No related merge requests found
Showing
with 18 additions and 577 deletions
......@@ -14,11 +14,8 @@ https://www.drupal.org/docs/8/extending-drupal-8/installing-drupal-8-modules).
### Module Dependencies
The OpenAPI module leverages the [schemata] module to derive the entity schema
for the project. The SwaggerUI sub modules requires the [swagger-api/swagger-ui]
(https://github.com/swagger-api/swagger-ui) library to be installed into the
sites `/libraries` directory. Installation instructions can be found in the
sub module's README.md.
The OpenAPI module leverages the [schemata](https://drupal.org/project/schemata)
module to derive the entity schema for the project.
## Documentation
......@@ -42,6 +39,22 @@ view the entity schema. For the two supplied modules, these urls are below.
If you don't have one of these modules enabled, you will need to do so in order
to use the functionality provided by this module.
### Viewing OpenAPI Schema In the UI
This module uses the [OpenAPI UI module](https://drupal.org/project/openapi_ui)
to display the generated docs within a web interface. You can install openapi_ui
and its extention modules.
We recommend that you use the [Redoc](https://github.com/Rebilly/ReDoc) project.
This can be downloaded and configured to display docs within a drupal site using
the [Redoc for OpenAPI UI](https://drupal.org/project/openapi_ui_redoc) module.
Once the module installed, you will need to have a supported api schema module
nstalled, see "Using OpenAPI" above. You can then navigate to the respective url
for the api.
- REST - `/admin/config/services/openapi/redoc/rest`
- JsonAPI - `/admin/config/services/openapi/redoc/jsonapi`
### Adding support for a custom api
You can write additional integrations for other rest modules and contributed
......
# OpenAPI reDoc Module
This module provides a reDoc documentation ui for Drupal Core and JsonApi REST
Resources.
[Learn more about reDoc](https://github.com/Rebilly/ReDoc).
name: OpenAPI Redoc Documentation
type: module
description: Documentation using the ReDoc
core: 8.x
package: OpenAPI
dependencies:
- openapi:openapi
openapi_redoc.jsonapi:
title: Redoc JSON API Documentation
description: 'JSON API documentation generated by the ReDoc JS library.'
parent: system.admin_config_services
route_name: openapi_redoc.jsonapi
openapi_redoc.rest:
title: Redoc REST API Documentation
description: 'REST API documentation generated by the ReDoc JS library.'
parent: system.admin_config_services
route_name: openapi_redoc.rest
<?php
/**
* @file
* Contains OpenApi reDoc Module.
*/
/**
* Implements hook_theme().
*/
function openapi_redoc_theme() {
return [
'redoc' => [
'render element' => 'elements',
'variables' => [
'attributes' => [
'scroll-y-offset' => 150,
'lazy-rendering' => TRUE,
],
'openapi_url' => '',
'js_url' => 'https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js',
],
],
];
}
/**
* Implements hook_menu_links_discovered_alter().
*/
function openapi_redoc_menu_links_discovered_alter(&$links) {
// Disable menu items depending on module enabled.
// @todo Is this possible to do in custom menu class?
// Currently this is not working.
$module_handler = \Drupal::moduleHandler();
if (!$module_handler->moduleExists('jsonapi')) {
unset($links['openapi_redoc.jsonapi']);
}
if (!$module_handler->moduleExists('rest')) {
unset($links['openapi_redoc.rest']);
}
}
route_callbacks:
- '\Drupal\openapi_redoc\Routing\Routes::routes'
<?php
namespace Drupal\openapi_redoc\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Provides callback for generating docs page.
*/
class DocController extends ControllerBase {
/**
* The configuration object factory.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* Create a new DocController.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* Current request.
*/
public function __construct(RequestStack $request_stack) {
$this->request = $request_stack->getCurrentRequest();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('request_stack'));
}
/**
* Generates the doc page.
*
* @param string $api_module
* The API module.
*
* @return array
* A render array.
*/
public function generateDocs($api_module) {
$options = $this->request->get('options', []);
$build = [
'#theme' => 'redoc',
'#openapi_url' => Url::fromRoute("openapi.download", ['openapi_generator' => $api_module], ['query' => ['_format' => 'json', 'options' => $options]])->setAbsolute()->toString(),
];
return $build;
}
/**
* Gets the page title.
*
* @param string $api_module
* The API module.
*
* @return string
* The title.
*/
public function getTitle($api_module) {
$title = '';
// @todo Support $options in title.
if ($api_module === 'jsonapi') {
$title = 'JSON API documentation';
}
elseif ($api_module === 'rest') {
$title = 'REST API documentation';
}
return $title;
}
}
<?php
namespace Drupal\openapi_redoc\Routing;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Defines dynamic routes.
*
* @internal
*/
class Routes implements ContainerInjectionInterface {
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Routes constructor.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
public function routes() {
$collection = new RouteCollection();
foreach (['rest', 'jsonapi'] as $api_module) {
if ($this->moduleHandler->moduleExists($api_module)) {
$route_name = "openapi_redoc.$api_module";
$route = (new Route("/admin/config/services/openapi/redoc/$api_module"))
->setDefault(RouteObjectInterface::CONTROLLER_NAME, '\Drupal\openapi_redoc\Controller\DocController::generateDocs')
->setDefault('api_module', $api_module)
->setDefault('_title_callback', '\Drupal\openapi_redoc\Controller\DocController::getTitle')
->setMethods(['GET'])
->setRequirements([
'_permission' => 'access openapi api docs',
]);
$collection->add($route_name, $route);
}
}
return $collection;
}
}
<redoc spec-url='{{ openapi_url }}' {{ attributes }} ></redoc>
<script src="{{ js_url }}"> </script>
# OpenAPI Swagger UI Module Documentation
The OpenAPI Swagger UI module provides a visual web UI for browsing REST API
documentation using [swagger-ui](https://github.com/swagger-api/swagger-ui).
The swagger-ui library needs to be installed into the drupal libraries folder
for this module to work properly. For sites using composer, the package is found
on Packagist. Otherwise installation can be done manually.
## Installation - Composer (recommended)
If you're using composer to manage the site (recommended), there are two
composer plugins required for this setup, `composer/installers` and `mnsami/
composer-custom-directory-installer`. `composer-custom-directory-installer`
extends the base installer libraries to allow setting installer path for an
individual package. Follow the steps below.
1. Run the following to ensure that you have the `composer/installers` and
`mnsami/composer-custom-directory-installer` packages installed. These package
facilitate the installation of packages into directories other than `/vendor`
(e.g. `web/libraries`) using Composer.
```
composer require composer/installers mnsami/composer-custom-directory-installer
```
2. Edit your project's composer.json file to include instructions for installing
the `swagger-ui` library to Drupal's `/libraries`. Your file should include the
following:
```
"extra": {
"installer-paths": {
...
"web/libraries/{$name}": ["swagger-api/swagger-ui", "type:drupal-library"],
...
}
}
```
3. Run the following to add the swagger-ui library to your composer project. It
will be automatically installed into `web/libraries`
```
composer require swagger-api/swagger-ui 3.0.17
```
*__NOTE:__ Using a version of `swagger-ui` other then 3.0.17, can cause issues
with the display of swagger-ui within your Drupal Site.*
## Installation - Manual
If you are not managing your project through composer you can manually download
swagger-ui from https://github.com/swagger-api/swagger-ui/archive/v3.0.17.zip,
and extract the files into your `/libraries/swagger-ui` for your Drupal Project.
#swagger-ui-container .info .url,
#swagger-ui-container .topbar {
display: none;
}
/**
* @file
* Provides Swagger integration.
*/
(function ($, Drupal, drupalSettings) {
// SwaggerUI expects $ to be defined as the jQuery object.
// @todo File a patch to Swagger UI to not require this.
window.$ = $;
Drupal.behaviors.swaggerui = {
attach: function (context, settings) {
var url = drupalSettings.openapi.json_url;
/*
hljs.configure({
highlightSizeThreshold: 5000
});
*/
// Build a system
const ui = SwaggerUIBundle({
url: url,
//dom_id: '#swagger-ui',
dom_id: "#swagger-ui-container",
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
window.ui = ui;
}
};
})(jQuery, Drupal, drupalSettings);
name: OpenAPI Documentation using Swagger UI
type: module
description: Documentation using the Swagger JS library
core: 8.x
package: OpenAPI
dependencies:
- openapi:openapi
swagger_ui:
js:
/libraries/swagger-ui/dist/swagger-ui-bundle.js: {}
/libraries/swagger-ui/dist/swagger-ui-standalone-preset.js: {}
swagger_ui_integration:
js:
js/swagger.js: {}
css:
theme:
css/swagger-ui.css: {}
dependencies:
- core/jquery
- core/drupal
- core/drupalSettings
openapi_swagger_ui.rest.list:
title: Swagger UI REST API Documentation
description: 'REST API documentation.'
parent: system.admin_config_services
route_name: openapi.swagger_ui.rest.list
openapi_swagger_ui.jsonapi:
title: Swagger UI JSON API Documentation
description: 'JSON API documentation.'
parent: system.admin_config_services
route_name: openapi.swagger_ui.jsonapi
<?php
/**
* @file
* Contains OpenApi SwaggerUI module.
*/
/**
* Implements hook_theme().
*/
function openapi_swagger_ui_theme() {
return [
'swagger_ui' => [
'render element' => 'elements',
],
];
}
/**
* Implements hook_menu_links_discovered_alter().
*/
function openapi_swagger_ui_menu_links_discovered_alter(&$links) {
// Disable menu items depending on module enabled.
// @todo Is this possible to do in custom menu class?
// Currently this is not working.
$module_handler = \Drupal::moduleHandler();
if (!$module_handler->moduleExists('jsonapi')) {
unset($links['openapi_swagger_ui.jsonapi']);
}
if (!$module_handler->moduleExists('rest')) {
unset($links['openapi_swagger_ui.rest.list']);
}
}
route_callbacks:
- '\Drupal\openapi_swagger_ui\Routing\Routes::routes'
<?php
namespace Drupal\openapi_swagger_ui\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Controller for the Swagger UI page callbacks.
*/
abstract class SwaggerUIControllerBase extends ControllerBase {
protected $request;
/**
* The generator plugin id.
* @var string
*/
protected $generator_plugin_id;
/**
* Constructs a new SwaggerController object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Symfony\Component\HttpFoundation\RequestStack $request
* The request.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, RequestStack $request) {
$this->entityTypeManager = $entity_type_manager;
$this->request = $request;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
$container->get('request_stack')
);
}
/**
* Creates render array for documentation page for a given resource url.
*
* @param \Drupal\Core\Url $json_url
* The resource file needed to create the documentation page.
*
* @return array
* The render array.
*/
protected function swaggerUi(Url $json_url) {
$query = [
'_format' => 'json',
];
if ($options = $this->request->getCurrentRequest()->get('options', [])) {
$query['options'] = $options;
}
$json_url->setOption('query', $query);
$build = [
'#theme' => 'swagger_ui',
'#attached' => [
'library' => [
'openapi_swagger_ui/swagger_ui_integration',
'openapi_swagger_ui/swagger_ui',
],
'drupalSettings' => [
'openapi' => [
'json_url' => $json_url->toString(),
],
],
],
];
return $build;
}
/**
* Creates documentations page for non-entity resources.
*
* @return array
* Render array for documentations page.
*/
public function openApiResources() {
$json_url = Url::fromRoute("openapi.download", ['openapi_generator' => $this->generator_plugin_id]);
$build = $this->swaggerUi($json_url);
return $build;
}
}
<?php
namespace Drupal\openapi_swagger_ui\Controller;
/**
* Swagger UI controller for JSON API documentation.
*/
class SwaggerUIJsonApiController extends SwaggerUIControllerBase {
/**
* {@inheritdoc}
*/
protected $generator_plugin_id = 'jsonapi';
}
<?php
namespace Drupal\openapi_swagger_ui\Controller;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Url;
use Drupal\openapi\RestInspectionTrait;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Controller for REST documentation.
*/
class SwaggerUIRestController extends SwaggerUIControllerBase {
use RestInspectionTrait;
/**
* {@inheritdoc}
*/
protected $generator_plugin_id = 'rest';
/**
* Constructs a new SwaggerController object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Symfony\Component\HttpFoundation\RequestStack $request
* The request.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, RequestStack $request) {
parent::__construct($entity_type_manager, $request);
$this->entityTypeManager = $entity_type_manager;
$this->request = $request;
}
/**
* List all REST Doc pages.
*/
public function listResources() {
$return['pages_heading'] = [
'#type' => 'markup',
'#markup' => '<h2>' . $this->t('Documentation Pages') . '</h2>',
];
// @todo Implement non entity doc page.
foreach ($this->getRestEnabledEntityTypes() as $entity_type_id => $entity_type) {
if ($bundle_type = $entity_type->getBundleEntityType()) {
$bundle_storage = $this->entityTypeManager->getStorage($bundle_type);
/** @var \Drupal\Core\Config\Entity\ConfigEntityBundleBase[] $bundles */
$bundles = $bundle_storage->loadMultiple();
$bundle_links = [];
foreach ($bundles as $bundle_name => $bundle) {
$bundle_links[$bundle_name] = [
'title' => $bundle->label(),
'url' => Url::fromRoute('openapi.swagger_ui.rest',
[],
[
'query' => [
'options' =>
[
'entity_type_id' => $entity_type_id,
'bundle_name' => $bundle_name,
],
],
]
),
];
}
$return[$entity_type_id] = [
'#theme' => 'links',
'#links' => $bundle_links,
'#heading' => [
'text' => $this->t('@entity_type bundles', ['@entity_type' => $entity_type->getLabel()]),
'level' => 'h3',
],
];
}
}
return $return;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment