refactor: adjust oapi-codegen template to the project needs

This commit is contained in:
Dawid Wysokiński 2024-02-10 12:15:23 +01:00
parent 6d61e43143
commit 50d83fcc14
Signed by: Kichiyaki
GPG Key ID: B5445E357FB8B892
3 changed files with 298 additions and 11 deletions

View File

@ -7,6 +7,7 @@ import (
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
"github.com/ettle/strcase"
"github.com/oapi-codegen/runtime"
)
type apiError struct {
@ -78,7 +79,7 @@ type apiErrorRenderer struct {
formatErrorPath func(segments []errorPathSegment) []string
}
var errInternalServerError = apiError{
var errAPIInternalServerError = apiError{
status: http.StatusInternalServerError,
code: "internal-server-error",
message: "internal server error",
@ -95,16 +96,11 @@ func (re apiErrorRenderer) render(w http.ResponseWriter, r *http.Request) {
switch {
case errors.As(err, &apiErr):
case errors.As(err, &paramFormatErr):
apiErr = apiError{
status: http.StatusBadRequest,
code: "invalid-param-format",
path: []string{"$query", paramFormatErr.ParamName},
message: paramFormatErr.Err.Error(),
}
apiErr = re.invalidParamFormatErrorToAPIError(paramFormatErr)
case errors.As(err, &domainErr):
apiErr = re.domainErrorToAPIError(domainErr)
default:
apiErr = errInternalServerError
apiErr = errAPIInternalServerError
}
errs = append(errs, apiErr)
@ -113,6 +109,34 @@ func (re apiErrorRenderer) render(w http.ResponseWriter, r *http.Request) {
renderJSON(w, r, errs.status(), errs.toResponse())
}
func (re apiErrorRenderer) invalidParamFormatErrorToAPIError(
paramFormatErr *apimodel.InvalidParamFormatError,
) apiError {
var location string
switch paramFormatErr.Location {
case runtime.ParamLocationPath:
location = "$path"
case runtime.ParamLocationHeader:
location = "$header"
case runtime.ParamLocationQuery:
location = "$query"
case runtime.ParamLocationCookie:
location = "$cookie"
case runtime.ParamLocationUndefined:
fallthrough
default:
location = "$unknown"
}
return apiError{
status: http.StatusBadRequest,
code: "invalid-param-format",
path: []string{location, paramFormatErr.ParamName},
message: paramFormatErr.Err.Error(),
}
}
func (re apiErrorRenderer) domainErrorToAPIError(domainErr domain.Error) apiError {
message := domainErr.Error()
var pathElems []errorPathSegment
@ -148,13 +172,13 @@ func (re apiErrorRenderer) domainErrorToAPIError(domainErr domain.Error) apiErro
if len(pathElems) > 0 {
if re.formatErrorPath == nil {
return errInternalServerError
return errAPIInternalServerError
}
path = re.formatErrorPath(pathElems)
if len(path) == 0 {
return errInternalServerError
return errAPIInternalServerError
}
}

View File

@ -1,3 +1,3 @@
package apimodel
//go:generate oapi-codegen --config=config.yml ../../../../api/openapi3.yml
//go:generate oapi-codegen -templates templates/ --config=config.yml ../../../../api/openapi3.yml

View File

@ -0,0 +1,263 @@
// ServerInterfaceWrapper converts contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
HandlerMiddlewares []MiddlewareFunc
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
type MiddlewareFunc func(http.Handler) http.Handler
{{range .}}{{$opid := .OperationId}}
// {{$opid}} operation middleware
func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Request) {
ctx := r.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}} = chi.URLParam(r, "{{.ParamName}}")
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(chi.URLParam(r, "{{.ParamName}}")), &{{$varName}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", chi.URLParam(r, "{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{Location: runtime.ParamLocationPath, ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{end}}
{{range .SecurityDefinitions}}
ctx = context.WithValue(ctx, {{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}})
{{end}}
{{if .RequiresParamObject}}
// Parameter object where we will unmarshal all parameters from the context
var params {{.OperationId}}Params
q := r.URL.Query()
{{range $paramIdx, $param := .QueryParams}}
{{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}}
// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
{{ end }}
{{ if (or (or .Required .IsPassThrough) .IsJson) }}
if paramValue := r.URL.Query().Get("{{.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 {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}{{if .Required}} else {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
return
}{{end}}
{{end}}
{{if .IsStyled}}
err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", q, &params.{{.GoName}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{Location: runtime.ParamLocationQuery, ParamName: "{{.ParamName}}", Err: 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 {
siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "{{.ParamName}}", Count: 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 {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{Location: runtime.ParamLocationHeader, ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
} {{if .Required}}else {
err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found")
siw.ErrorHandlerFunc(w, r, &RequiredHeaderError{ParamName: "{{.ParamName}}", Err: err})
return
}{{end}}
{{end}}
{{end}}
{{range .CookieParams}}
var cookie *http.Cookie
if cookie, err = r.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 {
err = fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}'")
siw.ErrorHandlerFunc(w, r, &UnescapedCookieParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
err = json.Unmarshal([]byte(decoded), &value)
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
{{- if .IsStyled}}
var value {{.TypeDef}}
err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{Location: runtime.ParamLocationCookie, ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}
{{- if .Required}} else {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
return
}
{{- end}}
{{end}}
{{end}}
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.{{.OperationId}}(w, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}})
}))
{{if opts.Compatibility.ApplyChiMiddlewareFirstToLast}}
for i := len(siw.HandlerMiddlewares) -1; i >= 0; i-- {
handler = siw.HandlerMiddlewares[i](handler)
}
{{else}}
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
{{end}}
handler.ServeHTTP(w, r.WithContext(ctx))
}
{{end}}
type UnescapedCookieParamError struct {
ParamName string
Err error
}
func (e *UnescapedCookieParamError) Error() string {
return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName)
}
func (e *UnescapedCookieParamError) Unwrap() error {
return e.Err
}
type UnmarshalingParamError struct {
ParamName string
Err error
}
func (e *UnmarshalingParamError) Error() string {
return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error())
}
func (e *UnmarshalingParamError) Unwrap() error {
return e.Err
}
type RequiredParamError struct {
ParamName string
}
func (e *RequiredParamError) Error() string {
return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName)
}
type RequiredHeaderError struct {
ParamName string
Err error
}
func (e *RequiredHeaderError) Error() string {
return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName)
}
func (e *RequiredHeaderError) Unwrap() error {
return e.Err
}
type InvalidParamFormatError struct {
Location runtime.ParamLocation
ParamName string
Err error
}
func (e *InvalidParamFormatError) Error() string {
return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error())
}
func (e *InvalidParamFormatError) Unwrap() error {
return e.Err
}
type TooManyValuesForParamError struct {
ParamName string
Count int
}
func (e *TooManyValuesForParamError) Error() string {
return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count)
}