Skip to content

feat: add Bind and built‑in decoders for JSON, XML, query, form, path, header - #52

Merged
qm012 merged 7 commits into
mainfrom
feat_binder
Sep 3, 2026
Merged

feat: add Bind and built‑in decoders for JSON, XML, query, form, path, header#52
qm012 merged 7 commits into
mainfrom
feat_binder

Conversation

@qm012

@qm012 qm012 commented Aug 25, 2026

Copy link
Copy Markdown
Owner

What

Add a generic request-binding API to sim:

  • Decoder[T] / DecoderFunc[T]: pluggable interface for decoding an HTTP request into a *T
  • Validator: optional post-decode validation hook, invoked automatically by Bind when implemented
  • Bind[T](r, src): decodes the request with the given decoder and returns the decoded value or error
  • Built-in decoders: BindJSON, BindXML, BindQuery, BindForm, BindPath, BindHeader
  • JSON decoder options: UseNumber(), DisallowUnknownFields()
  • BufferBody: buffers the request body, restores it for subsequent reads, and caches a copy in the context for repeated reads via BodyFromContext

Usage

JSON body with validation:

type createUser struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

func (u *createUser) Validate(ctx context.Context) error {
    if u.Email == "" {
        return errors.New("email required")
    }
    return nil
}

app.Post("/users", func(w http.ResponseWriter, r *http.Request) {
    u, err := sim.BindJSON[createUser](r, sim.DisallowUnknownFields())
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    // use u.Name, u.Email ...
})

Query with default= and a map target:

type listQuery struct {
    Page  int `query:"page,default=1"`
    Limit int `query:"limit,default=20"`
}

app.Get("/users", func(w http.ResponseWriter, r *http.Request) {
    q, err := sim.BindQuery[listQuery](r)
    // ...
    all, err := sim.BindQuery[map[string][]string](r)
})

Path and header:

type userPath struct {
    ID int64 `path:"id"`
}

type authHeader struct {
    Token string `header:"Authorization"`
}

app.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
    p, err := sim.BindPath[userPath](r)
    h, err := sim.BindHeader[authHeader](r)
})

Multipart form with file upload:

type uploadForm struct {
    Title string                  `form:"title"`
    File  *multipart.FileHeader   `form:"file"`
}

app.Post("/upload", func(w http.ResponseWriter, r *http.Request) {
    f, err := sim.BindForm[uploadForm](r)
})

Repeated body reads via BufferBody:

func BufferBodyHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		nr, err := sim.BufferBody(r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		h.ServeHTTP(w, nr)
	})
}

Custom formats plug in through sim.Decoder[T] / sim.DecoderFunc[T] and are passed to sim.Bind.

@qm012 qm012 self-assigned this Aug 25, 2026
@qm012 qm012 added the enhancement New feature or request label Aug 25, 2026
@qm012

qm012 commented Aug 26, 2026

Copy link
Copy Markdown
Owner Author

related #42

fix

fix test

readme
@qm012

qm012 commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

/lgtm

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/qm012/sim 94.37% (+3.10%) 👍

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/qm012/sim/binder.go 97.46% (+97.46%) 276 (+276) 269 (+269) 7 (+7) 🌟
github.com/qm012/sim/sim.go 0.00% (ø) 0 0 0

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/qm012/sim/binder_test.go
  • github.com/qm012/sim/example_test.go

@qm012
qm012 merged commit a3caa73 into main Sep 3, 2026
6 checks passed
@qm012
qm012 deleted the feat_binder branch September 3, 2026 07:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant