You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

103 lines
2.4 KiB

  1. package middleware
  2. import (
  3. "bytes"
  4. "fmt"
  5. "html/template"
  6. "net/http"
  7. "path"
  8. )
  9. // RedocOpts configures the Redoc middlewares
  10. type RedocOpts struct {
  11. // BasePath for the UI path, defaults to: /
  12. BasePath string
  13. // Path combines with BasePath for the full UI path, defaults to: docs
  14. Path string
  15. // SpecURL the url to find the spec for
  16. SpecURL string
  17. // RedocURL for the js that generates the redoc site, defaults to: https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js
  18. RedocURL string
  19. // Title for the documentation site, default to: API documentation
  20. Title string
  21. }
  22. // EnsureDefaults in case some options are missing
  23. func (r *RedocOpts) EnsureDefaults() {
  24. if r.BasePath == "" {
  25. r.BasePath = "/"
  26. }
  27. if r.Path == "" {
  28. r.Path = "docs"
  29. }
  30. if r.SpecURL == "" {
  31. r.SpecURL = "/swagger.json"
  32. }
  33. if r.RedocURL == "" {
  34. r.RedocURL = redocLatest
  35. }
  36. if r.Title == "" {
  37. r.Title = "API documentation"
  38. }
  39. }
  40. // Redoc creates a middleware to serve a documentation site for a swagger spec.
  41. // This allows for altering the spec before starting the http listener.
  42. //
  43. func Redoc(opts RedocOpts, next http.Handler) http.Handler {
  44. opts.EnsureDefaults()
  45. pth := path.Join(opts.BasePath, opts.Path)
  46. tmpl := template.Must(template.New("redoc").Parse(redocTemplate))
  47. buf := bytes.NewBuffer(nil)
  48. _ = tmpl.Execute(buf, opts)
  49. b := buf.Bytes()
  50. return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  51. if r.URL.Path == pth {
  52. rw.Header().Set("Content-Type", "text/html; charset=utf-8")
  53. rw.WriteHeader(http.StatusOK)
  54. _, _ = rw.Write(b)
  55. return
  56. }
  57. if next == nil {
  58. rw.Header().Set("Content-Type", "text/plain")
  59. rw.WriteHeader(http.StatusNotFound)
  60. _, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
  61. return
  62. }
  63. next.ServeHTTP(rw, r)
  64. })
  65. }
  66. const (
  67. redocLatest = "https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js"
  68. redocTemplate = `<!DOCTYPE html>
  69. <html>
  70. <head>
  71. <title>{{ .Title }}</title>
  72. <!-- needed for adaptive design -->
  73. <meta charset="utf-8"/>
  74. <meta name="viewport" content="width=device-width, initial-scale=1">
  75. <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
  76. <!--
  77. ReDoc doesn't change outer page styles
  78. -->
  79. <style>
  80. body {
  81. margin: 0;
  82. padding: 0;
  83. }
  84. </style>
  85. </head>
  86. <body>
  87. <redoc spec-url='{{ .SpecURL }}'></redoc>
  88. <script src="{{ .RedocURL }}"> </script>
  89. </body>
  90. </html>
  91. `
  92. )