Echo strict handler unexpected behavior filling request body with path params
Hello!
I encountered a problem while using code generation with the Echo framework and the strict handler functionality.
Problem Definition
The issue arises when defining an HTTP handler with path parameters and a JSON request body, where the request body object has no explicitly defined properties, but has additionalProperties: true. For example:
/{param1}/{param2}:
post:
operationId: HandlerID
parameters:
- name: param1
in: path
required: true
schema:
type: string
- name: param2
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: true
The code-generated request struct for the strict handler looks like this:
// HandlerIDJSONBody defines parameters for HandlerID.
type HandlerIDJSONBody map[string]interface{}
// HandlerIDJSONRequestBody defines body for HandlerID for application/json ContentType.
type HandlerIDJSONRequestBody HandlerIDJSONBody
type HandlerIDRequestObject struct {
Param1 string `json:"param1"`
Param2 string `json:"param2"`
Body *HandlerIDJSONRequestBody
}
The Body field may contain not only data from the request body but also path and query parameters.
The problem
The problem lies in the call to ctx.Bind on the Echo context object here: https://github.com/oapi-codegen/oapi-codegen/blob/2be24b11ae0f0a6610cffc0f1c1b43ea83fd82aa/pkg/codegen/templates/strict/strict-echo.tmpl#L35-L38
The Bind method not only unmarshals the body but also adds path and query parameters to the object, as stated in the Echo documentation:
// Bind binds path params, query params and the request body into provided type `i`. The default binder
// binds body based on Content-Type header.
Bind(i interface{}) [error](https://pkg.go.dev/builtin#error)
As a result, the Body field is populated with path parameters as well.
Suggestion
I suggest replacing the ctx.Bind() call with DefaultBinder.BindBody(). Documentation reference: https://pkg.go.dev/github.com/labstack/echo/v4#DefaultBinder.BindBody.
// BindBody binds request body contents to bindable object
// NB: then binding forms take note that this implementation uses standard library form parsing
// which parses form data from BOTH URL and BODY if content type is not MIMEMultipartForm
// See non-MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseForm
// See MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseMultipartForm
func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
Minimal reproduction
go version
go version go1.22.6 darwin/arm64
go.mod
require (
github.com/labstack/echo/v4 v4.12.0
github.com/oapi-codegen/oapi-codegen/v2 v2.3.0
github.com/oapi-codegen/runtime v1.1.1
)
api.yaml
openapi: 3.0.1
info:
license:
name: Proprietary
url: 'https://en.wikipedia.org/wiki/Proprietary_software'
version: 0.1.0
title: Test API
paths:
/{param1}/{param2}:
post:
operationId: HandlerID
parameters:
- name: param1
in: path
required: true
schema:
type: string
- name: param2
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: true
responses:
'200':
description: OK
config.yaml
package: main
generate:
echo-server: true
strict-server: true
models: true
output: ./cmd/main/api_server.gen.go
main.go for running and printing incoming Body
package main
import (
"context"
"fmt"
"github.com/labstack/echo/v4"
"net/http"
"time"
)
type API struct{}
func (a *API) HandlerID(ctx context.Context, request HandlerIDRequestObject) (HandlerIDResponseObject, error) {
fmt.Println(request.Param1, request.Param2)
fmt.Println(request.Body)
return HandlerID200Response{}, nil
}
func main() {
echo := echo.New()
strictHandler := NewStrictHandler(new(API), nil)
RegisterHandlers(echo, strictHandler)
server := &http.Server{ //nolint:exhaustruct
Handler: echo,
Addr: "localhost:8080",
ReadHeaderTimeout: 1 * time.Second,
}
server.ListenAndServe()
}
Run this code and call curl in terminal
curl "localhost:8080/param1val/param2val" -X POST -H "Content-Type: application/json" -d '{"key": "val"}'
And the server will print following:
param1val param2val
&map[key:val param1:[param1val] param2:[param2val]]
What means that Body contains keys param1 and param2
I have this same problem unfortunately