Errors

Piko's error handling separates the message shown to end users from the internal detail recorded in logs. The Error type carries both. Helpers NewError and Errorf construct instances. Development mode shows the internal detail, and production shows only the safe message. This page documents the API. Source file: errors.go.

An error returned from Call or Render feeds into the dispatcher type-switch. Nil returns 200 with the Response body. ValidationField and ValidationError return 422 with field-error payloads. UnauthorisedError returns 401. ForbiddenError returns 403. NotFoundError returns 404. Any other error returns 500 with the safe message shown to the user and the full error written to logs.

Type

Error

Type alias for safeerror.Error. Any error in a wrap chain can implement the interface. Piko discovers it via errors.As.

Interface:

MethodReturns
Error() stringFull internal string (logged server-side). Inherited from the standard error interface.
SafeMessage() stringUser-visible message.

Concrete instances returned by NewError and Errorf also implement Unwrap() error, so errors.Is and errors.As traverse the wrapped cause. The interface itself does not require Unwrap. Any error type that adds a SafeMessage() string method satisfies it.

Functions

NewError(safeMessage string, cause error) error

Wraps a cause error with a user-safe message.

func Render(r *piko.RequestData, props piko.NoProps) (Response, piko.Metadata, error) {
    user, err := loadUser(r.Context(), id)
    if err != nil {
        return Response{}, piko.Metadata{}, piko.NewError("could not load user profile", err)
    }
    return Response{User: user}, piko.Metadata{}, nil
}

In production, the end user sees "could not load user profile". The log line contains the full underlying cause.

Errorf(safeMessage string, internalFormat string, args ...any) error

Same contract as NewError but with a fmt.Errorf-style internal format:

return piko.Errorf("could not process order",
    "loading order %s from database: %w", orderID, err)

The %w verb still produces a wrapped error compatible with errors.Is and errors.As.

IsDevelopmentMode(r *RequestData) bool

Reports whether Piko is serving the current request in development mode (piko dev or piko dev-i). Returns false when r is nil or the context lacks development-mode information.

Use inside error-page Render functions to decide whether to expose internal detail:

func Render(r *piko.RequestData, props piko.NoProps) (Response, piko.Metadata, error) {
    errCtx := piko.GetErrorContext(r)
    if errCtx == nil {
        return Response{Message: "Unknown error"}, piko.Metadata{}, nil
    }

    message := errCtx.Message
    if piko.IsDevelopmentMode(r) && errCtx.InternalMessage != "" {
        message = errCtx.InternalMessage
    }

    return Response{Code: errCtx.StatusCode, Message: message}, piko.Metadata{}, nil
}

Typed errors for validation

HelperUseHTTP status
piko.ValidationField(field, message)Attach a validation message to a specific form field.422
piko.NewValidationError(fields map[string]string)Multi-field validation error.422

These integrate with action forms. The dispatch layer maps each field error to the matching input.

HTTP status mapping

Action Call and page Render return an error that Piko maps to an HTTP status:

Error shapeHTTP status
nil200
piko.ValidationField, piko.NewValidationError422
piko.BadRequestError400
piko.UnauthorisedError401
piko.ForbiddenError403
piko.NotFoundError404
piko.ConflictError409
piko.TeapotError418
piko.GenericPageError500
Any other error500 (logged with full detail; user sees a generic message)

Error-page context

GetErrorContext(r) returns the error details available to a custom error page:

type ErrorPageContext struct {
    Message         string
    InternalMessage string
    OriginalPath    string
    StatusCode      int
}
FieldPurpose
MessageHuman-readable error message safe for display to users.
InternalMessageFull internal error detail, populated only in development mode (piko dev or piko dev-i). Empty in production.
OriginalPathRequest path that triggered the error.
StatusCodeHTTP status code for the error (for example, 404 or 500).

See also