Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/oapi-codegen/oapi-codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ func main() {
switch g {
case "client":
opts.GenerateClient = true
case "gin-server":
opts.GenerateGinServer = true
case "chi-server":
opts.GenerateChiServer = true
case "server":
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ module github.com/deepmap/oapi-codegen
require (
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c
github.com/getkin/kin-openapi v0.61.0
github.com/gin-gonic/gin v1.7.2
github.com/go-chi/chi/v5 v5.0.0
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219
github.com/labstack/echo/v4 v4.2.1
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/pkg/errors v0.8.1
github.com/stretchr/testify v1.5.1
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -48,8 +49,9 @@ github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
Expand Down Expand Up @@ -97,3 +99,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
16 changes: 16 additions & 0 deletions pkg/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (

// Options defines the optional code to generate.
type Options struct {
GenerateGinServer bool // GenerateChiServer specifies whether to generate chi server boilerplate

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. GenerateChiServer should be GenerateGinServer

GenerateChiServer bool // GenerateChiServer specifies whether to generate chi server boilerplate
GenerateEchoServer bool // GenerateEchoServer specifies whether to generate echo server boilerplate
GenerateClient bool // GenerateClient specifies whether to generate client boilerplate
Expand Down Expand Up @@ -150,6 +151,14 @@ func Generate(swagger *openapi3.T, packageName string, opts Options) (string, er

}

var ginServerOut string
if opts.GenerateGinServer {
ginServerOut, err = GenerateGinServer(t, ops)
if err != nil {
return "", errors.Wrap(err, "error generating Go handlers for Paths")
}
}

var echoServerOut string
if opts.GenerateEchoServer {
echoServerOut, err = GenerateEchoServer(t, ops)
Expand Down Expand Up @@ -226,6 +235,13 @@ func Generate(swagger *openapi3.T, packageName string, opts Options) (string, er
}
}

if opts.GenerateGinServer {
_, err = w.WriteString(ginServerOut)
if err != nil {
return "", errors.Wrap(err, "error writing server path handlers")
}
}

if opts.GenerateEchoServer {
_, err = w.WriteString(echoServerOut)
if err != nil {
Expand Down
29 changes: 29 additions & 0 deletions pkg/codegen/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,35 @@ func GenerateTypesForOperations(t *template.Template, ops []OperationDefinition)
return buf.String(), nil
}

// GenerateChiServer This function generates all the go code for the ServerInterface as well as

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. GenerateChiServer should be GenerateGinServer

// all the wrapper functions around our handlers.
func GenerateGinServer(t *template.Template, operations []OperationDefinition) (string, error) {
var buf bytes.Buffer
w := bufio.NewWriter(&buf)

err := t.ExecuteTemplate(w, "gin-interface.tmpl", operations)
if err != nil {
return "", errors.Wrap(err, "error generating server interface")
}

err = t.ExecuteTemplate(w, "gin-middleware.tmpl", operations)
if err != nil {
return "", errors.Wrap(err, "error generating server middleware")
}

err = t.ExecuteTemplate(w, "gin-handler.tmpl", operations)
if err != nil {
return "", errors.Wrap(err, "error generating server http handler")
}

err = w.Flush()
if err != nil {
return "", errors.Wrap(err, "error flushing output buffer for server")
}

return buf.String(), nil
}

// GenerateChiServer This function generates all the go code for the ServerInterface as well as
// all the wrapper functions around our handlers.
func GenerateChiServer(t *template.Template, operations []OperationDefinition) (string, error) {
Expand Down
2 changes: 2 additions & 0 deletions pkg/codegen/template_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ var TemplateFunctions = template.FuncMap{
"genParamTypes": genParamTypes,
"genParamNames": genParamNames,
"genParamFmtString": ReplacePathParamsWithStr,
"swaggerUriToGinUri": SwaggerUriToGinUri,
"swaggerUriToEchoUri": SwaggerUriToEchoUri,
"swaggerUriToChiUri": SwaggerUriToChiUri,
"lcFirst": LowercaseFirstCharacter,
Expand All @@ -275,6 +276,7 @@ var TemplateFunctions = template.FuncMap{
"genResponseUnmarshal": genResponseUnmarshal,
"getResponseTypeDefinitions": getResponseTypeDefinitions,
"toStringArray": toStringArray,
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": strings.Title,
"stripNewLines": stripNewLines,
Expand Down
42 changes: 42 additions & 0 deletions pkg/codegen/templates/gin-handler.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Handler creates http.Handler with routing matching OpenAPI spec.
func Handler(si ServerInterface) *gin.Engine {
return HandlerWithOptions(si, GinServerOptions{})
}

type GinServerOptions struct {
BaseURL string
BaseRouter *gin.Engine
Middlewares []MiddlewareFunc
}

// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux.
func HandlerFromMux(si ServerInterface, r *gin.Engine) *gin.Engine {
return HandlerWithOptions(si, GinServerOptions {
BaseRouter: r,
})
}

func HandlerFromMuxWithBaseURL(si ServerInterface, r *gin.Engine, baseURL string) *gin.Engine {
return HandlerWithOptions(si, GinServerOptions {
BaseURL: baseURL,
BaseRouter: r,
})
}

// HandlerWithOptions creates http.Handler with additional options
func HandlerWithOptions(si ServerInterface, options GinServerOptions) *gin.Engine {
r := options.BaseRouter

if r == nil {
r = gin.New()
}
{{if .}}wrapper := ServerInterfaceWrapper{
Handler: si,
HandlerMiddlewares: options.Middlewares,
}
{{end}}
{{range .}}
r.{{.Method | upper }}(options.BaseURL+"{{.Path | swaggerUriToGinUri}}", wrapper.{{.OperationId}})
{{end}}
return r
}
7 changes: 7 additions & 0 deletions pkg/codegen/templates/gin-interface.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// ServerInterface represents all server handlers.
type ServerInterface interface {
{{range .}}{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
{{.OperationId}}(c *gin.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}})
{{end}}
}
177 changes: 177 additions & 0 deletions pkg/codegen/templates/gin-middleware.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// ServerInterfaceWrapper converts contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
HandlerMiddlewares []MiddlewareFunc
}

type MiddlewareFunc func(c *gin.Context)

{{range .}}{{$opid := .OperationId}}

// {{$opid}} operation middleware
func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) {

{{if or .RequiresParamObject (gt (len .PathParams) 0) }}
var err error
{{end}}

{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" -------------
var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}}

{{if .IsPassThrough}}
{{$varName}} = c.Query("{{.ParamName}}")
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(c.Query("{{.ParamName}}")), &{{$varName}})
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": "Error unmarshaling parameter '{{.ParamName}}' as JSON"})
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", c.Query("{{.ParamName}}"), &{{$varName}})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be c.Param not c.Query?

if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)})
return
}
{{end}}

{{end}}

{{range .SecurityDefinitions}}
c.Set({{.ProviderName | ucFirst}}Scopes, {{toStringArray .Scopes}})
{{end}}

{{if .RequiresParamObject}}
// Parameter object where we will unmarshal all parameters from the context
var params {{.OperationId}}Params

{{range $paramIdx, $param := .QueryParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
if paramValue := c.Query("{{.ParamName}}"); paramValue != "" {

{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
{{end}}

{{if .IsJson}}
var value {{.TypeDef}}
err = json.Unmarshal([]byte(paramValue), &value)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": "Error unmarshaling parameter '{{.ParamName}}' as JSON"})
return
}

params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}{{if .Required}} else {
c.JSON(http.StatusBadRequest, gin.H{"msg": "Query argument {{.ParamName}} is required, but not found"})
return
}{{end}}
{{if .IsStyled}}
err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", c.Request.URL.Query(), &params.{{.GoName}})
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)})
return
}
{{end}}
{{end}}

{{if .HeaderParams}}
headers := r.Header

{{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" -------------
if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found {
var {{.GoName}} {{.TypeDef}}
n := len(valueList)
if n != 1 {
c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n)})
return
}

{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
{{end}}

{{if .IsJson}}
err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}})
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": "Error unmarshaling parameter '{{.ParamName}}' as JSON"})
return
}
{{end}}

{{if .IsStyled}}
err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, valueList[0], &{{.GoName}})
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)})
return
}
{{end}}

params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}

} {{if .Required}}else {
c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found: %s", err)})
return
}{{end}}

{{end}}
{{end}}

{{range .CookieParams}}
var cookie *http.Cookie

if cookie, err = c.Cookie("{{.ParamName}}"); err == nil {

{{- if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
{{end}}

{{- if .IsJson}}
var value {{.TypeDef}}
var decoded string
decoded, err := url.QueryUnescape(cookie.Value)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": "Error unescaping cookie parameter '{{.ParamName}}'"})
return
}

err = json.Unmarshal([]byte(decoded), &value)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": "Error unmarshaling parameter '{{.ParamName}}' as JSON"})
return
}

params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}

{{- if .IsStyled}}
var value {{.TypeDef}}
err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": "Invalid format for parameter {{.ParamName}}: %s"})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}

}

{{- if .Required}} else {
c.JSON(http.StatusBadRequest, gin.H{"msg": "Query argument {{.ParamName}} is required, but not found"})
return
}
{{- end}}
{{end}}
{{end}}

for _, middleware := range siw.HandlerMiddlewares {
middleware(c)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you want a IsAborted check to prevent the handler from being executed if the middleware aborts?

siw.Handler.{{.OperationId}}(c{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}})
}
{{end}}



Loading