Skip to content

Commit

Permalink
Merge pull request #788 from gofiber/fix-708
Browse files Browse the repository at this point in the history
Refactoring and fixes to Swagger Middleware
  • Loading branch information
ReneWerner87 committed Sep 18, 2023
2 parents be85346 + 2968d4d commit fb703be
Show file tree
Hide file tree
Showing 6 changed files with 398 additions and 68 deletions.
117 changes: 92 additions & 25 deletions swagger/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
id: swagger
title: Swagger
---

# Swagger
Expand All @@ -14,52 +15,118 @@ Swagger middleware for [Fiber](https://github.com/gofiber/fiber). The middleware

**Note: Requires Go 1.18 and above**

## Install
### Table of Contents
- [Signatures](#signatures)
- [Installation](#installation)
- [Examples](#examples)
- [Config](#config)
- [Default Config](#default-config)

```
go get -u github.com/gofiber/fiber/v2
go get -u github.com/gofiber/contrib/swagger
```

## Signatures
### Signatures
```go
func New(config ...swagger.Config) fiber.Handler
```

## Config

| Property | Type | Description | Default |
|:----------|:---------|:----------------------------------------------------------------------|:--------|
| BasePath | `string` | BasePath is the base path for the configuration file. | `""` |
| FilePath | `string` | FilePath is the file path to the configuration file. | `""` |

### Installation
Swagger is tested on the latests [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet:
```bash
go mod init github.com/<user>/<repo>
```
And then install the swagger middleware:
```bash
go get github.com/gofiber/contrib/swagger
```

## Examples
Import the middleware package that is part of the Fiber web framework
### Examples
Import the middleware package
```go
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/contrib/swagger"
)
```

Then create a Fiber app with app := fiber.New().

After you initiate your Fiber app, you can use the following possibilities:

## Default Config

Using the default config:
```go
app.Use(swagger.New(cfg))
```

## Custom Config

Using a custom config:
```go
cfg := swagger.Config{
BasePath: "/", //swagger ui base path
BasePath: "/",
FilePath: "./docs/swagger.json",
Path: "swagger",
Title: "Swagger API Docs",
}

app.Use(swagger.New(cfg))
```

Using multiple instances of Swagger:
```go
// Create Swagger middleware for v1
//
// Swagger will be available at: /api/v1/docs
app.Use(swagger.New(swagger.Config{
BasePath: "/api/v1/",
FilePath: "./docs/v1/swagger.json",
Path: "docs",
}))

// Create Swagger middleware for v2
//
// Swagger will be available at: /api/v2/docs
app.Use(swagger.New(swagger.Config{
BasePath: "/api/v2/",
FilePath: "./docs/v2/swagger.json",
Path: "docs",
}))
```

### Config
```go
type Config struct {
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c *fiber.Ctx) bool

// BasePath for the UI path
//
// Optional. Default: /
BasePath string

// FilePath for the swagger.json or swagger.yaml file
//
// Optional. Default: ./swagger.json
FilePath string

// Path combines with BasePath for the full UI path
//
// Optional. Default: docs
Path string

// Title for the documentation site
//
// Optional. Default: Fiber API documentation
Title string

// CacheAge defines the max-age for the Cache-Control header in seconds.
//
// Optional. Default: 3600 (1 hour)
CacheAge int
}
```

### Default Config
```go
var ConfigDefault = Config{
Next: nil,
BasePath: "/",
FilePath: "./swagger.json",
Path: "docs",
Title: "Fiber API documentation",
CacheAge: 3600, // Default to 1 hour
}
```
7 changes: 3 additions & 4 deletions swagger/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,21 @@ module github.com/gofiber/contrib/swagger
go 1.18

require (
github.com/go-openapi/loads v0.21.2
github.com/go-openapi/runtime v0.26.0
github.com/gofiber/fiber/v2 v2.49.2
github.com/gorilla/handlers v1.5.1
github.com/stretchr/testify v1.8.4
github.com/valyala/fasthttp v1.50.0
gopkg.in/yaml.v2 v2.4.0
)

require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/spec v0.20.8 // indirect
github.com/go-openapi/strfmt v0.21.7 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
Expand All @@ -36,6 +34,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.50.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
go.mongodb.org/mongo-driver v1.11.3 // indirect
golang.org/x/sys v0.12.0 // indirect
Expand Down
4 changes: 0 additions & 4 deletions swagger/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc=
github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo=
Expand Down Expand Up @@ -80,8 +78,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
Expand Down
129 changes: 109 additions & 20 deletions swagger/swagger.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,60 @@ package swagger

import (
"encoding/json"
"errors"
"fmt"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime/middleware"
"github.com/gofiber/fiber/v2"
"github.com/gorilla/handlers"
"github.com/valyala/fasthttp/fasthttpadaptor"
"log"
"net/http"
"os"
"path"
"strings"

"github.com/go-openapi/runtime/middleware"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/adaptor"
"gopkg.in/yaml.v2"
)

// Config defines the config for middleware.
type Config struct {
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c *fiber.Ctx) bool

// BasePath for the UI path
//
// Optional. Default: /
BasePath string

// FilePath for the swagger.json or swagger.yaml file
//
// Optional. Default: ./swagger.json
FilePath string

// Path combines with BasePath for the full UI path
//
// Optional. Default: docs
Path string

// Title for the documentation site
//
// Optional. Default: Fiber API documentation
Title string

// CacheAge defines the max-age for the Cache-Control header in seconds.
//
// Optional. Default: 3600 (1 hour)
CacheAge int
}

// ConfigDefault is the default config
var ConfigDefault = Config{
Next: nil,
BasePath: "/",
FilePath: "./swagger.json",
Path: "docs",
Title: "Fiber API documentation",
CacheAge: 3600, // Default to 1 hour
}

// New creates a new middleware handler
Expand All @@ -40,36 +74,91 @@ func New(config ...Config) fiber.Handler {
if len(cfg.FilePath) == 0 {
cfg.FilePath = ConfigDefault.FilePath
}
if len(cfg.Path) == 0 {
cfg.Path = ConfigDefault.Path
}
if len(cfg.Title) == 0 {
cfg.Title = ConfigDefault.Title
}
if cfg.CacheAge == 0 {
cfg.CacheAge = ConfigDefault.CacheAge
}
}

// Verify Swagger file exists
if _, err := os.Stat(cfg.FilePath); os.IsNotExist(err) {
panic(errors.New(fmt.Sprintf("%s file is not exist", cfg.FilePath)))
panic(fmt.Errorf("%s file does not exist", cfg.FilePath))
}

specDoc, err := loads.Spec(cfg.FilePath)
// Read Swagger Spec into memory
rawSpec, err := os.ReadFile(cfg.FilePath)
if err != nil {
log.Fatalf("Failed to read provided Swagger file (%s): %v", cfg.FilePath, err.Error())
panic(err)
}

b, err := json.MarshalIndent(specDoc.Spec(), "", " ")
if err != nil {
panic(err)
// Validate we have valid JSON or YAML
var jsonData map[string]interface{}
errJSON := json.Unmarshal(rawSpec, &jsonData)
var yamlData map[string]interface{}
errYAML := yaml.Unmarshal(rawSpec, &yamlData)

if errJSON != nil && errYAML != nil {
log.Fatalf("Failed to parse the Swagger spec as JSON or YAML: JSON error: %s, YAML error: %s", errJSON, errYAML)
panic(fmt.Errorf("Invalid Swagger spec file: %s", cfg.FilePath))
}

specUrl := path.Join(cfg.BasePath, "swagger.json")
// Generate URL path's for the middleware
specURL := path.Join(cfg.BasePath, cfg.FilePath)
swaggerUIPath := path.Join(cfg.BasePath, cfg.Path)

// Serve the Swagger spec from memory
swaggerSpecHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, ".yaml") || strings.HasSuffix(r.URL.Path, ".yml") {
w.Header().Set("Content-Type", "application/yaml")
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", cfg.CacheAge))
_, err := w.Write(rawSpec)
if err != nil {
http.Error(w, "Error processing YAML Swagger Spec", http.StatusInternalServerError)
return
}
} else if strings.HasSuffix(r.URL.Path, ".json") {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", cfg.CacheAge))
_, err := w.Write(rawSpec)
if err != nil {
http.Error(w, "Error processing JSON Swagger Spec", http.StatusInternalServerError)
return
}
} else {
http.NotFound(w, r)
}
})

// Define UI Options
swaggerUIOpts := middleware.SwaggerUIOpts{
BasePath: cfg.BasePath,
SpecURL: specUrl,
Path: "docs",
SpecURL: specURL,
Path: cfg.Path,
Title: cfg.Title,
}

swaggerUiHandler := middleware.SwaggerUI(swaggerUIOpts, nil)
specFileHandler, err := handlers.CORS()(middleware.Spec(cfg.BasePath, b, swaggerUiHandler)), nil
// Create UI middleware
middlewareHandler := adaptor.HTTPHandler(middleware.SwaggerUI(swaggerUIOpts, swaggerSpecHandler))

// Return new handler
return func(c *fiber.Ctx) error {
handler := fasthttpadaptor.NewFastHTTPHandler(specFileHandler)
handler(c.Context())
return nil
// Don't execute middleware if Next returns true
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}

// Only respond to requests to SwaggerUI and SpecURL (swagger.json)
if !(c.Path() == swaggerUIPath || c.Path() == specURL) {
return c.Next()
}

// Pass Fiber context to handler
return middlewareHandler(c)
}
}
}
12 changes: 12 additions & 0 deletions swagger/swagger.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
consumes:
- application/json
produces:
- application/json
schemes:
- http
swagger: "2.0"
info:
description: "Documentation for TestApi"
title: "TestApi"
version: "1.0.0"
basePath: "/"
Loading

0 comments on commit fb703be

Please sign in to comment.