Skip to content

Instantly share code, notes, and snippets.

@phoenisx
Created November 16, 2025 07:47
Show Gist options
  • Select an option

  • Save phoenisx/6a2e9aefc2e5f0fa3822688b37db4eef to your computer and use it in GitHub Desktop.

Select an option

Save phoenisx/6a2e9aefc2e5f0fa3822688b37db4eef to your computer and use it in GitHub Desktop.
Custom oapi-codegen strict handler template to manage error handling. Helps with issue: https://github.com/oapi-codegen/oapi-codegen/issues/2085
{{range .}}
{{/**
* Each operation's RequestObject
*/}}
{{$opid := .OperationId -}}
type {{$opid | ucFirst}}RequestObject struct {
{{range .PathParams -}}
{{.GoName | ucFirst}} {{.TypeDef}} {{.JsonTag}}
{{end -}}
{{if .RequiresParamObject -}}
Params {{$opid}}Params
{{end -}}
{{if .HasMaskedRequestContentTypes -}}
ContentType string
{{end -}}
{{$multipleBodies := gt (len .Bodies) 1 -}}
{{range .Bodies -}}
{{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if eq .NameTag "Multipart"}}*multipart.Reader{{else if ne .NameTag ""}}*{{$opid}}{{.NameTag}}RequestBody{{else}}io.Reader{{end}}
{{end -}}
}
{{/**
*
* Each operation's ResponseObject
*/}}
type {{$opid | ucFirst}}ResponseObject interface {
Visit{{$opid}}Response(w http.ResponseWriter) error
}
{{/**
* Loops through Response to find all it's details like headers and bodies etc.
*/}}
{{range .Responses}}
{{$statusCode := .StatusCode -}}
{{$hasHeaders := ne 0 (len .Headers) -}}
{{$fixedStatusCode := .HasFixedStatusCode -}}
{{$isRef := .IsRef -}}
{{$isExternalRef := .IsExternalRef -}}
{{$ref := .Ref | ucFirstWithPkgName -}}
{{$headers := .Headers -}}
{{/**
*
* Each Response's Headers
*/}}
{{if (and $hasHeaders (not $isRef)) -}}
type {{$opid}}{{$statusCode}}ResponseHeaders struct {
{{range .Headers -}}
{{.GoName}} {{.Schema.TypeDecl}}
{{end -}}
}
{{end}}
{{/**
*
* Loop through Response Bodies and create necessary structs and impl interfaces.
* I can remove unnecessary structs generated for all error responses, i.e 4xx - 5xx
*/}}
{{range .Contents}}
{{$receiverTypeName := printf "%s%s%s%s" $opid $statusCode .NameTagOrContentType "Response"}}
{{/**
*
* Each Response's Bodies struct types
*/}}
{{if and $fixedStatusCode $isRef -}}
{{ if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) (eq .NameTag "Multipart") -}}
type {{$receiverTypeName}} {{$ref}}{{.NameTagOrContentType}}Response
{{else if $isExternalRef -}}
type {{$receiverTypeName}} struct { {{$ref}} }
{{else -}}
{{if lt ($statusCode | atoi) 400 -}}
type {{$receiverTypeName}} struct{ {{$ref}}{{.NameTagOrContentType}}Response }
{{end}}
{{end}}
{{else if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}}
type {{$receiverTypeName}} {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if and .Schema.IsRef (not .Schema.IsExternalRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}}
{{else -}}
type {{$receiverTypeName}} struct {
Body {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}}
{{if $hasHeaders -}}
Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders
{{end -}}
{{if not $fixedStatusCode -}}
StatusCode int
{{end -}}
{{if not .HasFixedContentType -}}
ContentType string
{{end -}}
{{if not .IsSupported -}}
ContentLength int64
{{end -}}
}
{{end}}
{{/**
*
* Each Response's Bodies interface implementation.
*/}}
{{if lt ($statusCode | atoi) 400 -}}
func (response {{$receiverTypeName}}) Visit{{$opid}}Response(w http.ResponseWriter) error {
{{/**
*
* I don't know what this is exactly but feels like form response for with multipart response.
*/}}
{{if eq .NameTag "Multipart" -}}
writer := multipart.NewWriter(w)
{{end -}}
{{/**
*
* Set's Headers content type if exists
*/}}
w.Header().Set("Content-Type", {{if eq .NameTag "Multipart"}}{{if eq .ContentType "multipart/form-data"}}writer.FormDataContentType(){{else}}mime.FormatMediaType("{{.ContentType}}", map[string]string{"boundary": writer.Boundary()}){{end}}{{else if .HasFixedContentType }}"{{.ContentType}}"{{else}}response.ContentType{{end}})
{{if not .IsSupported -}}
if response.ContentLength != 0 {
w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength))
}
{{end -}}
{{/**
*
* Set all kinds of headers defined in schema. We only want to modify for Set-Cookie, so we'll add codition accordingly
*/}}
{{range $headers -}}
{{ printf "%s" .Schema.TypeDecl }}
{{if and (eq .Name "Set-Cookie") -}}
for _, cookie := range response.Headers.SetCookie {
w.Header().Add("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}}))
}
{{else -}}
w.Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}}))
{{end -}}
{{end -}}
w.WriteHeader({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}})
{{$hasBodyVar := or ($hasHeaders) (not $fixedStatusCode) (not .IsSupported)}}
{{if .IsJSON -}}
{{$hasUnionElements := ne 0 (len .Schema.UnionElements)}}
return json.NewEncoder(w).Encode(response{{if $hasBodyVar}}.Body{{end}}{{if $hasUnionElements}}.union{{end}})
{{else if eq .NameTag "Text" -}}
_, err := w.Write([]byte({{if $hasBodyVar}}response.Body{{else}}response{{end}}))
return err
{{else if eq .NameTag "Formdata" -}}
if form, err := runtime.MarshalForm({{if $hasBodyVar}}response.Body{{else}}response{{end}}, nil); err != nil {
return err
} else {
_, err := w.Write([]byte(form.Encode()))
return err
}
{{else if eq .NameTag "Multipart" -}}
defer writer.Close()
return {{if $hasBodyVar}}response.Body{{else}}response{{end}}(writer);
{{else -}}
if closer, ok := response.Body.(io.ReadCloser); ok {
defer closer.Close()
}
_, err := io.Copy(w, response.Body)
return err
{{end}}{{/* if eq .NameTag "JSON" */ -}}
}
{{end}}
{{end}}
{{if eq 0 (len .Contents) -}}
{{if and $fixedStatusCode $isRef -}}
type {{$opid}}{{$statusCode}}Response {{if not $isExternalRef}}={{end}} {{$ref}}Response
{{else -}}
type {{$opid}}{{$statusCode}}Response struct {
{{if $hasHeaders -}}
Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders
{{end}}
{{if not $fixedStatusCode -}}
StatusCode int
{{end -}}
}
{{end -}}
func (response {{$opid}}{{$statusCode}}Response) Visit{{$opid}}Response(w http.ResponseWriter) error {
{{range $headers -}}
{{if and (eq .Name "Set-Cookie") (eq .Schema.TypeDecl "[]string") -}}
for _, cookie := range response.Headers.SetCookie {
w.Header().Add("{{.Name}}", fmt.Sprint(cookie))
}
{{else -}}
w.Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}}))
{{end -}}
{{end -}}
w.WriteHeader({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}})
return nil
}
{{end}}
{{end}}
{{end}}
// StrictServerInterface represents all server handlers.
type StrictServerInterface interface {
{{range .}}
{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
{{$opid := .OperationId -}}
{{$opid}}(ctx context.Context, request {{$opid | ucFirst}}RequestObject) ({{$opid | ucFirst}}ResponseObject, error)
{{end}}{{/* range . */ -}}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment