refactor: adjust oapi-codegen template to the project needs
This commit is contained in:
parent
6d61e43143
commit
50d83fcc14
|
@ -7,6 +7,7 @@ import (
|
||||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
||||||
"github.com/ettle/strcase"
|
"github.com/ettle/strcase"
|
||||||
|
"github.com/oapi-codegen/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiError struct {
|
type apiError struct {
|
||||||
|
@ -78,7 +79,7 @@ type apiErrorRenderer struct {
|
||||||
formatErrorPath func(segments []errorPathSegment) []string
|
formatErrorPath func(segments []errorPathSegment) []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var errInternalServerError = apiError{
|
var errAPIInternalServerError = apiError{
|
||||||
status: http.StatusInternalServerError,
|
status: http.StatusInternalServerError,
|
||||||
code: "internal-server-error",
|
code: "internal-server-error",
|
||||||
message: "internal server error",
|
message: "internal server error",
|
||||||
|
@ -95,16 +96,11 @@ func (re apiErrorRenderer) render(w http.ResponseWriter, r *http.Request) {
|
||||||
switch {
|
switch {
|
||||||
case errors.As(err, &apiErr):
|
case errors.As(err, &apiErr):
|
||||||
case errors.As(err, ¶mFormatErr):
|
case errors.As(err, ¶mFormatErr):
|
||||||
apiErr = apiError{
|
apiErr = re.invalidParamFormatErrorToAPIError(paramFormatErr)
|
||||||
status: http.StatusBadRequest,
|
|
||||||
code: "invalid-param-format",
|
|
||||||
path: []string{"$query", paramFormatErr.ParamName},
|
|
||||||
message: paramFormatErr.Err.Error(),
|
|
||||||
}
|
|
||||||
case errors.As(err, &domainErr):
|
case errors.As(err, &domainErr):
|
||||||
apiErr = re.domainErrorToAPIError(domainErr)
|
apiErr = re.domainErrorToAPIError(domainErr)
|
||||||
default:
|
default:
|
||||||
apiErr = errInternalServerError
|
apiErr = errAPIInternalServerError
|
||||||
}
|
}
|
||||||
|
|
||||||
errs = append(errs, apiErr)
|
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())
|
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 {
|
func (re apiErrorRenderer) domainErrorToAPIError(domainErr domain.Error) apiError {
|
||||||
message := domainErr.Error()
|
message := domainErr.Error()
|
||||||
var pathElems []errorPathSegment
|
var pathElems []errorPathSegment
|
||||||
|
@ -148,13 +172,13 @@ func (re apiErrorRenderer) domainErrorToAPIError(domainErr domain.Error) apiErro
|
||||||
|
|
||||||
if len(pathElems) > 0 {
|
if len(pathElems) > 0 {
|
||||||
if re.formatErrorPath == nil {
|
if re.formatErrorPath == nil {
|
||||||
return errInternalServerError
|
return errAPIInternalServerError
|
||||||
}
|
}
|
||||||
|
|
||||||
path = re.formatErrorPath(pathElems)
|
path = re.formatErrorPath(pathElems)
|
||||||
|
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
return errInternalServerError
|
return errAPIInternalServerError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
package apimodel
|
package apimodel
|
||||||
|
|
||||||
//go:generate oapi-codegen --config=config.yml ../../../../api/openapi3.yml
|
//go:generate oapi-codegen -templates templates/ --config=config.yml ../../../../api/openapi3.yml
|
||||||
|
|
|
@ -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, ¶ms.{{.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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user