mirror of
https://github.com/DanielnetoDotCom/YouPHPTube
synced 2025-10-04 18:29:39 +02:00
415 lines
13 KiB
Markdown
415 lines
13 KiB
Markdown
# Plug points
|
||
|
||
Swagger UI exposes most of its internal logic through the plugin system.
|
||
|
||
Often, it is beneficial to override the core internals to achieve custom behavior.
|
||
|
||
### Note: Semantic Versioning
|
||
|
||
Swagger UI's internal APIs are _not_ part of our public contract, which means that they can change without the major version change.
|
||
|
||
If your custom plugins wrap, extend, override, or consume any internal core APIs, we recommend specifying a specific minor version of Swagger UI to use in your application, because they will _not_ change between patch versions.
|
||
|
||
If you're installing Swagger UI via NPM, for example, you can do this by using a tilde:
|
||
|
||
```js
|
||
{
|
||
"dependencies": {
|
||
"swagger-ui": "~3.11.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
### `fn.opsFilter`
|
||
|
||
When using the `filter` option, tag names will be filtered by the user-provided value. If you'd like to customize this behavior, you can override the default `opsFilter` function.
|
||
|
||
For example, you can implement a multiple-phrase filter:
|
||
|
||
```js
|
||
const MultiplePhraseFilterPlugin = function() {
|
||
return {
|
||
fn: {
|
||
opsFilter: (taggedOps, phrase) => {
|
||
const phrases = phrase.split(", ")
|
||
|
||
return taggedOps.filter((val, key) => {
|
||
return phrases.some(item => key.indexOf(item) > -1)
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
### Logo component
|
||
While using the Standalone Preset the SwaggerUI logo is rendered in the Top Bar.
|
||
The logo can be exchanged by replacing the `Logo` component via the plugin api:
|
||
|
||
```jsx
|
||
import React from "react";
|
||
const MyLogoPlugin = {
|
||
components: {
|
||
Logo: () => (
|
||
<img alt="My Logo" height="40" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTM3IiBoZWlnaHQ9IjEzNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KCiA8Zz4KICA8dGl0bGU+TGF5ZXIgMTwvdGl0bGU+CiAgPHRleHQgdHJhbnNmb3JtPSJtYXRyaXgoMy40Nzc2OSAwIDAgMy4yNjA2NyAtNjczLjEyOCAtNjkxLjk5MykiIHN0cm9rZT0iIzAwMCIgZm9udC1zdHlsZT0ibm9ybWFsIiBmb250LXdlaWdodD0ibm9ybWFsIiB4bWw6c3BhY2U9InByZXNlcnZlIiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtZmFtaWx5PSInT3BlbiBTYW5zIEV4dHJhQm9sZCciIGZvbnQtc2l6ZT0iMjQiIGlkPSJzdmdfMSIgeT0iMjQxLjIyMTkyIiB4PSIxOTYuOTY5MjEiIHN0cm9rZS13aWR0aD0iMCIgZmlsbD0iIzYyYTAzZiI+TXkgTG9nbzwvdGV4dD4KICA8cGF0aCBpZD0ic3ZnXzIiIGQ9Im0zOTUuNjAyNSw1MS4xODM1OWw1My44Nzc3MSwwbDE2LjY0ODYzLC01MS4xODM1OGwxNi42NDg2NCw1MS4xODM1OGw1My44Nzc3LDBsLTQzLjU4NzksMzEuNjMyODNsMTYuNjQ5NDksNTEuMTgzNThsLTQzLjU4NzkyLC0zMS42MzM2OWwtNDMuNTg3OTEsMzEuNjMzNjlsMTYuNjQ5NDksLTUxLjE4MzU4bC00My41ODc5MiwtMzEuNjMyODN6IiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZT0iIzAwMCIgZmlsbD0iIzYyYTAzZiIvPgogPC9nPgo8L3N2Zz4="/>
|
||
)
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
### JSON Schema components
|
||
In swagger there are so called JSON Schema components. These are used to render inputs for parameters and components of request bodies with `application/x-www-form-urlencoded` or `multipart/*` media-type.
|
||
|
||
Internally swagger uses following mapping to find the JSON Schema component from OpenAPI Specification schema information:
|
||
|
||
For each schema’s type(eg. `string`, `array`, …) and if defined schema’s format (eg. ‘date’, ‘uuid’, …) there is a corresponding component mapping:
|
||
|
||
**If format defined:**
|
||
```js
|
||
`JsonSchema_${type}_${format}`
|
||
```
|
||
|
||
**Fallback if `JsonSchema_${type}_${format}` component does not exist or format not defined:**
|
||
```js
|
||
`JsonSchema_${type}`
|
||
```
|
||
|
||
**Default:**
|
||
```js
|
||
`JsonSchema_string`
|
||
```
|
||
|
||
With this, one can define custom input components or override existing.
|
||
|
||
#### Example Date-Picker plugin
|
||
|
||
If one would like to input date values you could provide a custom plugin to integrate [react-datepicker](https://www.npmjs.com/package/react-datepicker) into swagger-ui.
|
||
All you need to do is to create a component to wrap [react-datepicker](https://www.npmjs.com/package/react-datepicker) accordingly to the format.
|
||
|
||
**There are two cases:**
|
||
- ```yaml
|
||
type: string
|
||
format: date
|
||
```
|
||
The resulting name for mapping to succeed: `JsonSchema_string_date`
|
||
- ```yaml
|
||
type: string
|
||
format: date-time
|
||
```
|
||
The resulting name for mapping to succeed: `JsonSchema_string_date-time`
|
||
|
||
This creates the need for two components and simple logic to strip any time input in case the format is date:
|
||
```js
|
||
import React from "react";
|
||
import DatePicker from "react-datepicker";
|
||
import "react-datepicker/dist/react-datepicker.css";
|
||
|
||
const JsonSchema_string_date = (props) => {
|
||
const dateNumber = Date.parse(props.value);
|
||
const date = dateNumber
|
||
? new Date(dateNumber)
|
||
: new Date();
|
||
|
||
return (
|
||
<DatePicker
|
||
selected={date}
|
||
onChange={d => props.onChange(d.toISOString().substring(0, 10))}
|
||
/>
|
||
);
|
||
}
|
||
|
||
const JsonSchema_string_date_time = (props) => {
|
||
const dateNumber = Date.parse(props.value);
|
||
const date = dateNumber
|
||
? new Date(dateNumber)
|
||
: new Date();
|
||
|
||
return (
|
||
<DatePicker
|
||
selected={date}
|
||
onChange={d => props.onChange(d.toISOString())}
|
||
showTimeSelect
|
||
timeFormat="p"
|
||
dateFormat="Pp"
|
||
/>
|
||
);
|
||
}
|
||
|
||
|
||
export const DateTimeSwaggerPlugin = {
|
||
components: {
|
||
JsonSchema_string_date: JsonSchema_string_date,
|
||
"JsonSchema_string_date-time": JsonSchema_string_date_time
|
||
}
|
||
};
|
||
```
|
||
|
||
### Request Snippets
|
||
|
||
SwaggerUI can be configured with the `requestSnippetsEnabled: true` option to activate Request Snippets.
|
||
Instead of the generic curl that is generated upon doing a request. It gives you more granular options:
|
||
- curl for bash
|
||
- curl for cmd
|
||
- curl for powershell
|
||
|
||
There might be the case where you want to provide your own snipped generator. This can be done by using the plugin api.
|
||
A Request Snipped generator consists of the configuration and a `fn`,
|
||
which takes the internal request object and transforms it to the desired snippet.
|
||
|
||
```js
|
||
// Add config to Request Snippets Configuration with an unique key like "node_native"
|
||
const snippetConfig = {
|
||
requestSnippetsEnabled: true,
|
||
requestSnippets: {
|
||
generators: {
|
||
"node_native": {
|
||
title: "NodeJs Native",
|
||
syntax: "javascript"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
const SnippedGeneratorNodeJsPlugin = {
|
||
fn: {
|
||
// use `requestSnippetGenerator_` + key from config (node_native) for generator fn
|
||
requestSnippetGenerator_node_native: (request) => {
|
||
const url = new Url(request.get("url"))
|
||
let isMultipartFormDataRequest = false
|
||
const headers = request.get("headers")
|
||
if(headers && headers.size) {
|
||
request.get("headers").map((val, key) => {
|
||
isMultipartFormDataRequest = isMultipartFormDataRequest || /^content-type$/i.test(key) && /^multipart\/form-data$/i.test(val)
|
||
})
|
||
}
|
||
const packageStr = url.protocol === "https:" ? "https" : "http"
|
||
let reqBody = request.get("body")
|
||
if (request.get("body")) {
|
||
if (isMultipartFormDataRequest && ["POST", "PUT", "PATCH"].includes(request.get("method"))) {
|
||
return "throw new Error(\"Currently unsupported content-type: /^multipart\\/form-data$/i\");"
|
||
} else {
|
||
if (!Map.isMap(reqBody)) {
|
||
if (typeof reqBody !== "string") {
|
||
reqBody = JSON.stringify(reqBody)
|
||
}
|
||
} else {
|
||
reqBody = getStringBodyOfMap(request)
|
||
}
|
||
}
|
||
} else if (!request.get("body") && request.get("method") === "POST") {
|
||
reqBody = ""
|
||
}
|
||
|
||
const stringBody = "`" + (reqBody || "")
|
||
.replace(/\\n/g, "\n")
|
||
.replace(/`/g, "\\`")
|
||
+ "`"
|
||
|
||
return `const http = require("${packageStr}");
|
||
const options = {
|
||
"method": "${request.get("method")}",
|
||
"hostname": "${url.host}",
|
||
"port": ${url.port || "null"},
|
||
"path": "${url.pathname}"${headers && headers.size ? `,
|
||
"headers": {
|
||
${request.get("headers").map((val, key) => `"${key}": "${val}"`).valueSeq().join(",\n ")}
|
||
}` : ""}
|
||
};
|
||
const req = http.request(options, function (res) {
|
||
const chunks = [];
|
||
res.on("data", function (chunk) {
|
||
chunks.push(chunk);
|
||
});
|
||
res.on("end", function () {
|
||
const body = Buffer.concat(chunks);
|
||
console.log(body.toString());
|
||
});
|
||
});
|
||
${reqBody ? `\nreq.write(${stringBody});` : ""}
|
||
req.end();`
|
||
}
|
||
}
|
||
}
|
||
|
||
const ui = SwaggerUIBundle({
|
||
"dom_id": "#swagger-ui",
|
||
deepLinking: true,
|
||
presets: [
|
||
SwaggerUIBundle.presets.apis,
|
||
SwaggerUIStandalonePreset
|
||
],
|
||
plugins: [
|
||
SwaggerUIBundle.plugins.DownloadUrl,
|
||
SnippedGeneratorNodeJsPlugin
|
||
],
|
||
layout: "StandaloneLayout",
|
||
validatorUrl: "https://validator.swagger.io/validator",
|
||
url: "https://petstore.swagger.io/v2/swagger.json",
|
||
...snippetConfig,
|
||
})
|
||
```
|
||
|
||
### Error handling
|
||
|
||
SwaggerUI comes with a `safe-render` plugin that handles error handling allows plugging into error handling system and modify it.
|
||
|
||
The plugin accepts a list of component names that should be protected by error boundaries.
|
||
|
||
Its public API looks like this:
|
||
|
||
```js
|
||
{
|
||
fn: {
|
||
componentDidCatch,
|
||
withErrorBoundary: withErrorBoundary(getSystem),
|
||
},
|
||
components: {
|
||
ErrorBoundary,
|
||
Fallback,
|
||
},
|
||
}
|
||
```
|
||
|
||
safe-render plugin is automatically utilized by [base](https://github.com/swagger-api/swagger-ui/blob/78f62c300a6d137e65fd027d850981b010009970/src/core/presets/base.js) and [standalone](https://github.com/swagger-api/swagger-ui/tree/78f62c300a6d137e65fd027d850981b010009970/src/standalone) SwaggerUI presets and
|
||
should always be used as the last plugin, after all the components are already known to the SwaggerUI.
|
||
The plugin defines a default list of components that should be protected by error boundaries:
|
||
|
||
```js
|
||
[
|
||
"App",
|
||
"BaseLayout",
|
||
"VersionPragmaFilter",
|
||
"InfoContainer",
|
||
"ServersContainer",
|
||
"SchemesContainer",
|
||
"AuthorizeBtnContainer",
|
||
"FilterContainer",
|
||
"Operations",
|
||
"OperationContainer",
|
||
"parameters",
|
||
"responses",
|
||
"OperationServers",
|
||
"Models",
|
||
"ModelWrapper",
|
||
"Topbar",
|
||
"StandaloneLayout",
|
||
"onlineValidatorBadge"
|
||
]
|
||
```
|
||
|
||
As demonstrated below, additional components can be protected by utilizing the safe-render plugin
|
||
with configuration options. This gets really handy if you are a SwaggerUI integrator and you maintain a number of
|
||
plugins with additional custom components.
|
||
|
||
```js
|
||
const swaggerUI = SwaggerUI({
|
||
url: "https://petstore.swagger.io/v2/swagger.json",
|
||
dom_id: '#swagger-ui',
|
||
plugins: [
|
||
() => ({
|
||
components: {
|
||
MyCustomComponent1: () => 'my custom component',
|
||
},
|
||
}),
|
||
SwaggerUI.plugins.SafeRender({
|
||
fullOverride: true, // only the component list defined here will apply (not the default list)
|
||
componentList: [
|
||
"MyCustomComponent1",
|
||
],
|
||
}),
|
||
],
|
||
});
|
||
```
|
||
|
||
##### componentDidCatch
|
||
|
||
This static function is invoked after a component has thrown an error.
|
||
It receives two parameters:
|
||
|
||
1. `error` - The error that was thrown.
|
||
2. `info` - An object with a componentStack key containing [information about which component threw the error](https://reactjs.org/docs/error-boundaries.html#component-stack-traces).
|
||
|
||
It has precisely the same signature as error boundaries [componentDidCatch lifecycle method](https://reactjs.org/docs/react-component.html#componentdidcatch),
|
||
except it's a static function and not a class method.
|
||
|
||
Default implement of componentDidCatch uses `console.error` to display the received error:
|
||
|
||
```js
|
||
export const componentDidCatch = console.error;
|
||
```
|
||
|
||
To utilize your own error handling logic (e.g. [bugsnag](https://www.bugsnag.com/)), create new SwaggerUI plugin that overrides componentDidCatch:
|
||
|
||
{% highlight js linenos %}
|
||
const BugsnagErrorHandlerPlugin = () => {
|
||
// init bugsnag
|
||
|
||
return {
|
||
fn: {
|
||
componentDidCatch = (error, info) => {
|
||
Bugsnag.notify(error);
|
||
Bugsnag.notify(info);
|
||
},
|
||
},
|
||
};
|
||
};
|
||
{% endhighlight %}
|
||
|
||
##### withErrorBoundary
|
||
|
||
This function is HOC (Higher Order Component). It wraps a particular component into the `ErrorBoundary` component.
|
||
It can be overridden via a plugin system to control how components are wrapped by the ErrorBoundary component.
|
||
In 99.9% of situations, you won't need to override this function, but if you do, please read the source code of this function first.
|
||
|
||
##### Fallback
|
||
|
||
The component is displayed when the error boundary catches an error. It can be overridden via a plugin system.
|
||
Its default implementation is trivial:
|
||
|
||
```js
|
||
import React from "react"
|
||
import PropTypes from "prop-types"
|
||
|
||
const Fallback = ({ name }) => (
|
||
<div className="fallback">
|
||
😱 <i>Could not render { name === "t" ? "this component" : name }, see the console.</i>
|
||
</div>
|
||
)
|
||
Fallback.propTypes = {
|
||
name: PropTypes.string.isRequired,
|
||
}
|
||
export default Fallback
|
||
```
|
||
|
||
Feel free to override it to match your look & feel:
|
||
|
||
```js
|
||
const CustomFallbackPlugin = () => ({
|
||
components: {
|
||
Fallback: ({ name } ) => `This is my custom fallback. ${name} failed to render`,
|
||
},
|
||
});
|
||
|
||
const swaggerUI = SwaggerUI({
|
||
url: "https://petstore.swagger.io/v2/swagger.json",
|
||
dom_id: '#swagger-ui',
|
||
plugins: [
|
||
CustomFallbackPlugin,
|
||
]
|
||
});
|
||
```
|
||
|
||
##### ErrorBoundary
|
||
|
||
This is the component that implements React error boundaries. Uses `componentDidCatch` and `Fallback`
|
||
under the hood. In 99.9% of situations, you won't need to override this component, but if you do,
|
||
please read the source code of this component first.
|
||
|
||
|
||
##### Change in behavior
|
||
|
||
In prior releases of SwaggerUI (before v4.3.0), almost all components have been protected, and when thrown error,
|
||
`Fallback` component was displayed. This changes with SwaggerUI v4.3.0. Only components defined
|
||
by the `safe-render` plugin are now protected and display fallback. If a small component somewhere within
|
||
SwaggerUI React component tree fails to render and throws an error. The error bubbles up to the closest
|
||
error boundary, and that error boundary displays the `Fallback` component and invokes `componentDidCatch`.
|