Documenting Responses (v2)
This guide shows how to document API responses with Go-Swagger-Generator v2 for OpenAPI 3.0. Responses describe the output of an API operation for different HTTP status codes.
Basic Response (Single Object)
Here's an example of documenting an endpoint that returns a single object (e.g., a Product DTO) for a successful operation.
package main
import (
"github.com/gin-gonic/gin"
"github.com/ruiborda/go-swagger-generator/v2/src/openapi"
"github.com/ruiborda/go-swagger-generator/v2/src/openapi_spec/mime"
"github.com/ruiborda/go-swagger-generator/v2/src/swagger"
"net/http"
)
// Product DTO
type Product struct {
ID int64 `json:"id,omitempty" yaml:"id,omitempty"`
Name string `json:"name" yaml:"name"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Price float64 `json:"price" yaml:"price"`
Category string `json:"category,omitempty" yaml:"category,omitempty"`
}
// _, _ = swagger.Swagger().ComponentSchemaFromDTO(&Product{})
// Swagger documentation for GET /products/{productId}
var _ = swagger.Swagger().Path("/products/{productId}"). // Path relative to server URL
Get(func(op openapi.Operation) {
op.Summary("Get product by ID").
Description("Returns a single product by its ID.").
OperationID("getProductByIdV2").
Tag("Product Operations").
PathParameter("productId", func(p openapi.Parameter) {
p.Description("ID of product to return").Required(true).
Schema(func(s openapi.Schema) { s.Type("integer").Format("int64") })
}).
Response(http.StatusOK, func(r openapi.Response) { // Define response for HTTP 200 OK
r.Description("Successful operation - product found").
Content(mime.ApplicationJSON, func(mt openapi.MediaType) { // Specify content type
mt.SchemaFromDTO(&Product{}) // Use Product DTO for the response schema
})
// Optionally, add other content types like XML
// r.Content(mime.ApplicationXML, func(mt openapi.MediaType) {
// mt.SchemaFromDTO(&Product{})
// })
}).
Response(http.StatusNotFound, func(r openapi.Response) {
r.Description("Product not found")
// Optionally, define a schema for the error response body
// r.Content(mime.ApplicationJSON, func(mt openapi.MediaType) {
// mt.Schema(openapi.S().Ref("#/components/schemas/ErrorModel"))
// })
})
}).
Doc()
// Handler function (example)
func GetProductByID(c *gin.Context) {
// productIdStr := c.Param("productId")
// Fetch product...
product := Product{ID: 1, Name: "Sample Laptop", Price: 999.99, Category: "Electronics"}
c.JSON(http.StatusOK, product)
}
Array Response
This example shows how to document an endpoint that returns an array of objects.
package main
import (
"github.com/gin-gonic/gin"
"github.com/ruiborda/go-swagger-generator/v2/src/openapi"
"github.com/ruiborda/go-swagger-generator/v2/src/openapi_spec/mime"
"github.com/ruiborda/go-swagger-generator/v2/src/swagger"
"net/http"
)
// Pet DTO
type Pet struct {
ID int64 `json:"id,omitempty" yaml:"id,omitempty"`
Name string `json:"name" yaml:"name"`
Status string `json:"status,omitempty" yaml:"status,omitempty"`
}
// _, _ = swagger.Swagger().ComponentSchemaFromDTO(&Pet{})
// Swagger documentation for GET /pets
var _ = swagger.Swagger().Path("/pets").
Get(func(op openapi.Operation) {
op.Summary("List all pets").
Description("Returns an array of all pets in the system.").
OperationID("listPetsV2").
Tag("Pet Operations").
Response(http.StatusOK, func(r openapi.Response) {
r.Description("Successful operation - array of pets returned").
Content(mime.ApplicationJSON, func(mt openapi.MediaType) {
// For an array response, use SchemaFromDTO with a pointer to a slice of DTO pointers
mt.SchemaFromDTO(&[]*Pet{})
})
})
}).
Doc()
// Handler function (example)
func ListPets(c *gin.Context) {
pets := []*Pet{{ID: 1, Name: "Buddy"}, {ID: 2, Name: "Lucy"}}
c.JSON(http.StatusOK, pets)
}
Multiple Response Status Codes with Different Schemas
Endpoints often return different responses (and schemas) for different status codes (e.g., success vs. error codes).
package main
import (
"github.com/gin-gonic/gin"
"github.com/ruiborda/go-swagger-generator/v2/src/openapi"
"github.com/ruiborda/go-swagger-generator/v2/src/openapi_spec/mime"
"github.com/ruiborda/go-swagger-generator/v2/src/swagger"
"net/http"
)
// User DTO & ErrorResponse DTO
type User struct { ID int64 `json:"id"`; Username string `json:"username"`; Email string `json:"email"`; }
// _, _ = swagger.Swagger().ComponentSchemaFromDTO(&User{})
type ErrorResponse struct { Code int `json:"code"`; Message string `json:"message"`; Details string `json:"details,omitempty"`;}
// _, _ = swagger.Swagger().ComponentSchemaFromDTO(&ErrorResponse{})
// Swagger documentation for POST /users
var _ = swagger.Swagger().Path("/users").
Post(func(op openapi.Operation) {
op.Summary("Create a new user").OperationID("createUserV2").Tag("User Operations").
RequestBody( /* ... define request body ... */ ).
Response(http.StatusCreated, func(r openapi.Response) {
r.Description("User created successfully").
Content(mime.ApplicationJSON, func(mt openapi.MediaType) { mt.SchemaFromDTO(&User{}) })
}).
Response(http.StatusBadRequest, func(r openapi.Response) {
r.Description("Invalid user data supplied").
Content(mime.ApplicationJSON, func(mt openapi.MediaType) { mt.SchemaFromDTO(&ErrorResponse{}) })
}).
Response(http.StatusConflict, func(r openapi.Response) {
r.Description("Username or email already exists").
Content(mime.ApplicationJSON, func(mt openapi.MediaType) { mt.SchemaFromDTO(&ErrorResponse{}) })
}).
Response(http.StatusInternalServerError, func(r openapi.Response) {
r.Description("Internal server error encountered").
Content(mime.ApplicationJSON, func(mt openapi.MediaType) { mt.SchemaFromDTO(&ErrorResponse{}) })
})
}).
Doc()
// Handler function (example)
func CreateUser(c *gin.Context) {
// ... bind user data ...
// if validationFails { c.JSON(http.StatusBadRequest, ErrorResponse{...}); return }
// if conflict { c.JSON(http.StatusConflict, ErrorResponse{...}); return }
createdUser := User{ID: 1, Username: "newuser", Email: "new@example.com"}
c.JSON(http.StatusCreated, createdUser)
}
Response Headers
This example demonstrates how to document custom headers returned in a response.
package main
import (
"github.com/gin-gonic/gin"
"github.com/ruiborda/go-swagger-generator/v2/src/openapi"
"github.com/ruiborda/go-swagger-generator/v2/src/openapi_spec/mime"
"github.com/ruiborda/go-swagger-generator/v2/src/swagger"
"net/http"
"time"
)
// Swagger documentation for POST /auth/login (example with headers)
var _ = swagger.Swagger().Path("/auth/login").
Post(func(op openapi.Operation) {
op.Summary("User login").OperationID("loginUserV2").Tag("Auth Operations").
RequestBody( /* ... define login request body ... */ ).
Response(http.StatusOK, func(r openapi.Response) {
r.Description("Login successful, token returned in body, rate limits in headers").
Header("X-Rate-Limit-Limit", func(h openapi.Header) {
h.Description("The number of allowed requests in the current period").
Schema(func(s openapi.Schema) { s.Type("integer") })
}).
Header("X-Rate-Limit-Remaining", func(h openapi.Header) {
h.Description("The number of remaining requests in the current period").
Schema(func(s openapi.Schema) { s.Type("integer") })
}).
Header("X-Token-Expires-At", func(h openapi.Header) {
h.Description("Timestamp when the authentication token expires").
Schema(func(s openapi.Schema) { s.Type("string").Format("date-time") })
}).
Content(mime.ApplicationJSON, func(mt openapi.MediaType) {
mt.Schema(func(s openapi.Schema) { // Schema for login token response
s.Type("object").Property("token", func(ps openapi.Schema){ ps.Type("string") })
})
})
}).
Response(http.StatusUnauthorized, func(r openapi.Response) {
r.Description("Invalid credentials")
})
}).
Doc()
// Handler function (example)
func LoginUser(c *gin.Context) {
// ... authenticate user ...
c.Header("X-Rate-Limit-Limit", "1000")
c.Header("X-Rate-Limit-Remaining", "999")
c.Header("X-Token-Expires-At", time.Now().Add(1*time.Hour).Format(time.RFC3339))
c.JSON(http.StatusOK, gin.H{"token": "dummy-jwt-token"})
}
Paginated Response
Here's how to document a response that is paginated, often returning metadata alongside the data array.
package main
import (
"github.com/gin-gonic/gin"
"github.com/ruiborda/go-swagger-generator/v2/src/openapi"
"github.com/ruiborda/go-swagger-generator/v2/src/openapi_spec/mime"
"github.com/ruiborda/go-swagger-generator/v2/src/swagger"
"net/http"
// "strconv"
)
// Article DTO
type Article struct { ID int64 `json:"id"`; Title string `json:"title"`; }
// _, _ = swagger.Swagger().ComponentSchemaFromDTO(&Article{})
// PagedResponse - Generic DTO for paginated responses. Could also be defined inline.
// type PagedResponse struct {
// Page int `json:"page"`
// PageSize int `json:"pageSize"`
// TotalPages int `json:"totalPages"`
// TotalItems int `json:"totalItems"`
// Data interface{} `json:"data"` // Use specific type like []*Article here
// }
// Swagger documentation for GET /articles
var _ = swagger.Swagger().Path("/articles").
Get(func(op openapi.Operation) {
op.Summary("List articles with pagination").OperationID("listArticlesV2").Tag("Article Operations").
QueryParameter("page", func(p openapi.Parameter) { /* ... */ }).
QueryParameter("pageSize", func(p openapi.Parameter) { /* ... */ }).
Response(http.StatusOK, func(r openapi.Response) {
r.Description("Successful operation - paginated list of articles").
Content(mime.ApplicationJSON, func(mt openapi.MediaType) {
mt.Schema(func(s openapi.Schema) { // Inline schema for paginated response structure
s.Type("object").
Property("page", func(ps openapi.Schema){ ps.Type("integer").Description("Current page number") }).
Property("pageSize", func(ps openapi.Schema){ ps.Type("integer").Description("Number of items per page") }).
Property("totalPages", func(ps openapi.Schema){ ps.Type("integer").Description("Total number of pages") }).
Property("totalItems", func(ps openapi.Schema){ ps.Type("integer").Description("Total number of items") }).
Property("data", func(ps openapi.Schema){ // The actual data array
ps.Type("array").Items(openapi.S().Ref("#/components/schemas/Article"))
})
})
})
})
}).
Doc()
// Handler function (example)
func ListArticles(c *gin.Context) {
// page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
// pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
articles := []*Article{{ID: 1, Title: "Intro to OpenAPI"}}
response := gin.H{
"page": 1, "pageSize": 20, "totalPages": 5, "totalItems": 100,
"data": articles,
}
c.JSON(http.StatusOK, response)
}
File Download Response
Documenting a file download, where the response body is the file content.
package main
import (
"github.com/gin-gonic/gin"
"github.com/ruiborda/go-swagger-generator/v2/src/openapi"
"github.com/ruiborda/go-swagger-generator/v2/src/openapi_spec/mime"
"github.com/ruiborda/go-swagger-generator/v2/src/swagger"
"net/http"
)
// Swagger documentation for GET /reports/{reportId}/download
var _ = swagger.Swagger().Path("/reports/{reportId}/download").
Get(func(op openapi.Operation) {
op.Summary("Download a report file").OperationID("downloadReportV2").Tag("Report Operations").
PathParameter("reportId", func(p openapi.Parameter) { /* ... */ }).
QueryParameter("format", func(p openapi.Parameter) { // e.g., format=pdf or format=csv
p.Description("File format for download").Required(false).
Schema(func(s openapi.Schema){ s.Type("string").Enum("pdf", "csv", "xlsx").Default("pdf") })
}).
Response(http.StatusOK, func(r openapi.Response) {
r.Description("Report file downloaded successfully").
// Multiple content types can be specified if the format query param changes the output type
Content(mime.ApplicationPdf, func(mt openapi.MediaType) { // For PDF
mt.Schema(func(s openapi.Schema) { s.Type("string").Format("binary") })
}).
Content("text/csv", func(mt openapi.MediaType) { // For CSV
mt.Schema(func(s openapi.Schema) { s.Type("string").Format("binary") })
})
// For a single known binary type, only one Content entry is needed.
}).
Response(http.StatusNotFound, func(r openapi.Response) {
r.Description("Report not found or format unavailable")
})
}).
Doc()
// Handler function (example)
func DownloadReport(c *gin.Context) {
// reportID := c.Param("reportId")
format := c.DefaultQuery("format", "pdf")
var contentType string
var fileData []byte // Your report data
switch format {
case "pdf":
contentType = mime.ApplicationPdf
fileData = []byte("%PDF-1.4 sample PDF content")
case "csv":
contentType = "text/csv"
fileData = []byte("col1,col2\nval1,val2")
default:
c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported format"})
return
}
c.Header("Content-Disposition", "attachment; filename=report."+format)
c.Data(http.StatusOK, contentType, fileData)
}