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