Error Code¶
web/error_code.go provides a structured error type ErrorCode for returning HTTP error responses with business codes from handlers.
ErrorCode Structure¶
type ErrorCode struct {
Code int `json:"code"`
Message string `json:"message"`
Detail string `json:"detail,omitempty"`
Err error `json:"-"`
}
| Field | Description |
|---|---|
Code |
Business/HTTP error code, serialized to the response JSON |
Message |
Short error summary |
Detail |
Optional detailed description |
Err |
Wrapped original error, not serialized, supports errors.Is / errors.As |
Error Code Constants¶
| Constant | Value | Meaning |
|---|---|---|
CodeOK |
200 | Success |
CodeBadRequest |
400 | Bad request |
CodeUnauthorized |
401 | Unauthorized |
CodeForbidden |
403 | Forbidden |
CodeNotFound |
404 | Not found |
CodeMethodNotAllowed |
405 | Method not allowed |
CodeTooManyRequests |
429 | Too many requests |
CodeInternalError |
500 | Internal server error |
CodeServiceUnavailable |
503 | Service unavailable |
CodeValidationFailed |
1001 | Validation failed |
CodeDuplicateEntry |
1002 | Duplicate entry |
CodeTokenExpired |
1003 | Token expired |
CodeTokenInvalid |
1004 | Token invalid |
Constructing Errors¶
Pre-defined constructors¶
Each pre-defined error has a constructor that returns a mutable copy:
return nil, web.NewBadRequest().WithDetail("invalid params") // 400
return nil, web.NewUnauthorized().WithError(err) // 401
return nil, web.NewForbidden().WithDetail("access denied") // 403
return nil, web.NewNotFound().WithDetail("user not found") // 404
return nil, web.NewMethodNotAllowed().WithDetail("not allowed") // 405
return nil, web.NewTooManyRequests().WithDetail("rate limited") // 429
return nil, web.NewInternalError().WithError(err) // 500
return nil, web.NewServiceUnavailable().WithDetail("try later") // 503
// Business errors
return nil, web.NewValidationError().WithError(err) // 1001
return nil, web.NewDuplicateEntry().WithDetail("email exists") // 1002
return nil, web.NewTokenExpired().WithDetail("token expired") // 1003
return nil, web.NewTokenInvalid().WithDetail("token invalid") // 1004
Custom error code¶
Chaining Methods¶
WithDetail(detail string) *ErrorCode— attach detailed informationWithError(err error) *ErrorCode— wrap the original error
Usage in Handlers¶
func (c *UserController) Get(req *web.Request) (any, error) {
user, err := c.userService.GetById(req.ParamUint("id"))
if err != nil {
return nil, web.NewInternalError().WithError(err)
}
if user == nil {
return nil, web.NewNotFound().WithDetail("user not found")
}
return user, nil
}
Error Mapping¶
web.ClassifyError(value, err) maps an error to an HTTP status code and message. DefaultConverter uses this internally:
- If the error chain contains
*ErrorCode, use itsCode. - If the return value is
*web.MessagewithCode != 200, useMessage.Code. os.ErrNotExist→ 404;os.ErrPermission→ 403.- Any other error → 500.
You can also call it directly in your own converter or filter:
code, msg := web.ClassifyError(nil, err)
req.Response().AbortWithStatusJSON(code, &web.Message{Code: code, Msg: msg})
Handle Errors Uniformly in a Filter¶
func (f *ErrorHandlerFilter) Handle(fc web.FilterChain, req *web.Request) (any, error) {
result, err := fc.Next()
if err == nil {
return result, nil
}
var ec *web.ErrorCode
if errors.As(err, &ec) {
req.Response().AbortWithStatusJSON(ec.Code, ec)
return nil, nil
}
req.Response().AbortWithStatusJSON(500, web.NewInternalError().WithError(err))
return nil, nil
}
Full Example¶
package main
import (
"context"
"errors"
wf "github.com/chuccp/go-web-frame"
"github.com/chuccp/go-web-frame/config"
"github.com/chuccp/go-web-frame/core"
"github.com/chuccp/go-web-frame/web"
)
type UserController struct{ core.IService }
func (c *UserController) Init(ctx *core.Context) error {
ctx.Get("/users/:id", c.Get)
return nil
}
func (c *UserController) Get(req *web.Request) (any, error) {
if req.ParamUint("id") == 0 {
return nil, web.NewBadRequest().WithDetail("id is required")
}
return map[string]any{"id": req.ParamUint("id"), "name": "alice"}, nil
}
type AuthFilter struct{ core.IFilter }
func (f *AuthFilter) Handle(fc web.FilterChain, req *web.Request) (any, error) {
if req.GetHeader("Authorization") == "" {
return nil, web.NewUnauthorized().WithDetail("missing token")
}
return fc.Next()
}
func main() {
cfg, _ := config.LoadSingleFileConfig("application.yml")
builder := wf.NewBuilder(cfg)
builder.Filter(&AuthFilter{})
builder.Rest(&UserController{})
builder.Build().Run(context.Background())
}
Next Steps¶
- Response Converter - How converters handle ErrorCode
- Filter/Middleware - Handle errors uniformly in filters
- Web API Reference - web.Message and error responses