プラグポイント
Swagger UI は、その内部ロジックのほとんどをプラグインシステムを通じて公開しています。
カスタム動作を実現するために、コア内部をオーバーライドすることがしばしば有益です。
注:セマンティックバージョニング
Swagger UI の内部 API は、当社の公開契約の一部では**ありません**。つまり、メジャーバージョンの変更なしに変更される可能性があります。
カスタムプラグインが内部コア API をラップ、拡張、オーバーライド、または使用する場合、パッチバージョン間では変更されないため、アプリケーションで使用する Swagger UI の特定のマイナーバージョンを指定することをお勧めします。
例えば、NPM 経由で Swagger UI をインストールしている場合、チルダを使用することでこれを行うことができます。
1{2 "dependencies": {3 "swagger-ui": "~3.11.0"4 }5}
fn.opsFilter
filter
オプションを使用すると、タグ名はユーザーが指定した値でフィルタリングされます。この動作をカスタマイズしたい場合は、デフォルトの opsFilter
関数をオーバーライドできます。
例えば、複数のフレーズフィルタを実装できます。
1const MultiplePhraseFilterPlugin = function() {2 return {3 fn: {4 opsFilter: (taggedOps, phrase) => {5 const phrases = phrase.split(", ")6
7 return taggedOps.filter((val, key) => {8 return phrases.some(item => key.indexOf(item) > -1)9 })10 }11 }12 }13}
ロゴコンポーネント
Standalone Preset を使用している場合、SwaggerUI のロゴはトップバーに表示されます。ロゴはプラグイン API を介して Logo
コンポーネントを置き換えることで変更できます。
1import React from "react";2const MyLogoPlugin = {3 components: {4 Logo: () => (5 <img alt="My Logo" height="40" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTM3IiBoZWlnaHQ9IjEzNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KCiA8Zz4KICA8dGl0bGU+TGF5ZXIgMTwvdGl0bGU+CiAgPHRleHQgdHJhbnNmb3JtPSJtYXRyaXgoMy40Nzc2OSAwIDAgMy4yNjA2NyAtNjczLjEyOCAtNjkxLjk5MykiIHN0cm9rZT0iIzAwMCIgZm9udC1zdHlsZT0ibm9ybWFsIiBmb250LXdlaWdodD0ibm9ybWFsIiB4bWw6c3BhY2U9InByZXNlcnZlIiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtZmFtaWx5PSInT3BlbiBTYW5zIEV4dHJhQm9sZCciIGZvbnQtc2l6ZT0iMjQiIGlkPSJzdmdfMSIgeT0iMjQxLjIyMTkyIiB4PSIxOTYuOTY5MjEiIHN0cm9rZS13aWR0aD0iMCIgZmlsbD0iIzYyYTAzZiI+TXkgTG9nbzwvdGV4dD4KICA8cGF0aCBpZD0ic3ZnXzIiIGQ9Im0zOTUuNjAyNSw1MS4xODM1OWw1My44Nzc3MSwwbDE2LjY0ODYzLC01MS4xODM1OGwxNi42NDg2NCw1MS4xODM1OGw1My44Nzc3LDBsLTQzLjU4NzksMzEuNjMyODNsMTYuNjQ5NDksNTEuMTgzNThsLTQzLjU4NzkyLC0zMS42MzM2OWwtNDMuNTg3OTEsMzEuNjMzNjlsMTYuNjQ5NDksLTUxLjE4MzU4bC00My41ODc5MiwtMzEuNjMyODN6IiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZT0iIzAwMCIgZmlsbD0iIzYyYTAzZiIvPgogPC9nPgo8L3N2Zz4="/>6 )7 }8}
JSON Schema コンポーネント
Swagger には、JSON スキーマコンポーネントと呼ばれるものがあります。これらは、application/x-www-form-urlencoded
または multipart/*
メディアタイプのリクエストボディのパラメータとコンポーネントの入力レンダリングに使用されます。
Swagger は内部的に、OpenAPI Specification のスキーマ情報から JSON スキーマコンポーネントを見つけるために以下のマッピングを使用します。
各スキーマのタイプ(例:string
、array
など)と、定義されていればスキーマのフォーマット(例:「date」、「uuid」など)に対応するコンポーネントマッピングがあります。
フォーマットが定義されている場合
1`JsonSchema_${type}_${format}`
JsonSchema_${type}_${format}
コンポーネントが存在しない場合、またはフォーマットが定義されていない場合のフォールバック
1`JsonSchema_${type}`
デフォルト
1`JsonSchema_string`
これにより、カスタム入力コンポーネントを定義したり、既存のコンポーネントをオーバーライドしたりできます。
例:Date-Picker プラグイン
日付値を入力したい場合、react-datepicker を swagger-ui に統合するためのカスタムプラグインを提供できます。必要なのは、フォーマットに合わせて react-datepicker をラップするコンポーネントを作成することだけです。
2つのケースがあります。
-
マッピングが成功するための結果名:1type: string2format: date
JsonSchema_string_date
-
マッピングが成功するための結果名:1type: string2format: date-time
JsonSchema_string_date-time
これにより、フォーマットが日付の場合に時間入力を削除する2つのコンポーネントとシンプルなロジックが必要になります。
1import React from "react";2import DatePicker from "react-datepicker";3import "react-datepicker/dist/react-datepicker.css";4
5const JsonSchema_string_date = (props) => {6 const dateNumber = Date.parse(props.value);7 const date = dateNumber8 ? new Date(dateNumber)9 : new Date();10
11 return (12 <DatePicker13 selected={date}14 onChange={d => props.onChange(d.toISOString().substring(0, 10))}15 />16 );17}18
19const JsonSchema_string_date_time = (props) => {20 const dateNumber = Date.parse(props.value);21 const date = dateNumber22 ? new Date(dateNumber)23 : new Date();24
25 return (26 <DatePicker27 selected={date}28 onChange={d => props.onChange(d.toISOString())}29 showTimeSelect30 timeFormat="p"31 dateFormat="Pp"32 />33 );34}35
36
37export const DateTimeSwaggerPlugin = {38 components: {39 JsonSchema_string_date: JsonSchema_string_date,40 "JsonSchema_string_date-time": JsonSchema_string_date_time41 }42};
リクエストスニペット
SwaggerUI は requestSnippetsEnabled: true
オプションで設定して、リクエストスニペットを有効にできます。
リクエストを行った際に生成される一般的な curl の代わりに、よりきめ細かなオプションを提供します。
- bash 用 curl
- cmd 用 curl
- powershell 用 curl
独自のスニペットジェネレーターを提供したい場合があります。これはプラグインAPIを使用することで可能です。
リクエストスニペットジェネレーターは、設定と fn
で構成されます。
これは内部リクエストオブジェクトを受け取り、目的のスニペットに変換します。
1// Add config to Request Snippets Configuration with an unique key like "node_native"2const snippetConfig = {3 requestSnippetsEnabled: true,4 requestSnippets: {5 generators: {6 "node_native": {7 title: "NodeJs Native",8 syntax: "javascript"9 }10 }11 }12}13
14const SnippedGeneratorNodeJsPlugin = {15 fn: {16 // use `requestSnippetGenerator_` + key from config (node_native) for generator fn17 requestSnippetGenerator_node_native: (request) => {18 const url = new Url(request.get("url"))19 let isMultipartFormDataRequest = false20 const headers = request.get("headers")21 if(headers && headers.size) {22 request.get("headers").map((val, key) => {23 isMultipartFormDataRequest = isMultipartFormDataRequest || /^content-type$/i.test(key) && /^multipart\/form-data$/i.test(val)24 })25 }26 const packageStr = url.protocol === "https:" ? "https" : "http"27 let reqBody = request.get("body")28 if (request.get("body")) {29 if (isMultipartFormDataRequest && ["POST", "PUT", "PATCH"].includes(request.get("method"))) {30 return "throw new Error(\"Currently unsupported content-type: /^multipart\\/form-data$/i\");"31 } else {32 if (!Map.isMap(reqBody)) {33 if (typeof reqBody !== "string") {34 reqBody = JSON.stringify(reqBody)35 }36 } else {37 reqBody = getStringBodyOfMap(request)38 }39 }40 } else if (!request.get("body") && request.get("method") === "POST") {41 reqBody = ""42 }43
44 const stringBody = "`" + (reqBody || "")45 .replace(/\\n/g, "\n")46 .replace(/`/g, "\\`")47 + "`"48
49 return `const http = require("${packageStr}");50const options = {51 "method": "${request.get("method")}",52 "hostname": "${url.host}",53 "port": ${url.port || "null"},54 "path": "${url.pathname}"${headers && headers.size ? `,55 "headers": {56 ${request.get("headers").map((val, key) => `"${key}": "${val}"`).valueSeq().join(",\n ")}57 }` : ""}58};59const req = http.request(options, function (res) {60 const chunks = [];61 res.on("data", function (chunk) {62 chunks.push(chunk);63 });64 res.on("end", function () {65 const body = Buffer.concat(chunks);66 console.log(body.toString());67 });68});69${reqBody ? `\nreq.write(${stringBody});` : ""}70req.end();`71 }72 }73}74
75const ui = SwaggerUIBundle({76 "dom_id": "#swagger-ui",77 deepLinking: true,78 presets: [79 SwaggerUIBundle.presets.apis,80 SwaggerUIStandalonePreset81 ],82 plugins: [83 SwaggerUIBundle.plugins.DownloadUrl,84 SnippedGeneratorNodeJsPlugin85 ],86 layout: "StandaloneLayout",87 validatorUrl: "https://validator.swagger.io/validator",88 url: "https://petstore.swagger.io/v2/swagger.json",89 ...snippetConfig,90})
エラー処理
SwaggerUI には、エラー処理を扱う safe-render
プラグインがあり、エラー処理システムに接続して変更することができます。
このプラグインは、エラー境界で保護されるべきコンポーネント名のリストを受け入れます。
その公開 API は次のようになります。
1{2 fn: {3 componentDidCatch,4 withErrorBoundary: withErrorBoundary(getSystem),5 },6 components: {7 ErrorBoundary,8 Fallback,9 },10}
safe-render プラグインは、base および standalone SwaggerUI プリセットによって自動的に利用され、すべてのコンポーネントがSwaggerUIに認識された後、常に最後のプラグインとして使用されるべきです。このプラグインは、エラー境界によって保護されるべきコンポーネントのデフォルトリストを定義します。
1[2 "App",3 "BaseLayout",4 "VersionPragmaFilter",5 "InfoContainer",6 "ServersContainer",7 "SchemesContainer",8 "AuthorizeBtnContainer",9 "FilterContainer",10 "Operations",11 "OperationContainer",12 "parameters",13 "responses",14 "OperationServers",15 "Models",16 "ModelWrapper",17 "Topbar",18 "StandaloneLayout",19 "onlineValidatorBadge"20]
以下に示すように、設定オプションを持つ safe-render プラグインを利用することで、追加のコンポーネントを保護することができます。これは、SwaggerUI インテグレータであり、追加のカスタムコンポーネントを持つ多数のプラグインを管理している場合に非常に便利です。
1const swaggerUI = SwaggerUI({2 url: "https://petstore.swagger.io/v2/swagger.json",3 dom_id: '#swagger-ui',4 plugins: [5 () => ({6 components: {7 MyCustomComponent1: () => 'my custom component',8 },9 }),10 SwaggerUI.plugins.SafeRender({11 fullOverride: true, // only the component list defined here will apply (not the default list)12 componentList: [13 "MyCustomComponent1",14 ],15 }),16 ],17});
componentDidCatch
この静的関数は、コンポーネントがエラーをスローした後に呼び出されます。
2つのパラメータを受け取ります。
error
- スローされたエラー。info
- コンポーネントスタックキーを持つオブジェクトで、どのコンポーネントがエラーをスローしたかに関する情報が含まれています。
エラー境界の componentDidCatch ライフサイクルメソッドと全く同じシグネチャを持っていますが、これは静的関数であり、クラスメソッドではありません。
componentDidCatch のデフォルトの実装は、受け取ったエラーを表示するために console.error
を使用します。
1export 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%の状況では、この関数をオーバーライドする必要はありませんが、もしオーバーライドする場合は、まずこの関数のソースコードを読んでください。
フォールバック
エラー境界がエラーをキャッチしたときに表示されるコンポーネントです。プラグインシステムを介してオーバーライドできます。そのデフォルトの実装はごく単純です。
1import React from "react"2import PropTypes from "prop-types"3
4const Fallback = ({ name }) => (5 <div className="fallback">6 😱 <i>Could not render { name === "t" ? "this component" : name }, see the console.</i>7 </div>8)9Fallback.propTypes = {10 name: PropTypes.string.isRequired,11}12export default Fallback
あなたのルック&フィールに合わせて自由にオーバーライドしてください。
1const CustomFallbackPlugin = () => ({2 components: {3 Fallback: ({ name } ) => `This is my custom fallback. ${name} failed to render`,4 },5});6
7const swaggerUI = SwaggerUI({8 url: "https://petstore.swagger.io/v2/swagger.json",9 dom_id: '#swagger-ui',10 plugins: [11 CustomFallbackPlugin,12 ]13});
エラー境界
これは、React エラー境界を実装するコンポーネントです。componentDidCatch
と Fallback
を内部で使用します。99.9%の状況では、このコンポーネントをオーバーライドする必要はありませんが、もしオーバーライドする場合は、まずこのコンポーネントのソースコードを読んでください。
動作の変更
以前の SwaggerUI リリース (v4.3.0 以前) では、ほとんどすべてのコンポーネントが保護されており、エラーがスローされると Fallback
コンポーネントが表示されていました。これは SwaggerUI v4.3.0 で変更されます。safe-render
プラグインで定義されたコンポーネントのみが保護され、フォールバックが表示されるようになりました。SwaggerUI React コンポーネントツリー内のどこかの小さなコンポーネントがレンダリングに失敗してエラーをスローした場合、そのエラーは最も近いエラー境界までバブルアップし、そのエラー境界が Fallback
コンポーネントを表示し、componentDidCatch
を呼び出します。