Error Handling
ss-keel-core provides a built-in error type KError that maps directly to HTTP status codes. Any *KError returned from a handler is automatically serialized to a JSON error response.
KError carries a status code, a machine-readable code string, and a human-readable message.
type KError struct { Code string StatusCode int Message string Cause error // optional, not exposed in responses}Built-in Constructors
Section titled âBuilt-in Constructorsâcore.NotFound("user not found") // 404core.Unauthorized("token expired") // 401core.Forbidden("insufficient permissions") // 403core.Conflict("email already exists") // 409core.BadRequest("invalid input") // 400core.Internal("db failed", err) // 500 (cause logged, not exposed)Returning Errors from Handlers
Section titled âReturning Errors from HandlersâReturn a *KError directly from your handler:
func (c *UserController) getByID(ctx *core.Ctx) error { id := ctx.Params("id")
user, err := c.service.GetByID(ctx.Context(), id) if err != nil { return core.NotFound("user not found") }
return ctx.OK(user)}The frameworkâs error handler detects *KError via errors.As and returns:
{ "code": "NOT_FOUND", "message": "user not found", "statusCode": 404}Validation Errors
Section titled âValidation ErrorsâParseBody automatically returns a 422 Unprocessable Entity with field-level errors when validation fails:
{ "errors": [ { "field": "email", "message": "must be a valid email" }, { "field": "name", "message": "this field is required" } ]}You donât need to handle this manually â returning the error from ParseBody is enough:
func (c *UserController) create(ctx *core.Ctx) error { var dto CreateUserDTO if err := ctx.ParseBody(&dto); err != nil { return err // 400 or 422 automatically } ...}Wrapping Errors
Section titled âWrapping ErrorsâUse core.Internal when an unexpected error occurs so the cause is logged but not leaked to the client:
result, err := db.Query(...)if err != nil { return core.Internal("failed to query users", err) // Response: 500 Internal Server Error // The original error is logged internally}Propagating Errors Through Layers
Section titled âPropagating Errors Through LayersâDefine domain-level errors in your service layer and return them from handlers:
var ErrUserNotFound = core.NotFound("user not found")var ErrEmailTaken = core.Conflict("email already in use")
// users/service.gofunc (s *UserService) GetByID(ctx context.Context, id string) (*User, error) { user, err := s.repo.FindByID(ctx, id) if err != nil { return nil, ErrUserNotFound } return user, nil}
// users/controller.gofunc (c *UserController) getByID(ctx *core.Ctx) error { user, err := c.service.GetByID(ctx.Context(), ctx.Params("id")) if err != nil { return err // KError propagates up to the error handler } return ctx.OK(user)}Error Response Format
Section titled âError Response Formatâ| Constructor | Status | Code |
|---|---|---|
NotFound(msg) | 404 | NOT_FOUND |
Unauthorized(msg) | 401 | UNAUTHORIZED |
Forbidden(msg) | 403 | FORBIDDEN |
Conflict(msg) | 409 | CONFLICT |
BadRequest(msg) | 400 | BAD_REQUEST |
Internal(msg, cause) | 500 | INTERNAL |