swagger.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. package ginSwagger
  2. import (
  3. "html/template"
  4. "net/http"
  5. "os"
  6. "path/filepath"
  7. "regexp"
  8. "sync"
  9. "golang.org/x/net/webdav"
  10. "github.com/gin-gonic/gin"
  11. "github.com/swaggo/swag"
  12. )
  13. type swaggerConfig struct {
  14. URL string
  15. DocExpansion string
  16. Title string
  17. Oauth2RedirectURL template.JS
  18. DefaultModelsExpandDepth int
  19. DeepLinking bool
  20. PersistAuthorization bool
  21. Oauth2DefaultClientID string
  22. }
  23. // Config stores ginSwagger configuration variables.
  24. type Config struct {
  25. // The url pointing to API definition (normally swagger.json or swagger.yaml). Default is `doc.json`.
  26. URL string
  27. DocExpansion string
  28. InstanceName string
  29. Title string
  30. DefaultModelsExpandDepth int
  31. DeepLinking bool
  32. PersistAuthorization bool
  33. Oauth2DefaultClientID string
  34. }
  35. func (config Config) toSwaggerConfig() swaggerConfig {
  36. return swaggerConfig{
  37. URL: config.URL,
  38. DeepLinking: config.DeepLinking,
  39. DocExpansion: config.DocExpansion,
  40. DefaultModelsExpandDepth: config.DefaultModelsExpandDepth,
  41. Oauth2RedirectURL: "`${window.location.protocol}//${window.location.host}$" +
  42. "{window.location.pathname.split('/').slice(0, window.location.pathname.split('/').length - 1).join('/')}" +
  43. "/oauth2-redirect.html`",
  44. Title: config.Title,
  45. PersistAuthorization: config.PersistAuthorization,
  46. Oauth2DefaultClientID: config.Oauth2DefaultClientID,
  47. }
  48. }
  49. // URL presents the url pointing to API definition (normally swagger.json or swagger.yaml).
  50. func URL(url string) func(*Config) {
  51. return func(c *Config) {
  52. c.URL = url
  53. }
  54. }
  55. // DocExpansion list, full, none.
  56. func DocExpansion(docExpansion string) func(*Config) {
  57. return func(c *Config) {
  58. c.DocExpansion = docExpansion
  59. }
  60. }
  61. // DeepLinking set the swagger deep linking configuration.
  62. func DeepLinking(deepLinking bool) func(*Config) {
  63. return func(c *Config) {
  64. c.DeepLinking = deepLinking
  65. }
  66. }
  67. // DefaultModelsExpandDepth set the default expansion depth for models
  68. // (set to -1 completely hide the models).
  69. func DefaultModelsExpandDepth(depth int) func(*Config) {
  70. return func(c *Config) {
  71. c.DefaultModelsExpandDepth = depth
  72. }
  73. }
  74. // InstanceName set the instance name that was used to generate the swagger documents
  75. // Defaults to swag.Name ("swagger").
  76. func InstanceName(name string) func(*Config) {
  77. return func(c *Config) {
  78. c.InstanceName = name
  79. }
  80. }
  81. // PersistAuthorization Persist authorization information over browser close/refresh.
  82. // Defaults to false.
  83. func PersistAuthorization(persistAuthorization bool) func(*Config) {
  84. return func(c *Config) {
  85. c.PersistAuthorization = persistAuthorization
  86. }
  87. }
  88. // Oauth2DefaultClientID set the default client ID used for OAuth2
  89. func Oauth2DefaultClientID(oauth2DefaultClientID string) func(*Config) {
  90. return func(c *Config) {
  91. c.Oauth2DefaultClientID = oauth2DefaultClientID
  92. }
  93. }
  94. // WrapHandler wraps `http.Handler` into `gin.HandlerFunc`.
  95. func WrapHandler(handler *webdav.Handler, options ...func(*Config)) gin.HandlerFunc {
  96. var config = Config{
  97. URL: "doc.json",
  98. DocExpansion: "list",
  99. InstanceName: swag.Name,
  100. Title: "Swagger UI",
  101. DefaultModelsExpandDepth: 1,
  102. DeepLinking: true,
  103. PersistAuthorization: false,
  104. Oauth2DefaultClientID: "",
  105. }
  106. for _, c := range options {
  107. c(&config)
  108. }
  109. return CustomWrapHandler(&config, handler)
  110. }
  111. // CustomWrapHandler wraps `http.Handler` into `gin.HandlerFunc`.
  112. func CustomWrapHandler(config *Config, handler *webdav.Handler) gin.HandlerFunc {
  113. var once sync.Once
  114. if config.InstanceName == "" {
  115. config.InstanceName = swag.Name
  116. }
  117. if config.Title == "" {
  118. config.Title = "Swagger UI"
  119. }
  120. // create a template with name
  121. index, _ := template.New("swagger_index.html").Parse(swaggerIndexTpl)
  122. var matcher = regexp.MustCompile(`(.*)(index\.html|doc\.json|favicon-16x16\.png|favicon-32x32\.png|/oauth2-redirect\.html|swagger-ui\.css|swagger-ui\.css\.map|swagger-ui\.js|swagger-ui\.js\.map|swagger-ui-bundle\.js|swagger-ui-bundle\.js\.map|swagger-ui-standalone-preset\.js|swagger-ui-standalone-preset\.js\.map)[?|.]*`)
  123. return func(ctx *gin.Context) {
  124. if ctx.Request.Method != http.MethodGet {
  125. ctx.AbortWithStatus(http.StatusMethodNotAllowed)
  126. return
  127. }
  128. matches := matcher.FindStringSubmatch(ctx.Request.RequestURI)
  129. if len(matches) != 3 {
  130. ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
  131. return
  132. }
  133. path := matches[2]
  134. once.Do(func() {
  135. handler.Prefix = matches[1]
  136. })
  137. switch filepath.Ext(path) {
  138. case ".html":
  139. ctx.Header("Content-Type", "text/html; charset=utf-8")
  140. case ".css":
  141. ctx.Header("Content-Type", "text/css; charset=utf-8")
  142. case ".js":
  143. ctx.Header("Content-Type", "application/javascript")
  144. case ".png":
  145. ctx.Header("Content-Type", "image/png")
  146. case ".json":
  147. ctx.Header("Content-Type", "application/json; charset=utf-8")
  148. }
  149. switch path {
  150. case "index.html":
  151. _ = index.Execute(ctx.Writer, config.toSwaggerConfig())
  152. case "doc.json":
  153. doc, err := swag.ReadDoc(config.InstanceName)
  154. if err != nil {
  155. ctx.AbortWithStatus(http.StatusInternalServerError)
  156. return
  157. }
  158. ctx.String(http.StatusOK, doc)
  159. default:
  160. handler.ServeHTTP(ctx.Writer, ctx.Request)
  161. }
  162. }
  163. }
  164. // DisablingWrapHandler turn handler off
  165. // if specified environment variable passed.
  166. func DisablingWrapHandler(handler *webdav.Handler, envName string) gin.HandlerFunc {
  167. if os.Getenv(envName) != "" {
  168. return func(c *gin.Context) {
  169. // Simulate behavior when route unspecified and
  170. // return 404 HTTP code
  171. c.String(http.StatusNotFound, "")
  172. }
  173. }
  174. return WrapHandler(handler)
  175. }
  176. // DisablingCustomWrapHandler turn handler off
  177. // if specified environment variable passed.
  178. func DisablingCustomWrapHandler(config *Config, handler *webdav.Handler, envName string) gin.HandlerFunc {
  179. if os.Getenv(envName) != "" {
  180. return func(c *gin.Context) {
  181. // Simulate behavior when route unspecified and
  182. // return 404 HTTP code
  183. c.String(http.StatusNotFound, "")
  184. }
  185. }
  186. return CustomWrapHandler(config, handler)
  187. }
  188. const swaggerIndexTpl = `<!-- HTML for static distribution bundle build -->
  189. <!DOCTYPE html>
  190. <html lang="en">
  191. <head>
  192. <meta charset="UTF-8">
  193. <title>{{.Title}}</title>
  194. <link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
  195. <link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
  196. <link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
  197. <style>
  198. html
  199. {
  200. box-sizing: border-box;
  201. overflow: -moz-scrollbars-vertical;
  202. overflow-y: scroll;
  203. }
  204. *,
  205. *:before,
  206. *:after
  207. {
  208. box-sizing: inherit;
  209. }
  210. body {
  211. margin:0;
  212. background: #fafafa;
  213. }
  214. </style>
  215. </head>
  216. <body>
  217. <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
  218. <defs>
  219. <symbol viewBox="0 0 20 20" id="unlocked">
  220. <path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
  221. </symbol>
  222. <symbol viewBox="0 0 20 20" id="locked">
  223. <path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
  224. </symbol>
  225. <symbol viewBox="0 0 20 20" id="close">
  226. <path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
  227. </symbol>
  228. <symbol viewBox="0 0 20 20" id="large-arrow">
  229. <path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
  230. </symbol>
  231. <symbol viewBox="0 0 20 20" id="large-arrow-down">
  232. <path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
  233. </symbol>
  234. <symbol viewBox="0 0 24 24" id="jump-to">
  235. <path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
  236. </symbol>
  237. <symbol viewBox="0 0 24 24" id="expand">
  238. <path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
  239. </symbol>
  240. </defs>
  241. </svg>
  242. <div id="swagger-ui"></div>
  243. <script src="./swagger-ui-bundle.js"> </script>
  244. <script src="./swagger-ui-standalone-preset.js"> </script>
  245. <script>
  246. window.onload = function() {
  247. // Build a system
  248. const ui = SwaggerUIBundle({
  249. url: "{{.URL}}",
  250. dom_id: '#swagger-ui',
  251. validatorUrl: null,
  252. oauth2RedirectUrl: {{.Oauth2RedirectURL}},
  253. persistAuthorization: {{.PersistAuthorization}},
  254. presets: [
  255. SwaggerUIBundle.presets.apis,
  256. SwaggerUIStandalonePreset
  257. ],
  258. plugins: [
  259. SwaggerUIBundle.plugins.DownloadUrl
  260. ],
  261. layout: "StandaloneLayout",
  262. docExpansion: "{{.DocExpansion}}",
  263. deepLinking: {{.DeepLinking}},
  264. defaultModelsExpandDepth: {{.DefaultModelsExpandDepth}}
  265. })
  266. const defaultClientId = "{{.Oauth2DefaultClientID}}";
  267. if (defaultClientId) {
  268. ui.initOAuth({
  269. clientId: defaultClientId
  270. })
  271. }
  272. window.ui = ui
  273. }
  274. </script>
  275. </body>
  276. </html>
  277. `