Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
e8adc266a6
|
|||
|
8d34a0c715
|
1
.gitea/CODEOWNERS
Normal file
1
.gitea/CODEOWNERS
Normal file
@@ -0,0 +1 @@
|
||||
* @go/CoreDevelopers @go/Agents
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -5,6 +5,35 @@ All notable changes to this module will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.0] - 2026-05-12
|
||||
|
||||
### Changed
|
||||
|
||||
- Go directive bumped from 1.25 to 1.26
|
||||
- `xerrors` dependency bumped to v1.0.1
|
||||
- `valid` dependency bumped to v1.0.1
|
||||
|
||||
### Stabilization
|
||||
|
||||
- API committed as stable. `Handle`, `HandleNoBody`, `HandleEmpty`, `HandlerFunc`,
|
||||
`JSON`, `NoContent`, and `Error` are unchanged from v0.10.0.
|
||||
|
||||
[1.0.0]: https://code.nochebuena.dev/go/httputil/compare/v0.10.0...v1.0.0
|
||||
|
||||
## [0.10.0] - 2026-03-25
|
||||
|
||||
### Changed
|
||||
|
||||
- `Error(w http.ResponseWriter, err error)` — now includes `"platformCode"` in the JSON error body when the `*xerrors.Err` carries one; omitted otherwise
|
||||
- Upgraded `code.nochebuena.dev/go/xerrors` dependency to `v0.10.0`
|
||||
|
||||
### Design Notes
|
||||
|
||||
- `platformCode` is the domain-layer error identifier introduced in `xerrors` v0.10.0. It travels through `Error` transparently — no transport-layer logic depends on it.
|
||||
- Backwards-compatible: error responses without a platform code are identical to v0.9.0.
|
||||
|
||||
[0.10.0]: https://code.nochebuena.dev/go/httputil/compare/v0.9.0...v0.10.0
|
||||
|
||||
## [0.9.0] - 2026-03-18
|
||||
|
||||
### Added
|
||||
|
||||
6
go.mod
6
go.mod
@@ -1,10 +1,10 @@
|
||||
module code.nochebuena.dev/go/httputil
|
||||
|
||||
go 1.25
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
code.nochebuena.dev/go/valid v0.9.0
|
||||
code.nochebuena.dev/go/xerrors v0.9.0
|
||||
code.nochebuena.dev/go/valid v1.0.1
|
||||
code.nochebuena.dev/go/xerrors v1.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
8
go.sum
8
go.sum
@@ -1,7 +1,7 @@
|
||||
code.nochebuena.dev/go/valid v0.9.0 h1:o8/tICIoed2+uwBp+TxXa3FE6KmyirU266O4jEUgFCI=
|
||||
code.nochebuena.dev/go/valid v0.9.0/go.mod h1:SKpLcqpEsLMaEk7K3Y0kFF7Y3W5PHAQF6+U6wleFAhg=
|
||||
code.nochebuena.dev/go/xerrors v0.9.0 h1:8wrDto7e44ZW1YPOnT6JrxYXTqnvNuKpAO1/5bcT4TE=
|
||||
code.nochebuena.dev/go/xerrors v0.9.0/go.mod h1:mtXo7xscBreCB7w7smlBP5Onv8H1HVohCvF0I/VXbAY=
|
||||
code.nochebuena.dev/go/valid v1.0.1 h1:8O7uW8ufPk8lZ2EvKmTJpIrp9WsNjRg/KqMTxeTATrc=
|
||||
code.nochebuena.dev/go/valid v1.0.1/go.mod h1:tSZiKc/108a1hGUnKe/dCg+TfM9HPcrdhZyW4VgCLjU=
|
||||
code.nochebuena.dev/go/xerrors v1.0.1 h1:fgXoabY/ZwxAzaM1sKFf3sbL7ZHWyDxItB/rdbnl0mo=
|
||||
code.nochebuena.dev/go/xerrors v1.0.1/go.mod h1:03MMVfrhaf4XmTMgMrEUFCmuZPGHUCKDitiQvwCuwvY=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||
|
||||
@@ -240,6 +240,28 @@ func TestError_NilError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestError_PlatformCode_IncludedWhenSet(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
Error(rec, xerrors.New(xerrors.ErrNotFound, "employee not found").
|
||||
WithPlatformCode("EMPLOYEE_NOT_FOUND"))
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Errorf("want 404, got %d", rec.Code)
|
||||
}
|
||||
m := decodeMap(t, rec)
|
||||
if m["platformCode"] != "EMPLOYEE_NOT_FOUND" {
|
||||
t.Errorf("platformCode: want EMPLOYEE_NOT_FOUND, got %v", m["platformCode"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestError_PlatformCode_OmittedWhenNotSet(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
Error(rec, xerrors.New(xerrors.ErrInternal, "unexpected error"))
|
||||
m := decodeMap(t, rec)
|
||||
if _, ok := m["platformCode"]; ok {
|
||||
t.Errorf("platformCode should be absent for errors without a platform code, got %v", m["platformCode"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorCodeToStatus_AllCodes(t *testing.T) {
|
||||
cases := []struct {
|
||||
code xerrors.Code
|
||||
|
||||
13
response.go
13
response.go
@@ -22,28 +22,31 @@ func NoContent(w http.ResponseWriter) {
|
||||
}
|
||||
|
||||
// Error maps err to the appropriate HTTP status code and writes a JSON error body.
|
||||
// Understands *xerrors.Err — extracts Code and Message; fields are included if present.
|
||||
// Understands *xerrors.Err — extracts Code, PlatformCode, and Message.
|
||||
// Falls back to 500 for unknown errors.
|
||||
func Error(w http.ResponseWriter, err error) {
|
||||
if err == nil {
|
||||
JSON(w, http.StatusInternalServerError, errorBody("INTERNAL", "internal server error", nil))
|
||||
JSON(w, http.StatusInternalServerError, errorBody("INTERNAL", "", "internal server error", nil))
|
||||
return
|
||||
}
|
||||
var xe *xerrors.Err
|
||||
if errors.As(err, &xe) {
|
||||
status := errorCodeToStatus(xe.Code())
|
||||
body := errorBody(string(xe.Code()), xe.Message(), xe.Fields())
|
||||
body := errorBody(string(xe.Code()), xe.PlatformCode(), xe.Message(), xe.Fields())
|
||||
JSON(w, status, body)
|
||||
return
|
||||
}
|
||||
JSON(w, http.StatusInternalServerError, errorBody("INTERNAL", "internal server error", nil))
|
||||
JSON(w, http.StatusInternalServerError, errorBody("INTERNAL", "", "internal server error", nil))
|
||||
}
|
||||
|
||||
func errorBody(code, message string, fields map[string]any) map[string]any {
|
||||
func errorBody(code, platformCode, message string, fields map[string]any) map[string]any {
|
||||
m := map[string]any{
|
||||
"code": code,
|
||||
"message": message,
|
||||
}
|
||||
if platformCode != "" {
|
||||
m["platformCode"] = platformCode
|
||||
}
|
||||
for k, v := range fields {
|
||||
m[k] = v
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user