プラグインポイント
Swagger UI は、その内部ロジックの大部分をプラグインシステムを通じて公開しています。
多くの場合、カスタム動作を実現するためにコア内部をオーバーライドすることが有益です。
注:セマンティックバージョニング
Swagger UI の内部 API は、公開契約の一部ではありません。つまり、メジャーバージョンの変更なしに変更される可能性があります。
カスタムプラグインが内部コア API をラップ、拡張、オーバーライド、または使用する場合、アプリケーションで使用するための Swagger UI の特定のマイナーバージョンを指定することをお勧めします。パッチバージョン間では変更されません。
たとえば、NPM 経由で Swagger UI をインストールする場合は、チルダを使用してこれを行うことができます。
{
"dependencies": {
"swagger-ui": "~3.11.0"
}
}
fn.opsFilter
filter
オプションを使用する場合、タグ名はユーザーが提供した値によってフィルタリングされます。この動作をカスタマイズする場合は、デフォルトの opsFilter
関数をオーバーライドできます。
たとえば、複数句のフィルターを実装できます。
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)
})
}
}
}
}
ロゴコンポーネント
スタンドアロンプリセットを使用する場合、SwaggerUI ロゴはトップバーに表示されます。プラグイン API を介して `Logo` コンポーネントを置き換えることで、ロゴを交換できます。
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 スキーマコンポーネント
Swagger には、JSON スキーマコンポーネントと呼ばれるものがあります。これらは、application/x-www-form-urlencoded
または multipart/*
メディアタイプのパラメーターとリクエストボディのコンポーネントの入力をレンダリングするために使用されます。
内部的に、Swagger は OpenAPI 仕様スキーマ情報から JSON スキーマコンポーネントを見つけるために次のマッピングを使用します。
各スキーマのタイプ(例:string
、array
など)と、定義されている場合のスキーマのフォーマット(例:'date'、'uuid' など)には、対応するコンポーネントマッピングがあります。
フォーマットが定義されている場合
`JsonSchema_${type}_${format}`
JsonSchema_${type}_${format}
コンポーネントが存在しない場合、またはフォーマットが定義されていない場合のフォールバック
`JsonSchema_${type}`
デフォルト
`JsonSchema_string`
これにより、カスタム入力コンポーネントを定義したり、既存のコンポーネントをオーバーライドしたりできます。
日付ピッカープラグインの例
日付値を入力したい場合は、カスタムプラグインを提供して react-datepicker を swagger-ui に統合できます。必要なのは、フォーマットに合わせて react-datepicker をラップするコンポーネントを作成することだけです。
2 つのケースがあります。
これにより、2 つのコンポーネントと、フォーマットが日付の場合に時間入力を削除するための単純なロジックが必要になります。
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
}
};
リクエストスニペット
SwaggerUI は、requestSnippetsEnabled: true
オプションを使用して構成し、リクエストスニペットをアクティブ化できます。
リクエストを行う際に生成される一般的な curl の代わりに、より詳細なオプションが提供されます。
- bash 用 curl
- cmd 用 curl
- powershell 用 curl
独自の snipped ジェネレーターを提供したい場合があるかもしれません。これは、プラグイン API を使用して行うことができます。
リクエストスニペットジェネレーターは、構成とfn
で構成されます。
これは、内部リクエストオブジェクトを受け取り、それを目的のスニペットに変換します。
// 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,
})
エラー処理
SwaggerUI には、エラー処理を処理するsafe-render
プラグインが付属しており、エラー処理システムにプラグインし、それを変更できます。
このプラグインは、エラー境界によって保護されるべきコンポーネント名のリストを受け入れます。
公開 API は次のようになります。
{
fn: {
componentDidCatch,
withErrorBoundary: withErrorBoundary(getSystem),
},
components: {
ErrorBoundary,
Fallback,
},
}
safe-render プラグインは、base と standalone SwaggerUI プリセットによって自動的に使用され、すべてのコンポーネントが SwaggerUI に既に認識された後に、常に最後のプラグインとして使用する必要があります。このプラグインは、エラー境界によって保護されるべきコンポーネントのデフォルトリストを定義します。
[
"App",
"BaseLayout",
"VersionPragmaFilter",
"InfoContainer",
"ServersContainer",
"SchemesContainer",
"AuthorizeBtnContainer",
"FilterContainer",
"Operations",
"OperationContainer",
"parameters",
"responses",
"OperationServers",
"Models",
"ModelWrapper",
"Topbar",
"StandaloneLayout",
"onlineValidatorBadge"
]
以下に示すように、構成オプションを使用して safe-render プラグインを使用することで、追加のコンポーネントを保護できます。これは、SwaggerUI インテグレーターであり、追加のカスタムコンポーネントを含む多数のプラグインを管理している場合に非常に役立ちます。
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
この静的関数は、コンポーネントがエラーをスローした後に呼び出されます。
2 つの引数を受け取ります。
error
- スローされたエラー。
info
- どのコンポーネントがエラーをスローしたかについての情報を含む `componentStack` キーを持つオブジェクト。情報はこちら
これは、エラー境界の componentDidCatch ライフサイクルメソッド とまったく同じシグネチャを持っていますが、静的関数であり、クラスメソッドではありません。
componentDidCatch のデフォルトの実装では、console.error
を使用して受信したエラーを表示します。
export const componentDidCatch = console.error;
独自のエラー処理ロジック(例:bugsnag)を使用するには、componentDidCatch をオーバーライドする新しい SwaggerUI プラグインを作成します。
{% highlight js linenos %} const BugsnagErrorHandlerPlugin = () => { // bugsnag の初期化
return { fn: { componentDidCatch = (error, info) => { Bugsnag.notify(error); Bugsnag.notify(info); }, }, }; }; {% endhighlight %}
withErrorBoundary
この関数は HOC(Higher Order Component)です。特定のコンポーネントをErrorBoundary
コンポーネントにラップします。コンポーネントが ErrorBoundary コンポーネントによってどのようにラップされるかを制御するために、プラグインシステムを介してオーバーライドできます。99.9% の状況では、この関数をオーバーライドする必要はありませんが、オーバーライドする場合は、最初にこの関数のソースコードを参照してください。
フォールバック
エラー境界がエラーをキャッチしたときに表示されるコンポーネントです。プラグインシステムを介してオーバーライドできます。デフォルトの実装は単純です。
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
自由にオーバーライドして、見た目と感触を合わせることができます。
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
これは、React エラー境界を実装するコンポーネントです。内部的にcomponentDidCatch
とFallback
を使用します。99.9% の状況では、このコンポーネントをオーバーライドする必要はありませんが、オーバーライドする場合は、最初にこのコンポーネントのソースコードを参照してください。
動作の変更
SwaggerUI の以前のリリース(v4.3.0 より前)では、ほとんどすべてのコンポーネントが保護されており、エラーが発生すると、Fallback
コンポーネントが表示されていました。これは SwaggerUI v4.3.0 で変更されました。現在、safe-render
プラグインで定義されたコンポーネントのみが保護され、フォールバックが表示されます。SwaggerUI React コンポーネントツリー内の小さなコンポーネントがレンダリングに失敗してエラーをスローした場合、エラーは最も近いエラー境界までバブルアップし、そのエラー境界はFallback
コンポーネントを表示し、componentDidCatch
を呼び出します。